Swift 3.1弃用了initialize().我怎样才能实现同样的目标?

Jor*_*ith 41 objective-c ios swift

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

Swift 3.1弃用了这个函数并带有警告:

方法'initialize()'定义了Objective-C类方法'initialize',它不能保证被Swift调用,并且在将来的版本中将被禁止

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

Jor*_*ith 31

简单/简单的解决方案

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

第一个解决方案简单易懂.对于大多数情况,这是我建议的.虽然下一个解决方案提供的结果与原始initialize()功能更相似,但它也会导致应用启动时间略长.在大多数情况下,我不再认为值得努力,性能下降或代码复杂.简单的代码是很好的代码.

请继续阅读另一个选项.您可能有理由需要它(或者可能是它的一部分).


不那么简单的解决方案

第一种解决方案不一定能很好地扩展.如果您正在构建一个框架,您希望在没有任何人需要从应用程序委托中调用代码的情况下运行代码,该怎么办?

第一步

定义以下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)

    }

}
Run Code Online (Sandbox Code Playgroud)

第二步

这一切都很好,但是我们仍然需要一种实际运行我们定义的函数的方法,即NothingToSeeHere.harmlessFunction()在应用程序启动时.以前,这个答案建议使用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
    }

}
Run Code Online (Sandbox Code Playgroud)

第三步

我们现在有一个应用程序启动的入口点,以及从您选择的类中挂钩的方法.剩下要做的就是:而不是实现initialize(),遵守SelfAware和实现定义的方法,awake().

  • 嗯,它是自包含的`+ initialize`的本质.这就是它的用途.任何其他方法都不是一个解决方案,而不是一个不优雅的解决方案.我不认为这是一个港口.如果它是Objective-C的端口,它将在没有Objective-C的情况下工作.但事实并非如此.不要误会我的意思,我赞成你的回答.但是,它不是一个优雅的解决方案,而是一个黑客,因为在Swift中无法做到这一点. (6认同)
  • Swift中的*elegant*解决方案是使用Objective-C的机制吗? (2认同)
  • @AminNegm-Awad 我建议优雅来自对外部用户的不变行为。对于依赖于 `initialize()` 魔法的框架或类似的东西,这允许行为保持不变。您对我之前的评论是对的,这样做不符合软件“端口”的定义。也许“桥”会是一个更好的词。 (2认同)

mat*_*att 7

我的方法与adib基本相同.以下是使用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)
    }()

    // ...
}
Run Code Online (Sandbox Code Playgroud)

好的一点是,这适用于任何类,只要initialize你覆盖所有的基础 - 也就是说,你必须实现每个初始化程序.这是一个文本视图的示例,它在任何实例有机会出现在屏幕上之前配置一次自己的外观代理; 这个例子是人为的,但封装非常好:

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

}
Run Code Online (Sandbox Code Playgroud)

这表明这种方法的优势比app委托更好.只有一个app delegate实例,所以问题不是很有趣; 但是可以有很多CustomTextView实例.然而,在创建第一个实例时,该行CustomTextView.appearance().backgroundColor = .green只会执行一次,因为它是静态属性的初始化程序的一部分.这与类方法的行为非常相似.initialize


vk.*_*.li 5

如果你想以Pure 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

    }
}
Run Code Online (Sandbox Code Playgroud)

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