Swift 3.1 обесценивает initialize(). Как я могу достичь того же?
Objective-C объявляет функцию класса initialize()
, которая запускается один раз для каждого класса, прежде чем она будет использоваться. Он часто используется как точка входа для обмена реализациями методов (swizzling), между прочим.
Swift 3.1 обесценивает эту функцию с предупреждением:
Метод 'initialize()' определяет Objective-C метод класса 'initialize', который не гарантированно будет вызван Свифт и будет запрещен в будущих версиях
Как это можно решить, сохраняя при этом те же самые поведенческие функции и функции, которые я реализую в настоящее время, используя точку входа initialize()
?
Ответы
Ответ 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
- Отметьте свой класс как
@objc
- Наследуй его от
NSObject
- Добавьте категорию ObjC в свой класс
- Реализовать
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, а затем использовать ее по ссылке моста
Надеюсь, лучший способ.....