繁体   English   中英

Swift 3.1 弃用了 initialize()。 我怎样才能达到同样的目的?

[英]Swift 3.1 deprecates initialize(). How can I achieve the same thing?

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

Swift 3.1 弃用此函数并发出警告:

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

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

简单/简单的解决方案

一个常见的应用程序入口点是应用程序委托的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)

    }

}

第二步

这一切都很好,但是我们仍然需要一种方法来在应用程序启动时实际运行我们定义的函数,即NothingToSeeHere.harmlessFunction() 以前,此答案建议使用 Objective-C 代码来执行此操作。 然而,似乎我们可以只使用 Swift 来做我们需要的事情。 对于 macOS 或 UIApplication 不可用的其他平台,将需要以下变体。

extension UIApplication {

    private static let runOnce: Void = {
        NothingToSeeHere.harmlessFunction()
    }()

    override open var next: UIResponder? {
        // Called before applicationDidFinishLaunching
        UIApplication.runOnce
        return super.next
    }

}

第三步

我们现在在应用程序启动时有了一个入口点,并且有一种方法可以从您选择的类中挂钩。 剩下要做的就是:与其实现initialize() ,不如遵循SelfAware并实现定义的方法awake()

我的方法与 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)
    }()

    // ...
}

好处是它适用于任何类,就像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
    }()

}

这比应用程序委托更好地证明了这种方法的优势。 只有一个应用程序委托实例,所以问题不是很有趣; 但可以有很多 CustomTextView 实例。 尽管如此,行CustomTextView.appearance().backgroundColor = .green将只执行一次,因为第一个实例被创建,因为它是静态属性初始化程序的一部分。 这与类方法initialize的行为非常相似。

如果你想以Pure Swift方式修复 Method 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 相关的 swizzling,我们只想在超类上运行一次。 只需在每个目标类上运行 inject() 而不是循环整个项目类。

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

您还可以使用静态变量,因为它们已经是惰性的,并在顶级对象的初始化程序中引用它们。 这对于没有应用程序委托的应用程序扩展等很有用。

class Foo {
    static let classInit : () = {
        // do your global initialization here
    }()

    init() {
        // just reference it so that the variable is initialized
        Foo.classInit
    }
}

如果您更喜欢Pure Swift™! 那么我对这种事情的解决方案是在_UIApplicationMainPreparations时间开始运行:

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

这里的模式是我通过将UIApplicationDelegate分解为各个处理程序可以采用的各种协议来避免大规模应用程序委托问题,以防您想知道。 但重要的一点是,尽早开始工作的纯 Swift 方法是在@UIApplicationMain类的初始化中调度您的+initialize类型任务,就像这里的allHandlers构造一样。 _UIApplicationMainPreparations时间对于几乎任何人来说都应该足够早!

  1. 将您的班级标记为@objc
  2. NSObject继承它
  3. 将 ObjC 类别添加到您的课程中
  4. 在类别中实现initialize

例子

斯威夫特文件:

//MyClass.swift
@objc class MyClass : NSObject
{
}

对象文件:

//MyClass+ObjC.h
#import "MyClass-Swift.h"

@interface MyClass (ObjC)

@end

//MyClass+ObjC.m
#import "MyClass+ObjC.h"

@implement MyClass (ObjC)

+ (void)initialize {
    [super initialize];
}

@end

这是一个适用于swift 3.1+的解决方案

@objc func newViewWillAppear(_ animated: Bool) {
    self.newViewWillAppear(animated) //Incase we need to override this method
    let viewControllerName = String(describing: type(of: self)).replacingOccurrences(of: "ViewController", with: "", options: .literal, range: nil)
    print("VIEW APPEAR", viewControllerName)
}

static func swizzleViewWillAppear() {
    //Make sure This isn't a subclass of UIViewController, So that It applies to all UIViewController childs
    if self != UIViewController.self {
        return
    }
    let _: () = {
        let originalSelector = #selector(UIViewController.viewWillAppear(_:))
        let swizzledSelector = #selector(UIViewController.newViewWillAppear(_:))
        let originalMethod = class_getInstanceMethod(self, originalSelector)
        let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
        method_exchangeImplementations(originalMethod!, swizzledMethod!);
    }()
}

然后在 AppDelegate 上:

UIViewController.swizzleViewWillAppear()

取自以下帖子

用闭包初始化静态存储属性

[带闭包的静态存储属性]

使用执行某事的另一个示例

extension MyClass {
    static let shared: MyClass = {
        //create an instance and setup it
        let myClass = MyClass(parameter: "parameter")
        myClass.initialize()//setup

        return myClass
    }()
    //() to execute the closure.

    func initialize() {
        //is called once
    }
}

//using
let myClass = MyClass.shared

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

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

希望最好的方法......

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM