现在Swift已经弃用它,是否有替代macOS中的initialize()?

Sin*_*hus 18 macos swizzling swift

Objective-C声明了一个类函数initialize(),它在每个类使用之前运行一次.它经常被用作交换方法实现(混合)等的入口点.它的使用在Swift 3.1中被弃用.

这就是我以前做的事情:

extension NSView {
    public override class func initialize() {
        // This is called on class init and before `applicationDidFinishLaunching`
    }
}
Run Code Online (Sandbox Code Playgroud)

如果没有,我怎么能做同样的事情initialize

我需要它用于框架,所以要求在AppDelegate中调用一些东西是不行的.我之前需要它applicationDidFinishLaunching.

我非常喜欢这个解决方案.这正是我正在寻找的,但它适用于iOS.我需要它用于macOS.有人可以推荐一个macOS版本吗?

具体来说,我需要相当于此,但对于macOS:

extension UIApplication {
    private static let runOnce: Void = {
        // This is called before `applicationDidFinishLaunching`
    }()

    override open var next: UIResponder? {
        UIApplication.runOnce
        return super.next
    }
}
Run Code Online (Sandbox Code Playgroud)

我试过覆盖各种属性但NSApplication没有成功.

解决方案需要纯粹的Swift.没有Objective-C.

Cri*_*tik 6

不,Swift替代品initialize()不存在,主要是因为Swift是静态调度的,所以方法调用不能被截获.并且实际上不再需要,因为该方法通常用于初始化Objective-C文件中的静态变量,并且在Swift中静态/全局变量总是很懒并且可以使用表达式的结果进行初始化(在Objective-中不可能的事情) C).

即使有可能实现类似的东西,我也不鼓励你在没有框架用户知识的情况下隐式运行东西.我建议configure在库中的某处添加一个方法,并要求您的库的用户调用它.这样他们就可以控制初始化点.只有少数事情比我链接的框架更糟糕,但不再(或尚未)使用,未经我的同意开始执行代码.

让框架用户可以控制他们何时希望框架代码启动,这是更合理的.如果您的代码确实必须在应用程序生命周期的最开始运行,那么请求框架用户在任何其他调用之前调用您的框架入口点.正确配置框架也符合他们的利益.

例如:

MyFramework.configure(with: ...)
// or
MyManager.start()
Run Code Online (Sandbox Code Playgroud)


Cha*_*tka 5

编辑:自从我写了这个答案以来,OP在编辑中向问题添加了“纯Swift”。但是,我在这里留下这个答案,因为在撰写本文时,它仍然是唯一正确的方法。希望将在Swift 6或7或8中添加模块初始化挂钩,但自2018年3月起,纯Swift 该用例中是错误的工具

原始答案如下:

不幸的是,Swift与旧方法initialize()load()方法没有任何直接等效,因此无法在纯Swift AFAIK中完成。但是,如果您不反对将少量的Objective-C混入您的项目中,这并非难事。只需创建一个完全暴露给Objective-C的Swift类:

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

然后将此简短的Objective-C文件添加到项目中:

#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];
}
Run Code Online (Sandbox Code Playgroud)

有了这两段代码,与框架链接的应用程序应在applicationDidFinishLaunching调用之前的某个时间将“ App Will Launch”登录到控制台。

另外,如果您的模块中已经有一个公共的ObjC可见类,则可以通过以下类别来执行此操作,而不必使用运行时函数:

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

和:

#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
Run Code Online (Sandbox Code Playgroud)

  • @SindreSorhus链接解决方案中的hack是不可取的,原因有两个:1)如果两个单独的框架试图覆盖NSApplication上的nextResponder,它们中的一个将被踩踏,并且不确定是哪个,以及2)如果在将来的AppKit版本中,“ NSApplication”获得了对“ nextResponder”的自己的覆盖,这将使情况更加混乱,并导致谁知道多少未定义行为。抱歉,没有针对该问题的纯Swift解决方案,该解决方案不会带来积极的危害。 (2认同)