Swift 3.1弃用initialize()。 我怎样才能做到这一点?

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

Swift 3.1不赞成使用这个函数,并带有一个警告:

方法'initialize()'定义了Objective-C类的方法'initialize',它不保证被Swift调用,在未来的版本中将不被允许

如何解决这个问题,同时仍然保持我目前使用initialize()入口点实现的相同行为和function?

易/简单的解决scheme

一个常见的应用程序入口点是应用程序委托的applicationDidFinishLaunching 。 我们可以简单地为每个想要在初始化时通知的类添加一个静态函数,并从这里调用它。

编辑:这第一个解决scheme简单易懂。 对于大多数情况下,这是我build议。 尽pipe下一个解决scheme提供的结果与原始的initialize()函数更相似,但也会导致应用程序启动时间稍长。 在大多数情况下,我不再认为这是值得的努力,性能下降或代码复杂性。 简单的代码是好的代码🙂。

继续阅读另一个选项。 你可能有理由需要它(或者也许是它的一部分)。


不那么简单的解决scheme

第一个解决scheme不一定能很好地扩展。 那么如果你正在构build一个框架,你希望你的代码在没有人需要从应用程序委托中调用的情况下运行?

步骤1

定义下面的Swift代码。 目的是为任何想要为类似initialize()行为提供一个简单的入口点 – 现在可以简单地通过符合SelfAware来完成。 它还提供了一个函数来为每个合格类运行这个行为。

 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) } } 

第二步

这一切都很好,但是我们仍然需要一种方法来在应用程序启动时真正运行我们定义的函数,即NothingToSeeHere.harmlessFunction() 。 以前,这个答案build议使用Objective-C代码来做到这一点。 不过,我们似乎只能用Swift来做我们需要的东西。 对于没有UIApplication的macOS或其他平台,将需要以下变体。

 extension UIApplication { private static let runOnce: Void = { NothingToSeeHere.harmlessFunction() }() override open var next: UIResponder? { // Called before applicationDidFinishLaunching UIApplication.runOnce return super.next } } 

第三步

我们现在在应用程序启动时有一个入口点,并且可以从您select的类中钩入这个入口点。 剩下的事情就是:不是实现initialize() ,而是遵循SelfAware并实现定义的方法awake()

我的做法基本上和阿迪布一样。 以下是使用Core Data的桌面应用程序的示例; 这里的目标是在任何代码提到它之前注册我们的定制转换器:

 @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { override init() { super.init() AppDelegate.doInitialize } static let doInitialize : Void = { // set up transformer ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer) }() // ... } 

好的一点是,这适用于任何类,就像initialize一样,只要你覆盖了所有的基础 – 也就是说,你必须实现每个初始化。 下面是一个文本视图的例子,在任何实例有机会出现在屏幕上之前,它将自己的外观代理configuration一次。 这个例子是人造的,但封装非常好:

 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 }() } 

这表明这种方法比应用程序代表的优点要好得多。 只有一个应用程序委托实例,所以问题不是很有趣; 但是可以有很多CustomTextView实例。 尽pipe如此,在创build第一个实例时, CustomTextView.appearance().backgroundColor = .green只会执行一次 ,因为它是静态属性初始值设定项的一部分。 这与类方法initialize的行为非常相似。

您也可以使用静态variables,因为这些variables已经是懒惰的 ,并将它们引用到顶级对象的初始化程序中。 这对于没有应用程序委托的应用程序扩展等是有用的。

 class Foo { static let classInit : () = { // do your global initialization here }() init() { // just reference it so that the variable is initialized Foo.classInit } } 

如果你喜欢Pure Swift™! 那么我对这种事情的解决scheme就是在_UIApplicationMainPreparations时间运行时间:

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

这里的模式是我通过将UIApplicationDelegate分解成单独的处理程序可以采用的各种协议来避免大规模应用程序委托问题,以防万一您想知道。 但重要的一点是,一个纯Swift的方式尽可能早地开始工作,就是在你的@UIApplicationMain类的初始化中派发你的+initializetypes任务,就像在这里构造allHandlers 。 对于任何人来说,时间应该足够早。

稍微join@ JordanSmith的优秀类,确保每个awake()只被调用一次:

 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) } } 

如果你想以纯Swift的方式修复你的方法Swizzling:

 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 } } 

由于objc_getClassList是Objective-C,它不能获得超类(例如UILabel),但只能获得所有的子类,但是对于UIKit相关的调整,我们只想在超类上运行一次。 只需在每个目标类上运行inject(),而不是循环整个项目类。

我认为这是一个解决方法。

我们还可以在Objective-C代码中编写initialize()函数,然后通过桥引用来使用它

希望最好的办法…..