Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default property value with closure makes a compiler to recompile all files

This source has a paragraph Setting a Default Property Value with a Closure or Function where we can find an example

Here’s a skeleton outline of how a closure can be used to provide a default property value:

class SomeClass {

    let someProperty: SomeType = {
         // create a default value for someProperty inside this closure
         // someValue must be of the same type as SomeType
         return someValue
    }() 
}

Well, I use it very often... Also, I often wait for the whole project to recompile after changing just one symbol. And today I have discovered that these two things are associated to each other.

Lets imagine we have some class where we set some default properties with a closure and with a function

class Class1 {
    
    let value: Int
    
    init(_ value: Int) {
        self.value = value
    }
    
    private lazy var lazyValueWithClosure: Int = {
        return 1111
    }()
    
    private lazy var lazyValueWithFunction: Int = self.getValue()
    
    private func getValue() -> Int {
        return 2222
    }
}

Also we have some other class in a separate file where we use the above Class1

class Class2 {
    
    let value: Int
    
    init(_ value: Int) {
        self.value = value
        _ = Class1(100)
    }
}

And some other class in a separate file where we use Class2

class Class3 {
    
    let value: Int
    
    init(_ value: Int) {
        self.value = value
        _ = Class2(100)
    }
}

and etc...

I've decided to use terminal + xcodebuild + grep to get only info about recompiled files. That is the command I use to get compilation info:

xcodebuild -scheme Test -sdk iphonesimulator -arch x86_64 -configuration Debug build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep '^[0-9]\{1,20\}.[0-9]\{1,20\}ms.*init(_ value: Int)'

That is all for preparations. Now we go to Class1 and change 2222 to some other value. Run the above command and get a result.

0.1ms   /Users/iwheelbuy/Documents/recompile/Test/Classes/Class1.swift:11:5 init(_ value: Int)

The result is good. Setting default value with functions works as expected. We have changed one file and only one file was compiled.

Then lets change the value 1111 from the Class1 to some other value and run the command. Terminal output now looks like this:

0.8ms   /Users/iwheelbuy/Documents/recompile/Test/Classes/Class5.swift:11:5 init(_ value: Int)
0.3ms   /Users/iwheelbuy/Documents/recompile/Test/Classes/Class1.swift:11:5 init(_ value: Int)
1.0ms   /Users/iwheelbuy/Documents/recompile/Test/Classes/Class4.swift:11:5 init(_ value: Int)
0.3ms   /Users/iwheelbuy/Documents/recompile/Test/Classes/Class3.swift:11:5 init(_ value: Int)
0.3ms   /Users/iwheelbuy/Documents/recompile/Test/Classes/Class2.swift:11:5 init(_ value: Int)

All the classes were recompiled... Now imagine that you have a large project and any small change in a default value closure makes you wait for the whole project to recompile.

Questions:

  • What is the reason?
  • Any suggestions how to use default value closures and not to suffer from recompilation?
  • Related to this topic?
like image 683
iWheelBuy Avatar asked Jan 14 '17 06:01

iWheelBuy


1 Answers

This is a known problem in the Swift compiler. The issue is that once you use closures or lazy properties like this, every single Swift file will be type checked. I've written a blog post on this topic which you can find here.

like image 142
Robert Gummesson Avatar answered Nov 05 '22 14:11

Robert Gummesson