6 Replies
      Latest reply: Sep 12, 2016 9:32 PM by macnib RSS
      macnib Level 1 Level 1 (10 points)

        Sort of an odd problem. I have an app and a framework. Both projects are about 95% swift. ( some exceptions like sqlite config which wanted var args in c ... Swift did not like that ) In debug mode, the program runs as expected. Things are moving along positively and I decided it was time to try some optimizations out. eg. Make classes final and turn on whole module optimization -- optimize across files is my understanding.

         

        If I turn on whole module optimization on both the app and the framework, then the app crashes. An unexpected nil in a optional. If I only turn on whole module optimization on the frame work but leave the app fast optimization or debug, then the app does not crash.

         

        After some more investigation, it appears a class that inherits NSObject and overrides the initialize method was not called. Turns out the particular class only has class methods and initializes a static properties once. That is no instance methods. ( though it inherits instance methods from NSObject but I'm not using those instance methods )  But if whole module optimization is on for the framework and the app, then initialize never gets called.

         

        For kicks, I got rid of the NSObject inheritance. Then I attempted to set a static property with a cloture. The cloture would return a bool but also setup too.  But that never got called either -- it could not set up its singleton state. So, the app crashed. Data structures not constructed resulting in a nil in an optional.

         

        Oddly, what did work was to create an instance of the class and throw it away. I'm not using instance methods here. In this case, the overridden initialize method in NSObject was called once. So, the bug is I should not have to create an instance. Should it be that if the class method is called, then initialize must be called the first time automatically? Whole module optimization seems to interfere with this and debug mode works. Inconsistent behavior...

        • Re: Initialize & whole module optimization
          macnib Level 1 Level 1 (10 points)

          Also, the class not being initialized was internal to the framework. Not sure if thats going to make a difference.

          • Re: Initialize & whole module optimization
            eskimo Apple Staff Apple Staff (7,845 points)

            I suspect that none of the following will be news to you.  If so, please post some cut down snippets of each of your approaches so that we can see the specific details.

            Oh, another useful tip in situations like this is to put a breakpoint on your +initialize method, run it in the working case, and look at the backtrace to see how you got initialised.


            After some more investigation, it appears a class that inherits NSObject and overrides the initialize method was not called.

            In the above, does “initialize method” mean the initialize() class method (or +initialize in Objective-C)?  If so, you do have to be careful here because that method is only called when the class is actually used.  For example, this tiny test program prints false unless you uncomment line A.

            import Foundation
            
            var gIsSetUp: Bool = false
            
            class Test : NSObject {
                override class func initialize() {
                    gIsSetUp = true
                }
            }
            
            // print(Test.self)        // line A
            print(gIsSetUp)
            

            For kicks, I got rid of the NSObject inheritance. Then I attempted to set a static property with a cloture.

            Does anyone actually read this static property?  Static properties are initialised lazily, so you have to read it for that closure to run.

            Share and Enjoy

            Quinn “The Eskimo!”
            Apple Developer Relations, Developer Technical Support, Core OS/Hardware
            let myEmail = "eskimo" + "1" + "@apple.com"

              • Re: Initialize & whole module optimization
                macnib Level 1 Level 1 (10 points)

                Yes, this is override of initialize method in Objective-C.

                 

                Debug works fine. The initialize method is called when expected. When I optimize with whole module its difficult to run the debugger. So, I put in print statements and nothing was printed to the console.

                 

                Nothing external to the class actually reads the static property. Code within the class method does, though. Only the class method is called. The user does not have to worry about boilerplate setup. They just call the class methods they need.

                 

                It works in debug. I suspect the optimizer is building up information to what methods were called to trigger initialization.(?)

                 

                 

                
                //Something to hold stuff.
                typealias boolMap = [Int:Bool]
                
                //An internal class in my framework. Its used by a public class in the framework.
                final class ClassMethodsWrapper : NSObject {
                
                  static var boolStates = [Int:boolMap]()
                
                  override class func initialize() {
                
                       //Might get called more than once. ( per Objective-C documentation & examples )
                       if self != ClassMethodsWrapper.self {
                            return
                       }
                
                       //Build up data structure
                       //Do setup here for singleton
                  }
                
                  //No init or instance methods. ( thats down stairs )
                  //Only class methods here.
                  class func method1(program:Int) {
                     //Do something with boolStates
                  }
                
                  class func method2(program:Int) {
                  }
                
                
                }
                
                
                
                
                

                 

                This was the original intention from the ported code.

                 

                //Call the singleton without any setup boiler plate
                
                //Called from public class and public func exposed from frameworks module.
                ClassMethodsWrapper.method1(123)
                
                
                
                
                
                

                 

                This was the fix for whole module optimization.

                 

                //This would fix the issue. But I did not really need to make an instance here.
                //My port did not have an instance.
                
                let throwAway = ClassMethodsWrapper()
                
                //Call the singleton without any setup boiler plate
                //Never used instance throwAway
                ClassMethodsWrapper.method1(123)
                
                
                
                
                
                

                 

                I tried this too and it did not work. Thought I would try something more Swift like. Again, issue is with the whole module optimization.

                 

                typealias boolMap = [Int:Bool]
                
                //Get rid of NSObject and initialize.
                final class ClassMethodsWrapper {
                
                  static var boolStates = [Int:boolMap]()
                
                  static var initialized:Bool = {
                
                       //Build up data structure
                
                       //All done...
                       return true
                  }
                
                  //No init or instance methods.
                  //Only class methods.
                  class func method1(program:Int) {
                  }
                
                  class func method2(program:Int) {
                  }
                
                
                }
                
                
                
                
                
                  • Re: Initialize & whole module optimization
                    eskimo Apple Staff Apple Staff (7,845 points)

                    I’m surprised that your first approach doesn’t work.  I actually tested it myself (Xcode 8.0b6) with a tiny test project like this:

                    import Foundation
                    
                    class Test : NSObject { 
                    
                      override class func initialize() { 
                        NSLog("+initialise")
                      } 
                    
                      @objc class func myClassMethod() { 
                      } 
                    } 
                    
                    Test.myClassMethod()
                    

                    If I build this with the Release build configuration and then run it, it prints nothing.  OTOH, if I put the Test class into a separate Test.swift file, it prints this:

                    2016-09-05 10:19:21.950 xxx[18996:729389] +initialise
                    

                    That’s bugworthy IMO.

                    However, if I were in your shoes I’d move away from +initialize and instead rely on Swift’s race-free static variable initialisation.  For example:

                    class Test { 
                        static let myVar: Int = someComplexFunction()
                    }
                    

                    You can use a closure to do this inline:

                    class Test { 
                        static let myVar: Int = {
                            return 6 * 7
                        }()
                    }
                    

                    Share and Enjoy

                    Quinn “The Eskimo!”
                    Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                    let myEmail = "eskimo" + "1" + "@apple.com"

                      • Re: Initialize & whole module optimization
                        macnib Level 1 Level 1 (10 points)

                        I tried your suggestion. This is still not working for me. I get this error because initialize was not called creating the data structure. Also, no print --

                         

                        fatal error: unexpectedly found nil while unwrapping an Optional value

                         

                        I think the only difference from what I tested before was the trailing () on the cloture. I tried other stuff like making the class public and taking out the final keyword. Same issue.

                         

                        class Test {
                            static let myVar: Int = {
                                return 6 * 7
                            }()
                        }
                        
                        

                         

                        I suspect its something about the class being in the framework and perhaps internal without an instance. In the past, in Objective-C, I've run into issues where a class was being used in a storyboard only and would get stripped out. So, I would do a trick like [MyObjClass class] as a no op to get it linked into the framework. But I tried calling self in Swift and that did not work either. But creating an instance and throwing it away seems to work but not ideally.

                         

                        Also, regarding the framework build, I made sure dead code stripping was turned off and Don't Dead Strip Inits and Terms is turned on.

                         

                        The framework presents a public class that overrides UIView. That custom view is being instanced via a storyboard in the application ( which would be another module ). While the public view custom class is being loaded, that is when the internal class is being used. At that point, it should initialize or call that cloture and then allow class methods to be called. For test sakes, that would be when class Test above is called.

                         

                        Ya, I want to sweep through and remove as many "NS" objects I can. Try to preempt Swift 3.

                         

                        The good news is my port is now running acceptably with Swift. Though, I do have some kludges in there. Using final keywords and whole module optimization appears to have made things faster than Objective-C was prior. About 5 months of effort. Had to minimize, rwrite and isolate c++ code. Had to migrate to arc. Had to migrate to swift. Kind of feel more relaxed about the future.

                  • Re: Initialize & whole module optimization
                    macnib Level 1 Level 1 (10 points)

                    Just a note on this. It appears Swift 3.0 fixed the problem for me.

                     

                    The initialize method in Objective-C is now being called without making an instance of the class. The class is private in a swift framework.