簡體   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