47

Objective-C declares a class function, initialize(), that is run once for each class, before it is used. It is often used as an entry point for exchanging method implementations (swizzling), among other things.

Swift 3.1 deprecates this function with a warning:

Method 'initialize()' defines Objective-C class method 'initialize', which is not guaranteed to be invoked by Swift and will be disallowed in future versions

How can this be resolved, while still maintaining the same behaviour and features that I currently implement using the initialize() entry point?

Jordan Smith
  • 10,310
  • 7
  • 68
  • 114
  • 1
    To be more precise, the *method* `+initialize` is executed before anything in the class is used. In reality it is executed, when the bundle that contains the class is loaded, what can happen after app start. – Amin Negm-Awad Mar 16 '17 at 05:10
  • @AminNegm-Awad: I am not sure if that is correct. Perhaps you are thinking of the `load` method (which isn't available in Swift)? – Martin R Mar 16 '17 at 08:09
  • 1
    It is the same for `+initialize`. *The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program.* There is no guarantee that this is done at program start. Such a guarantee would cause an immediate load of all bundles to get the code in `+initialize`. – The difference to `+load` is, that it is executed on classes *and categories*. (Methods with the same selector in class and categorie!) But, of course, the fact of the possible late invocation is more important for categories than classes. – Amin Negm-Awad Mar 16 '17 at 08:27
  • @AminNegm-Awad: Perhaps I misunderstood your first comment. You said that in reality, `initialize` is called when the bundle ... is loaded. That is not what I experienced. – Martin R Mar 16 '17 at 08:35
  • Maybe. The OP said, that `+initialize` is executed at app start. But this is not true, if a bundle is loaded after app start. In such a case the earliest execution time for `+initialize` is, when the bundle is loaded. (Obviously `+initialize` cannot be executed before the bundle containing it, is loaded.) I agree that most developers has never made that experience. This is, because you need a situation in that a bundle is loaded after app start. You rarely have that. And the time of loading of bundles is an implementation detail. You simply cannot rely on loading all bundles at app start. – Amin Negm-Awad Mar 16 '17 at 08:46
  • To have an example: I wrote an AOP framework for Objective-C. Since the business logic should not refer to the aspects, I needed a way to automatically install the advices. Obviously `+initialize` (`AOPSomeBaseClass`) would be a good place to do that. I expected and experienced that this method is never executed, because the business logic intentionally didn't not refer to the framework and the framework has never been loaded, causing that the method is never been executed. So you need to nudge. Since you understand german, I link to the talk: https://macoun.de/video2009/ts6.php – Amin Negm-Awad Mar 16 '17 at 08:54
  • @AminNegm-Awad to be very technically correct, `initialize()` is called on any NSObject class before the first message to it is sent. In the case of method swizzling (the common reason to use `initialize()` in Swift), it doesn't matter - method exchange will happen before any message is sent, so all is well and good. – Jordan Smith Mar 16 '17 at 09:13
  • 1
    Yes, as I said hours ago. But it matters sometimes, i. e. in the case I mentioned. However, it is not guaranteed that it is sent to the class object (in Objective-C there are no member functions like `initialize()` and they are not called, but messages are sent) at app start. – Amin Negm-Awad Mar 16 '17 at 12:54
  • @AminNegm-Awad (nice to see you btw) - I think one legit think you want to do in initialize is to set NSApplicationCrashOnExceptions to true (NSUserDefaults). Otherwise your app won't crash and thus crash reporting tools cannot work properly if this flag is set too late. – Christian Kienle Mar 30 '17 at 15:42
  • @ChristianKienle Hi Chris :-) Yes, something like this or something else. In the early version of the Q was stated, that `+initialize` is executed at app start. I just wanted to correct this, because it is not true in all cases. (And now it is corrected in the Q.) Think of a dynamically linked debugging framework that is *not referred* from the app. If you put something in a `+initialize`, it will never be executed. However, this was a comment, not an answer. – Amin Negm-Awad Mar 31 '17 at 06:49
  • A simple hack for `NSObject`-derived classes would simply to introduce an Objective-C superclass which only does `initialize`. I'd imagine this can be done via creative use of the Objective-C runtime – autogenerate the superclass and make the `initialize` implementation a wrapper to call a method declared in a protocol. – adib Apr 10 '17 at 01:52
  • No, this cannot be done by a "simple hack" of `NSObject`, because the RTE does not even know the classes of not-loaded bundles. This is, because they are not loaded. You would have to search all paths where an app can load bundles from to have the negative effect of all bundles been loaded at app start. What for? The only positive effect would be that the previous version of the Q is correct, what is obviously no design goal. – Amin Negm-Awad Apr 18 '17 at 10:49

10 Answers10

35

Easy/Simple Solution

A common app entry point is an application delegate's applicationDidFinishLaunching. We could simply add a static function to each class that we want to notify on initialization, and call it from here.

This first solution is simple and easy to understand. For most cases, this is what I'd recommend. Although the next solution provides results that are more similar to the original initialize() function, it also results in slightly longer app start up times. I no longer think it is worth the effort, performance degradation, or code complexity in most cases. Simple code is good code.

Read on for another option. You may have reason to need it (or perhaps parts of it).


Not So Simple Solution

The first solution doesn't necessarily scale so well. And what if you are building a framework, where you'd like your code to run without anyone needing to call it from the application delegate?

Step One

Define the following Swift code. The purpose is to provide a simple entry point for any class that you would like to imbue with behavior akin to initialize() - this can now be done simply by conforming to SelfAware. It also provides a single function to run this behavior for every conforming class.

protocol SelfAware: class {
    static func awake()
}

class NothingToSeeHere {

    static func harmlessFunction() {

        let typeCount = Int(objc_getClassList(nil, 0))
        let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
        let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
        objc_getClassList(autoreleasingTypes, Int32(typeCount))
        for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
        types.deallocate(capacity: typeCount)

    }

}

Step Two

That's all good and well, but we still need a way to actually run the function we defined, i.e. NothingToSeeHere.harmlessFunction(), on application startup. Previously, this answer suggested using the Objective-C code to do this. However, it seems that we can do what we need using only Swift. For macOS or other platforms where UIApplication is not available, a variation of the following will be needed.

extension UIApplication {

    private static let runOnce: Void = {
        NothingToSeeHere.harmlessFunction()
    }()

    override open var next: UIResponder? {
        // Called before applicationDidFinishLaunching
        UIApplication.runOnce
        return super.next
    }

}

Step Three

We now have an entry point at application startup, and a way to hook into this from classes of your choice. All that is left to do: instead of implementing initialize(), conform to SelfAware and implement the defined method, awake().

Jordan Smith
  • 10,310
  • 7
  • 68
  • 114
  • 2
    The *elegant* solution in Swift works by using the mechanism of Objective-C? – Amin Negm-Awad Mar 16 '17 at 05:07
  • It's 'elegant' because it can be self contained (no initialization needed from the app delegate), and because it ports the objective c feature across to Swift, where it can be used natively. – Jordan Smith Mar 16 '17 at 05:42
  • 6
    Well, it is the nature of `+initialize` that it is self-contained. That's what it is for. Any other approach isn't a solution at all instead of being an inelegant solution. And I do not think that it is a port. If it would be a port from Objective-C, it would work without Objective-C. But it doesn't. Don't get me wrong, I upvoted your answer. However, it is not an elegant solution, but a hack, because there is no way to do that in Swift. – Amin Negm-Awad Mar 16 '17 at 05:49
  • 2
    @AminNegm-Awad I would suggest that the elegance comes from the unchanged behaviour to the outside user. For a Framework or something similar that relies on the magic of `initialize()`, this allows behaviour to remain unchanged. You are right about my previous comment, doing this doesn't meet the definition of a software 'port'. Perhaps 'bridge' would be a better word. – Jordan Smith Mar 16 '17 at 09:34
  • Indeed, this is necessary. However, it is not completely unchanged, because you have to implement the protocol. Another option could be to ask the RTE for a specific method instead of unwrapping to a protocol type. Well, the term elegance is not defined. Somebody should write an RFC for that … :-] – Amin Negm-Awad Mar 16 '17 at 13:17
  • @adib that's a good point. It doesn't have to be the application delegate, there may be other ways too. – Jordan Smith Mar 28 '17 at 18:42
  • This is great stuff @JordanSmith. Might suggest a small edit to ensure the same behaviour of a `dispatch_once`. Whoops - I see I can't format that nicely here in this comment, so I'll add an addendum answer below. – Scott Corscadden Mar 30 '17 at 18:15
  • 1
    I think the notion of falling back on Objective-C is highly regrettable. If I'd wanted to do that I wouldn't have gone through all the work of converting my application to Swift. – matt Mar 31 '17 at 02:33
  • @matt it's only for a very small amount of code, which is very different to the whole project being written in Objective C. Your answer works great, except for when you need to swizzle a class or initializer (in the cases I have, this happens a reasonable amount). While it's similar it doesn't replicate the functionality exactly, so won't work for every case. – Jordan Smith Mar 31 '17 at 02:39
  • @JordanSmith Yes,a dependency ro a different language for a small part of code … However, this is not true. Responder chain, Core Data, … an endless list of *necessary* fall backs to Object-C. Seems to be a design goal of Swift to shout "static safeness" and silently use the advantages of dynamic approaches of Objective-C. – Amin Negm-Awad Apr 03 '17 at 01:43
  • @matt This is just another example why converting code to Swift is regrettable work at all. – Amin Negm-Awad Apr 03 '17 at 01:43
  • @AminNegm-Awad the answer has been updated to use only Swift. I'm not convinced this is significantly better than the previous Objective-C bridge, but it resolves issues I ran into on a project with sharing the Objective-C file across multiple targets. – Jordan Smith Apr 03 '17 at 02:42
  • 1
    @AminNegm-Awad You left out Cocoa Bindings. I just converted a desktop application that makes heavy use of bindings from Objective-C to Swift, and darned near drove myself crazy. :) – matt Apr 03 '17 at 02:45
  • @matt Indeed, and likely the list is longer. – Amin Negm-Awad Apr 03 '17 at 05:20
  • @JordanSmith This code does not use Objective-C? `objc_getClassList()` et al.? – Amin Negm-Awad Apr 03 '17 at 05:42
  • @JordanSmith Then *However, it seems that we can do what we need using only Swift.* seems to be misleading. – Amin Negm-Awad Apr 03 '17 at 07:50
  • Th code *uses* the Objective-C RTE. Therefore "However, it seems that we can do what we need **using only Swift**." is not true. The point is that there is still a dependency to Objective-C, even you try to obfuscate it. Having a dependency to something independent is bad, obfuscating it is worse. However, I'm pretty sure that you have to use C headers. I think, that C is not Swift. – Amin Negm-Awad Apr 03 '17 at 08:03
  • 1
    This is not work when use class categories as only one random category get calls... Does anyone one have solution to this problem? – Moran77 Aug 06 '17 at 14:22
  • How can I adapt this for macOS? There's no `NSApplication#next`… – Sindre Sorhus Feb 28 '18 at 12:06
  • @SindreSorhus For a macOS primary application, kickoff your global initializations in the App Delegate's `init` method — that would be called *before* `applicationDidFinishLaunching`. For an app extension, call the global setup code [inside a closure that initializes a static property](https://stackoverflow.com/a/43068096/199360) and then make a reference to the static property at your top-level objects' initializers (i.e. the extension's view controller). – adib Aug 16 '18 at 08:00
  • @adib I'm aware of that. That's not a macOS adaption of this answer though. It's a different solution. I'm looking for a way to do it without needing anything in the app delegate: https://stackoverflow.com/questions/49029515/is-there-an-alternative-to-initialize-in-macos-now-that-swift-has-deprecated-i – Sindre Sorhus Aug 16 '18 at 18:21
  • I need perform code on first access to class like initialize was working, not in app launch. – Cy-4AH Aug 24 '18 at 08:52
  • can you please update your answer? line `objc_getClassList(autoreleasingTypes, Int32(typeCount))` gives error in swift 5 – Jafar Khoshtabiat Jan 18 '20 at 06:05
7

My approach is essentially the same as adib's. Here's an example from a desktop application that uses Core Data; the goal here is to register our custom transformer before any code mentions it:

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    override init() {
        super.init()
        AppDelegate.doInitialize
    }

    static let doInitialize : Void = {
        // set up transformer
        ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer)
    }()

    // ...
}

The nice thing is that this works for any class, just as initialize did, provided you cover all your bases — that is, you must implement every initializer. Here's an example of a text view that configures its own appearance proxy once before any instances have a chance to appear onscreen; the example is artificial but the encapsulation is extremely nice:

class CustomTextView : UITextView {

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame:frame, textContainer: textContainer)
        CustomTextView.doInitialize
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        CustomTextView.doInitialize
    }

    static let doInitialize : Void = {
        CustomTextView.appearance().backgroundColor = .green
    }()

}

That demonstrates the advantage of this approach much better than the app delegate does. There is only one app delegate instance, so the problem isn't very interesting; but there can be many CustomTextView instances. Nevertheless, the line CustomTextView.appearance().backgroundColor = .green will be executed only once, as the first instance is created, because it is part of the initializer for a static property. That is very similar to the behavior of the class method initialize.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I wonder if the optimizing compiler would be allowed to *not* initialize the static property since `CustomTextView.doInitialize` evaluates to an unused expression – just a thought, I have no idea if this is an issue or not. – Martin R Aug 22 '17 at 15:00
  • @MartinR Well, if that were the case, wouldn't that same worry haunt the standard dispatch-once replacement pattern? – matt Aug 22 '17 at 15:05
  • dispatch_once executes a block exactly once. Here the "once" part is a side-effect of the initialization of the static property. – The Swift migrator converts dispatch_once to `_ = MyClass.__once` (as e.g. here: https://stackoverflow.com/q/39576848/1187415). Could the assignment to `_` be required? – Martin R Aug 22 '17 at 15:22
  • @MartinR All I can tell you is that my value transformer initialization has never in fact failed. That's from a real application that I launch every day. – matt Aug 22 '17 at 15:52
  • OK, thanks. It was just a thought and I might be on a wrong track completely. – Martin R Aug 22 '17 at 17:18
  • @MartinR I would never suggest that. If I launch this application one day and the day-of-the-week fails to appear in the interface, I'll be the first to let you know! – matt Aug 22 '17 at 17:21
  • If you have an app delegate, why complicate things and wrap global initializer inside the app delegate's static method? An app delegate would only be instantiated *once* per application run, hence just setup your value transformers inside `AppDelegate.init`. – adib Aug 16 '18 at 07:55
5

If you want to fix your Method Swizzling in Pure Swift way:

public protocol SwizzlingInjection: class {
    static func inject()
}

class SwizzlingHelper {

    private static let doOnce: Any? = {
        UILabel.inject()
        return nil
    }()

    static func enableInjection() {
        _ = SwizzlingHelper.doOnce
    }
}

extension UIApplication {

    override open var next: UIResponder? {
        // Called before applicationDidFinishLaunching
        SwizzlingHelper.enableInjection()
        return super.next
    }

}

extension UILabel: SwizzlingInjection
{
    public static func inject() {
        // make sure this isn't a subclass
        guard self === UILabel.self else { return }

        // Do your own method_exchangeImplementations(originalMethod, swizzledMethod) here

    }
}

Since the objc_getClassList is Objective-C and it cannot get the superclass (e.g. UILabel) but all the subclasses only, but for UIKit related swizzling we just want to run it once on the superclass. Just run inject() on each target class instead of for-looping your whole project classes.

vk.edward.li
  • 1,899
  • 16
  • 22
  • same issue for me : `objc_getClassList` tells me UILabel does not conform to my SwizzlingInjection protocol. Only (internal) UILabel subclasses seems to be conforming to this protocol... too bad... cannot loop on runtime classes to perform auto swizzling injection on conforming classes, need to manually inject swizzling for each needed class. That's why, to me, this answer should be the accepted one ^^ – polo987 Nov 28 '18 at 09:35
3

A slight addition to @JordanSmith's excellent class which ensures that each awake() is only called once:

protocol SelfAware: class {
    static func awake()
}

@objc class NothingToSeeHere: NSObject {

    private static let doOnce: Any? = {
        _harmlessFunction()
    }()

    static func harmlessFunction() {
        _ = NothingToSeeHere.doOnce
    }

    private static func _harmlessFunction() {
        let typeCount = Int(objc_getClassList(nil, 0))
        let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
        let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
        objc_getClassList(autoreleasingTypes, Int32(typeCount))
        for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
        types.deallocate(capacity: typeCount)
    }
}
Scott Corscadden
  • 2,831
  • 1
  • 25
  • 43
2

You can also use static variables since those are already lazy and refer them in your top-level objects' initializers. This would be useful for app extensions and the like which doesn't have an application delegate.

class Foo {
    static let classInit : () = {
        // do your global initialization here
    }()

    init() {
        // just reference it so that the variable is initialized
        Foo.classInit
    }
}
adib
  • 8,285
  • 6
  • 52
  • 91
  • 3
    If you have to *refer them in your top-level objects' initializers.*, why not directly call `classInit()`? Is there any advantage? This is not self-contained solution at all. – Amin Negm-Awad Apr 03 '17 at 01:47
  • In any case `classInit` should be internal to the class. – adib Apr 03 '17 at 11:37
  • @AminNegm-Awad Directly call `classInit()` the code will be performed every time you create an instance of class `Foo`. That's not we want. – Eric Chai Apr 17 '17 at 15:44
  • Actually private to that class should be better. – Eric Chai Apr 17 '17 at 15:46
  • @EricChai An explicite call to `classInit()` will be executed, when it is called. – Amin Negm-Awad Apr 18 '17 at 10:28
  • Note that `classInit` is **not a method** but a property. In turn it is initialized by a closure which will be executed **only once** the first time the static property is referred. – adib Aug 16 '18 at 07:52
1

If you prefer Pure Swift™! then my solution to this kind of thing is running at _UIApplicationMainPreparations time to kick things off:

@UIApplicationMain
private final class OurAppDelegate: FunctionalApplicationDelegate {
    // OurAppDelegate() constructs these at _UIApplicationMainPreparations time
    private let allHandlers: [ApplicationDelegateHandler] = [
        WindowHandler(),
        FeedbackHandler(),
        ...

Pattern here is I'm avoiding the Massive Application Delegate problem by decomposing UIApplicationDelegate into various protocols that individual handlers can adopt, in case you're wondering. But the important point is that a pure-Swift way to get to work as early as possible is dispatch your +initialize type tasks in the initialization of your @UIApplicationMain class, like the construction of allHandlers here. _UIApplicationMainPreparations time ought to be early enough for pretty much anybody!

Alex Curylo
  • 4,744
  • 1
  • 27
  • 37
0
  1. Mark your class as @objc
  2. Inherit it from NSObject
  3. Add ObjC category to your class
  4. Implement initialize in category

Example

Swift files:

//MyClass.swift
@objc class MyClass : NSObject
{
}

Objc files:

//MyClass+ObjC.h
#import "MyClass-Swift.h"

@interface MyClass (ObjC)

@end

//MyClass+ObjC.m
#import "MyClass+ObjC.h"

@implement MyClass (ObjC)

+ (void)initialize {
    [super initialize];
}

@end
Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
0

Here is a solution that does work on swift 3.1+

@objc func newViewWillAppear(_ animated: Bool) {
    self.newViewWillAppear(animated) //Incase we need to override this method
    let viewControllerName = String(describing: type(of: self)).replacingOccurrences(of: "ViewController", with: "", options: .literal, range: nil)
    print("VIEW APPEAR", viewControllerName)
}

static func swizzleViewWillAppear() {
    //Make sure This isn't a subclass of UIViewController, So that It applies to all UIViewController childs
    if self != UIViewController.self {
        return
    }
    let _: () = {
        let originalSelector = #selector(UIViewController.viewWillAppear(_:))
        let swizzledSelector = #selector(UIViewController.newViewWillAppear(_:))
        let originalMethod = class_getInstanceMethod(self, originalSelector)
        let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
        method_exchangeImplementations(originalMethod!, swizzledMethod!);
    }()
}

Then on AppDelegate:

UIViewController.swizzleViewWillAppear()

Taking from the following post

Tomer
  • 4,382
  • 5
  • 38
  • 48
0

Init static stored property with closure

[static stored property with closure]

One more example to execute something once using

extension MyClass {
    static let shared: MyClass = {
        //create an instance and setup it
        let myClass = MyClass(parameter: "parameter")
        myClass.initialize()//setup

        return myClass
    }()
    //() to execute the closure.

    func initialize() {
        //is called once
    }
}

//using
let myClass = MyClass.shared

yoAlex5
  • 29,217
  • 8
  • 193
  • 205
-4

I think that is a workaround way.

Also we can write initialize() function in objective-c code, then use it by bridge reference

Hope the best way.....

tangkunyin
  • 1,383
  • 1
  • 8
  • 9