Objective-C声明了一个类函数initialize(),它在每个类使用之前运行一次。它经常被用作交换方法实现(混合)等的入口点。它的使用在Swift 3.1中被弃用。
这就是我以前做的事情:
extension NSView {
public override class func initialize() {
// This is called on class init and before `applicationDidFinishLaunching`
}
}
如果没有initialize
,我怎样才能实现同样的目标?
我需要它用于框架,因此要求在AppDelegate中调用某些东西是不行的。我需要在applicationDidFinishLaunching
之前调用它。
我非常喜欢this解决方案。这正是我正在寻找的东西,但它适用于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
}
}
我尝试覆盖NSApplication
上的各种属性但没有成功。
解决方案需要纯粹的Swift。没有Objective-C。
答案 0 :(得分:6)
Nope,一个替换initialize()
的Swift不存在,主要是因为Swift是静态调度的,所以方法调用不能被截获。并且实际上不再需要,因为该方法通常用于初始化Objective-C文件中的静态变量,并且在Swift中静态/全局变量总是很懒并且可以使用表达式的结果进行初始化(在Objective-中不可能的事情) C)。
即使有可能实现类似的东西,我也不鼓励你在没有框架用户知识的情况下隐式运行东西。我建议在库中的某处添加configure
方法,并要求您的库的用户调用它。这样他们就可以控制初始化点。只有少数事情比我链接的框架更糟糕,但不再(或者还没有)使用,未经我的同意开始执行代码。
让框架用户可以控制他们何时希望框架代码启动。如果您的代码确实必须在应用程序生命周期的最开始运行,那么请求框架用户在任何其他调用之前调用您的框架入口点。正确配置框架也符合他们的利益。
E.g:
MyFramework.configure(with: ...)
// or
MyManager.start()
答案 1 :(得分:5)
编辑:由于我写了这个答案,OP在编辑中添加了“纯粹的Swift”问题。但是,我在这里留下这个答案,因为在撰写本文时,它仍然是唯一正确的方法。希望模块初始化挂钩将在Swift 6或7或8中添加,但是截至2018年3月,纯Swift对于这个用例是the wrong tool。
原始答案如下:
不幸的是,Swift与旧的initialize()
和load()
方法没有任何直接对应关系,因此无法在纯Swift AFAIK中完成。但是,如果您不反对将少量Objective-C混合到您的项目中,那么这并不难。简单地创建一个完全暴露于Objective-C的Swift类:
class MyInitThingy: NSObject {
@objc static func appWillLaunch(_: Notification) {
print("App Will Launch")
}
}
然后将这个简短的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];
}
有了这两段代码,一个链接到你的框架的应用程序应该在调用applicationDidFinishLaunching
之前的某个时间将“App Will Launch”记录到控制台。
或者,如果您的模块中已经有一个公共ObjC可见类,则可以通过以下类别执行此操作而无需使用运行时函数:
public class SomeClass: NSObject {
@objc static func appWillLaunch(_: Notification) {
print("App Will Launch")
}
}
和
#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
答案 2 :(得分:2)
正如其他人先前所指出的那样,在Swift框架中执行你所要求的工作既不可能(也不是很好的编程)。从应用程序本身(这种行为所属)实现功能非常简单 - 无需混淆通知或选择器。您只需覆盖init
(或NSApplicationDelegate
)的UIApplicationDelegate
并在那里设置类初始值设定项:
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
super.init()
YourClass.initializeClass()
}
}
和相应的静态函数:
class YourClass {
static func initializeClass() {
// do stuff
}
}
这将实现与initialize()
相同的功能。