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

时间:2017-03-16 03:03:56

标签: ios objective-c swift

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

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

  

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

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

10 个答案:

答案 0 :(得分:30)

简单/简单的解决方案

常见的应用入口点是应用代表的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来完成我们需要的工作。对于没有UIApplication的macOS或其他平台,将需要以下变体。

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

答案 1 :(得分: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)
    }()

    // ...
}

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

}

这证明了这种方法的优势比app委托更好。只有一个app delegate实例,所以问题不是很有趣;但是可以有很多CustomTextView实例。然而,当{em>第一个实例被创建时,行CustomTextView.appearance().backgroundColor = .green将仅执行一次,因为它是静态属性的初始值设定项的一部分。这与类方法initialize的行为非常相似。

答案 2 :(得分:5)

如果您想以 Pure Swift 的方式修复方法调整:

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(),而不是循环整个项目类。

答案 3 :(得分:2)

您也可以使用自those are already lazy以来的静态变量,并在顶级对象的初始值设定项中引用它们。这对于没有应用程序委托的应用程序扩展等非常有用。

str_to_date('01-JAN-00 12.00.13.130000000','%d-%b-%y %h.%i.%s.%f')

答案 4 :(得分:2)

轻微添加@ 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)
    }
}

答案 5 :(得分:1)

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

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

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

答案 6 :(得分:0)

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

示例

快速文件:

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

Objc文件:

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

答案 7 :(得分:0)

这是在 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()

Taking from the following post

答案 8 :(得分:0)

另一个使用 static const config = { a: 1, b: "yo", default: { something: true } } type Config = typeof config; type ConfigKey = keyof Config; const getConfig = <K extends string>(key: K): K extends ConfigKey ? Config[K] : Config['default'] => { return (config as any)[key] || config.default } const ofTypeNumber = getConfig('a') const ofTypeString = getConfig('b') const ofTypeDefaultConfig = getConfig('c') [About]

执行一次操作的示例
closure stored property

答案 9 :(得分:-4)

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

我们也可以在objective-c代码中编写initialize()函数,然后通过桥接引用

使用它

希望最好的方式.....