简体   繁体   English

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

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

Objective-C declares a class function, initialize() , that is run once for each class, before it is used. Objective-C 声明了一个类函数, initialize() ,在使用之前为每个类运行一次。 It is often used as an entry point for exchanging method implementations (swizzling), among other things.它通常用作交换方法实现(swizzling)等的入口点。

Swift 3.1 deprecates this function with a warning: Swift 3.1 弃用此函数并发出警告:

Method 'initialize()' defines Objective-C class method 'initialize', which is not guaranteed to be invoked by Swift and will be disallowed in future versions方法 'initialize()' 定义了 Objective-C 类方法 'initialize',它不保证被 Swift 调用,并且在未来的版本中将被禁止

How can this be resolved, while still maintaining the same behaviour and features that I currently implement using the initialize() entry point?如何解决这个问题,同时仍然保持我当前使用initialize()入口点实现的相同行为和功能?

Easy/Simple Solution简单/简单的解决方案

A common app entry point is an application delegate's applicationDidFinishLaunching .一个常见的应用程序入口点是应用程序委托的applicationDidFinishLaunching We could simply add a static function to each class that we want to notify on initialization, and call it from here.我们可以简单地为每个我们想要在初始化时通知的类添加一个静态函数,然后从这里调用它。

This first solution is simple and easy to understand.第一个解决方案简单易懂。 For most cases, this is what I'd recommend.在大多数情况下,这是我推荐的。 Although the next solution provides results that are more similar to the original initialize() function, it also results in slightly longer app start up times.虽然下一个解决方案提供的结果更类似于原始的initialize()函数,但它也会导致应用程序启动时间稍长。 I no longer think it is worth the effort, performance degradation, or code complexity in most cases.在大多数情况下,我不再认为值得付出努力、性能下降或代码复杂性。 Simple code is good code.简单的代码就是好的代码。

Read on for another option.继续阅读另一种选择。 You may have reason to need it (or perhaps parts of it).您可能有理由需要它(或者可能是其中的一部分)。


Not So Simple Solution不是那么简单的解决方案

The first solution doesn't necessarily scale so well.第一个解决方案不一定能很好地扩展。 And what if you are building a framework, where you'd like your code to run without anyone needing to call it from the application delegate?如果你正在构建一个框架,你希望你的代码在没有任何人需要从应用程序委托中调用它的情况下运行呢?

Step One第一步

Define the following Swift code.定义以下 Swift 代码。 The purpose is to provide a simple entry point for any class that you would like to imbue with behavior akin to initialize() - this can now be done simply by conforming to SelfAware .目的是为您希望灌输类似于initialize()的行为的任何类提供一个简单的入口点 - 现在只需符合SelfAware即可完成。 It also provides a single function to run this behavior for every conforming class.它还提供了一个函数来为每个符合要求的类运行此行为。

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)

    }

}

Step Two第二步

That's all good and well, but we still need a way to actually run the function we defined, ie NothingToSeeHere.harmlessFunction() , on application startup.这一切都很好,但是我们仍然需要一种方法来在应用程序启动时实际运行我们定义的函数,即NothingToSeeHere.harmlessFunction() Previously, this answer suggested using the Objective-C code to do this.以前,此答案建议使用 Objective-C 代码来执行此操作。 However, it seems that we can do what we need using only Swift.然而,似乎我们可以只使用 Swift 来做我们需要的事情。 For macOS or other platforms where UIApplication is not available, a variation of the following will be needed.对于 macOS 或 UIApplication 不可用的其他平台,将需要以下变体。

extension UIApplication {

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

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

}

Step Three第三步

We now have an entry point at application startup, and a way to hook into this from classes of your choice.我们现在在应用程序启动时有了一个入口点,并且有一种方法可以从您选择的类中挂钩。 All that is left to do: instead of implementing initialize() , conform to SelfAware and implement the defined method, awake() .剩下要做的就是:与其实现initialize() ,不如遵循SelfAware并实现定义的方法awake()

My approach is essentially the same as adib's.我的方法与 adib 的方法基本相同。 Here's an example from a desktop application that uses Core Data;这是一个使用 Core Data 的桌面应用程序的示例; the goal here is to register our custom transformer before any code mentions it:这里的目标是在任何代码提到它之前注册我们的自定义转换器:

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    override init() {
        super.init()
        AppDelegate.doInitialize
    }

    static let doInitialize : Void = {
        // set up transformer
        ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer)
    }()

    // ...
}

The nice thing is that this works for any class, just as initialize did, provided you cover all your bases — that is, you must implement every initializer.好处是它适用于任何类,就像initialize一样,只要你覆盖了所有的基础——也就是说,你必须实现每个初始化器。 Here's an example of a text view that configures its own appearance proxy once before any instances have a chance to appear onscreen;这是一个文本视图的示例,它在任何实例有机会出现在屏幕上之前配置自己的外观代理一次; the example is artificial but the encapsulation is extremely nice:该示例是人为的,但封装非常好:

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

}

That demonstrates the advantage of this approach much better than the app delegate does.这比应用程序委托更好地证明了这种方法的优势。 There is only one app delegate instance, so the problem isn't very interesting;只有一个应用程序委托实例,所以问题不是很有趣; but there can be many CustomTextView instances.但可以有很多 CustomTextView 实例。 Nevertheless, the line CustomTextView.appearance().backgroundColor = .green will be executed only once , as the first instance is created, because it is part of the initializer for a static property.尽管如此,行CustomTextView.appearance().backgroundColor = .green将只执行一次,因为第一个实例被创建,因为它是静态属性初始化程序的一部分。 That is very similar to the behavior of the class method initialize .这与类方法initialize的行为非常相似。

If you want to fix your Method Swizzling in Pure Swift way:如果你想以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

    }
}

Since the objc_getClassList is Objective-C and it cannot get the superclass (eg UILabel) but all the subclasses only, but for UIKit related swizzling we just want to run it once on the superclass.由于objc_getClassList是 Objective-C 并且它不能获取超类(例如 UILabel),但只能获取所有子类,但是对于与 UIKit 相关的 swizzling,我们只想在超类上运行一次。 Just run inject() on each target class instead of for-looping your whole project classes.只需在每个目标类上运行 inject() 而不是循环整个项目类。

A slight addition to @JordanSmith's excellent class which ensures that each awake() is only called once: @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)
    }
}

You can also use static variables since those are already lazy and refer them in your top-level objects' initializers.您还可以使用静态变量,因为它们已经是惰性的,并在顶级对象的初始化程序中引用它们。 This would be useful for app extensions and the like which doesn't have an application delegate.这对于没有应用程序委托的应用程序扩展等很有用。

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

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

If you prefer Pure Swift™!如果您更喜欢Pure Swift™! then my solution to this kind of thing is running at _UIApplicationMainPreparations time to kick things off:那么我对这种事情的解决方案是在_UIApplicationMainPreparations时间开始运行:

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

Pattern here is I'm avoiding the Massive Application Delegate problem by decomposing UIApplicationDelegate into various protocols that individual handlers can adopt, in case you're wondering.这里的模式是我通过将UIApplicationDelegate分解为各个处理程序可以采用的各种协议来避免大规模应用程序委托问题,以防您想知道。 But the important point is that a pure-Swift way to get to work as early as possible is dispatch your +initialize type tasks in the initialization of your @UIApplicationMain class, like the construction of allHandlers here.但重要的一点是,尽早开始工作的纯 Swift 方法是在@UIApplicationMain类的初始化中调度您的+initialize类型任务,就像这里的allHandlers构造一样。 _UIApplicationMainPreparations time ought to be early enough for pretty much anybody! _UIApplicationMainPreparations时间对于几乎任何人来说都应该足够早!

  1. Mark your class as @objc将您的班级标记为@objc
  2. Inherit it from NSObjectNSObject继承它
  3. Add ObjC category to your class将 ObjC 类别添加到您的课程中
  4. Implement initialize in category在类别中实现initialize

Example例子

Swift files:斯威夫特文件:

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

Objc files:对象文件:

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

Here is a solution that does work on swift 3.1+这是一个适用于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!);
    }()
}

Then on AppDelegate:然后在 AppDelegate 上:

UIViewController.swizzleViewWillAppear()

Taking from the following post 取自以下帖子

Init static stored property with closure用闭包初始化静态存储属性

[static stored property with closure] [带闭包的静态存储属性]

One more example to execute something once using使用执行某事的另一个示例

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

I think that is a workaround way.我认为这是一种解决方法。

Also we can write initialize() function in objective-c code, then use it by bridge reference我们也可以在 Objective-C 代码中编写initialize()函数,然后通过桥引用使用它

Hope the best way.....希望最好的方法......

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

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