21

In Objective-C, NSObject had a class method called load that gets called when the class is loaded for the first time. What is the equivalent in Swift?

@implementation MyClass

+ (void)load
{
   [self registerClass];
}

@end
Cœur
  • 37,241
  • 25
  • 195
  • 267
aryaxt
  • 76,198
  • 92
  • 293
  • 442

9 Answers9

24

Prior to Swift 1.2:

override class func load() {
   NSLog("load");
}

EDIT:

As of Swift 1.2 you can no longer override the load method. Look into the method initialize instead, it behaves different than load though, it get's called the first time the class is being referenced somewhere rather than on application initial load

pkamb
  • 33,281
  • 23
  • 160
  • 191
aryaxt
  • 76,198
  • 92
  • 293
  • 442
12

Support for overriding load was removed in Swift 1.2

Eric
  • 139
  • 1
  • 4
5

Update: Starting from Swift 5 class extensions and categories on Swift classes are not allowed to have +load methods, +initialize doesn’t seem to be prohibited, though.

While direct equivalent is not available with Swift, this can be achieved relatively elegantly with Objective-C and categories. Foo should still inherit from NSObject and have a class / static non-private swiftyLoad/Initialize methods, which get invoked from Objective-C in Loader.m, which you include in compile sources alongside Foo.swift:

# Loader.m

#import <Foundation/Foundation.h>
#import <MyProject/MyProject-Swift.h>

// This was a more concise solution in Swift 4.2 and earlier. If you try
// this with Swift 5 you'd get "Swift class extensions and categories
// on Swift classes are not allowed to have +load methods" runtime error.

// @implementation Foo (private)
//     + (void)load { [self swiftyLoad]; }
//     + (void)initialize { [self swiftyInitialize]; }
// @end

// This is what you'd want to use with Swift 5 onwards, basically 
// just a pure Objective-C defined class.

@interface Loader : NSObject
@end

@implementation Loader : NSObject
    + (void)load { [Foo swiftyLoad]; }
    + (void)initialize { [Foo swiftyInitialize]; }
@end


# Foo.swift

import Foundation
public class Foo: NSObject {
    @objc public static func swiftyLoad() {
        Swift.print("Rock 'n' roll!")
    }
    @objc public static func swiftyInitialize() {
        Swift.print("Hello initialize!")
    }
}

The best part there's no need to mess with bridging headers or anything else, it just works. There are couple of gotchas, especially when using this approach in static libraries, check out the complete post on Medium for details! ✌️

Ian Bytchek
  • 8,804
  • 6
  • 46
  • 72
1

in Swift 2.0, Please use this method

public override class func initialize()
pkamb
  • 33,281
  • 23
  • 160
  • 191
Alix
  • 131
  • 6
  • 2
    @VladimirKofman It will be called the first time your class is used, which is not necessarily at load time. The class must be `@objc` however. – devios1 Mar 03 '16 at 21:25
1

What is the equivalent in Swift?

There is none.

Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself does not have an initializer that can assign a value to a stored type property at initialization time.

Source: "The Swift Programming Language", by Apple

Types simply don't have an initializer in Swift. As several answers here suggest, you may be able to work around that by using the Objective-C bridge and having your Swift class inherit from NSObject or by using a Objective-C loader bootstrap code but if you have a 100% Swift project, you basically only have two options:

  1. Manually initialize your classes somewhere within the code:
class MyCoolClass {

    static func initClass ( ) {
        // Do your init stuff here...
    }
}


// Somewhere in a method/function that is guaranteed to be called
// on app startup or at some other relevant event:
MyCoolClass.initClass()
  1. Use a run once pattern in all methods that require an initialized class:
class MyCoolClass {

    private static var classInitialized: Bool = {
         // Do your init stuff here...
         // This code will for sure only run once!
         return true
    }()

    private static func ensureClassIsInitialized ( ) { 
        _ = self.classInitialized 
    }


    func whateverA ( ... ) {
        MyCoolClass.ensureClassIsInitialized()
        // Do something...
    }

    func whateverB ( ... ) {
        MyCoolClass.ensureClassIsInitialized()
        // Do something...
    }


    func whateverC ( ... ) {
        MyCoolClass.ensureClassIsInitialized()
        // Do something...
    }

}

This follows a clean pattern that no code in Swift runs "automagically", code only runs if other code instructs it to run, which provides a clear and trackable code flow.

There might be a better solution for special cases yet you have not provided any information as for why you need that feature to begin with.

Mecki
  • 125,244
  • 33
  • 244
  • 253
1

The abilitity to use this method was removed in this PR.

It provides some rationale:

Swift's language model doesn't guarantee that type metadata will ever really be used, which makes overriding initialize() error-prone and not really any better than manually invoking an initialization function. Warn about this for Swift 3 compatibility and reject attempts to override +initialize in Swift 4.

Alexander
  • 59,041
  • 12
  • 98
  • 151
1

Swift 5

Method 'load()' defines Objective-C class method 'load', which is not permitted by Swift

You can not override load() method now but there is a legal way to call your custom swift_load() method of your swift classes on app startup. It is needed to make ObjC class which redirect its own load() method to your swift classes.

Just add next SwiftLoader.m file to your project:

// SwiftLoader.m

@interface SwiftLoader : NSObject

@end

@implementation SwiftLoader

+ (void)load {
    SEL selector = @selector(swift_load);
    
    int numClasses = objc_getClassList(NULL, 0);
    Class* classes = (Class *)malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);
    
    for (int i = 0; i < numClasses; i++) {
        Class class = classes[i];
        Method method = class_getClassMethod(class, selector);
        if (method != NULL) {
            IMP imp = method_getImplementation(method);
            ((id (*)(Class, SEL))imp)(class, selector);
        }
    }
    free(classes);
}

Now you can add swift_load class method to any of your swift classes to make setup tasks:

class MyClass {
    @objc
    class func swift_load() {
        print("load")
    }
}
iUrii
  • 11,742
  • 1
  • 33
  • 48
0

For Swift 2 or 3 (i.e. post-Swift 1.2), you can use:

class MySwiftClass: NSObject {
    internal override class func initialize() {
        DoStuff()
        super.initialize()
    }
}

But, as you can see, your class needs to inherit (directly or indirectly) form NSObject. This is required because the initialize() is called by the ObjC runtime.

And the initialize() method will only be called when MySwiftClass is referenced. So it will not be as magic as load().

But it will also be safer. For example: including a framework (let's say, by just adding it to your Podfile) won't allow the framework to mysteriously start to behave as soon as your app launches, without the need to add a single line of code to your project (at least… I hope! ).

MonsieurDart
  • 6,005
  • 2
  • 43
  • 45
0

I get conclusion for swift 1.2~5 doable ways:

Doable      |swift-load |swift-initialze|bridgeToOC-load|bridgeToOC-initialze|
---         |----       |---            |---            |---                 |
swift1.2~4.2|X          |O              |O              |O                   |
swift4.2~5.0|X          |X              |O              |O                   |
swift5.0~?  |X          |X              |X              |O                   |

and how to make it ? check here
https://medium.com/post-mortem/using-nsobjects-load-and-initialize-from-swift-f6f2c6d9aad0


but I think here is another way to make it swifter. use runtime and protol without load/initialze.

http://jordansmith.io/handling-the-deprecation-of-initialize/

leonardosccd
  • 1,503
  • 11
  • 12