Is there an alternative to initialize() in macOS now that Swift has deprecated it?

大城市里の小女人 提交于 2019-11-29 01:20:33

Nope, a Swift alternative to initialize() doesn't exist, mainly because Swift is statically dispatched, so method calls can't be intercepted. And is really no longer needed, as the method was commonly used to initialize static variables from Objective-C files, and in Swift static/global variables are always lazy and can be initialized with the result of an expression (thing not possible in Objective-C).

Even if it would be possible to achieve something similar, I would discourage you to implicitly run stuff without the knowledge of the framework users. I'd recommend to add a configure method somewhere in the library and ask the users of your library to call it. This way they have control over the initialization point. There are only a few things worser than having a framework that I linked against, but no longer (or not yet) using, to start executing code without my consent.

It's just saner to give framework users control over when they want the framework code to start. If indeed your code must run at the very beginning of the application lifecycle, then ask the framework users to make the call to your framework entry point before any other calls. It's also in their interest for your framework to be properly configured.

E.g.:

MyFramework.configure(with: ...)
// or
MyManager.start()

EDIT: Since I wrote this answer, the OP has added "pure Swift" to the question in an edit. However, I am leaving this answer here because it remains the only correct way to do this at the time of this writing. Hopefully, module initialization hooks will be added in Swift 6 or 7 or 8, but as of March 2018, pure Swift is the wrong tool for this use case.

Original answer follows:

Unfortunately, Swift doesn't have any direct equivalent to the old initialize() and load() methods, so this can't be done in pure Swift AFAIK. However, if you're not averse to mixing a small amount of Objective-C into your project, this isn't hard to do. Simply make a Swift class that's fully exposed to Objective-C:

class MyInitThingy: NSObject {
    @objc static func appWillLaunch(_: Notification) {
        print("App Will Launch")
    }
}

Then add this short Objective-C file to the project:

#import <Cocoa/Cocoa.h>

static void __attribute__ ((constructor)) Initer() {
    // Replace "MyFrameworkName" with your framework's module name.

    // You can also just #import <MyFrameworkName/MyFrameworkName-Swift.h>
    // and then access the class directly, but that requires the class to
    // be public, which pollutes the framework's external interface.
    // If we just look up the class and selector via the Objective-C runtime,
    // we can keep everything internal.

    Class class = NSClassFromString(@"MyFrameworkName.MyInitThingy");
    SEL selector = NSSelectorFromString(@"appWillLaunch:");

    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

    [center addObserver:class
               selector:selector
                   name:NSApplicationWillFinishLaunchingNotification
                 object:nil];
}

With these two pieces of code in place, an app that links against your framework should get "App Will Launch" logged to the console sometime before applicationDidFinishLaunching is called.

Alternatively, if you already have a public ObjC-visible class in your module, you can do this without having to use the runtime functions, via a category:

public class SomeClass: NSObject {
    @objc static func appWillLaunch(_: Notification) {
        print("App Will Launch")
    }
}

and:

#import <Cocoa/Cocoa.h>
#import <MyFrameworkName/MyFrameworkName-Swift.h>

@interface SomeClass (InternalSwiftMethods)

+ (void)appWillLaunch:(NSNotification *)notification;

@end

@implementation SomeClass (FrameworkInitialization)

+ (void)load {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

    [center addObserver:self
               selector:@selector(appWillLaunch:)
                   name:NSApplicationWillFinishLaunchingNotification
                 object:nil];
}

@end

As pointed out previously by others it is neither possible (nor good programming) to do what you ask for in a Framework in Swift. Achieving the functionality from the application itself (where this sort of behaviour belongs) is fairly simple though - no need to mess with notifications or selectors. You simply override the init of your NSApplicationDelegate (or UIApplicationDelegate) and set up your class initializer there:

class AppDelegate: NSObject, NSApplicationDelegate {

    override init() {
        super.init()
        YourClass.initializeClass()
    }
}

And the corresponding static function:

class YourClass {
    static func initializeClass() {
        // do stuff
    }
}

This will achieve the same functionality as initialize().

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!