Подтвердить что ты не робот

Swift 3.1 обесценивает initialize(). Как я могу достичь того же?

Objective-C объявляет функцию класса initialize(), которая запускается один раз для каждого класса, прежде чем она будет использоваться. Он часто используется как точка входа для обмена реализациями методов (swizzling), между прочим.

Swift 3.1 обесценивает эту функцию с предупреждением:

Метод 'initialize()' определяет Objective-C метод класса 'initialize', который не гарантированно будет вызван Свифт и будет запрещен в будущих версиях

Как это можно решить, сохраняя при этом те же самые поведенческие функции и функции, которые я реализую в настоящее время, используя точку входа initialize()?

4b9b3361

Ответ 1

Простое/Простое Решение

Распространенной точкой входа в приложение является делегат 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().

Ответ 2

Мой подход по существу такой же, как у 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.

Ответ 3

Если вы хотите исправить свой метод Swizzling в 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, связанных с swizzling, мы просто хотим запустить его один раз на суперклассе. Просто запустите inject() для каждого целевого класса, а не за цикл для всех ваших классов проекта.

Ответ 4

Вы также можете использовать статические переменные, поскольку те уже ленивы и ссылаются на них в инициализаторах объектов верхнего уровня. Это было бы полезно для расширений приложений и тому подобного, у которых нет делегата приложения.

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

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

Ответ 5

Незначительное дополнение к превосходному классу @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)
    }
}

Ответ 6

Если вы предпочитаете Pure Swift ™! то мое решение такого рода работает в _UIApplicationMainPreparations время, чтобы отбросить вещи:

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

Образец здесь я избегаю проблемы делегата Massive Application, разлагая UIApplicationDelegate на различные протоколы, которые могут принять отдельные обработчики, если вам интересно. Но важным моментом является то, что метод pure-Swift для работы как можно раньше отправляет ваши задачи типа +initialize в инициализацию вашего класса @UIApplicationMain, например, конструкцию allHandlers здесь. _UIApplicationMainPreparations время должно быть достаточно ранним для почти любого!

Ответ 7

  1. Отметьте свой класс как @objc
  2. Наследуй его от NSObject
  3. Добавьте категорию ObjC в свой класс
  4. Реализовать initialize в категории

пример

Swift файлы:

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

Ответ 8

Вот решение, которое работает на 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()

Из следующего поста

Ответ 9

Я думаю, что это обходной путь.

Также мы можем написать initialize() функцию в коде objective-c, а затем использовать ее по ссылке моста

Надеюсь, лучший способ.....