versiones ventajas requerimientos plataforma objective entre diferencia desarrollar con compiler ios objective-c swift

ios - ventajas - Swift 3.1 deprecates initialize(). ¿Cómo puedo lograr lo mismo?



ventajas de objective c (9)

Objective-C declara una función de clase, initialize() , que se ejecuta una vez para cada clase, antes de usarla. A menudo se usa como un punto de entrada para intercambiar implementaciones de métodos (swizzling), entre otras cosas.

Swift 3.1 desprecia esta función con una advertencia:

El método ''initialize ()'' define el método de clase Objective-C ''initialize'', que Swift no puede invocar y que no se permitirá en futuras versiones.

¿Cómo se puede resolver esto, manteniendo el mismo comportamiento y características que implemento actualmente usando el punto de entrada initialize() ?


Solución fácil / simple

Un punto de entrada de aplicación común es la aplicación de un delegado de applicationDidFinishLaunching . Podríamos simplemente agregar una función estática a cada clase que queremos notificar en la inicialización, y llamarla desde aquí.

Esta primera solución es simple y fácil de entender. Para la mayoría de los casos, esto es lo que recomendaría. Aunque la próxima solución proporciona resultados que son más similares a la función initialize() original, también da como resultado tiempos de inicio de la aplicación un poco más largos. Ya no creo que valga la pena el esfuerzo, la degradación del rendimiento o la complejidad del código en la mayoría de los casos. El código simple es un buen código.

Sigue leyendo para otra opción. Es posible que tenga razones para necesitarlo (o tal vez partes de él).

Solución no tan simple

La primera solución no necesariamente escala tan bien. ¿Y qué pasa si está creando un marco de trabajo, donde desea que se ejecute su código sin que nadie necesite llamarlo desde el delegado de la aplicación?

Paso uno

Defina el siguiente código Swift. El propósito es proporcionar un punto de entrada simple para cualquier clase que desee imbuir con un comportamiento similar al de initialize() ; esto ahora se puede hacer simplemente al conformarse con SelfAware . También proporciona una función única para ejecutar este comportamiento para cada clase conforme.

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

Segundo paso

Eso está muy bien, pero aún necesitamos una forma de ejecutar la función que definimos, es decir, NothingToSeeHere.harmlessFunction() , en el inicio de la aplicación. Anteriormente, esta respuesta sugería usar el código Objective-C para hacer esto. Sin embargo, parece que podemos hacer lo que necesitamos usando solo Swift. Para macOS u otras plataformas donde UIApplication no está disponible, se necesitará una variación de lo siguiente.

extension UIApplication { private static let runOnce: Void = { NothingToSeeHere.harmlessFunction() }() override open var next: UIResponder? { // Called before applicationDidFinishLaunching UIApplication.runOnce return super.next } }

Paso tres

Ahora tenemos un punto de entrada al inicio de la aplicación, y una forma de conectarlo desde las clases de su elección. Todo lo que queda por hacer: en lugar de implementar initialize() , conformarse con SelfAware e implementar el método definido, awake() .


  1. Marque su clase como @objc
  2. NSObject de NSObject
  3. Agregue la categoría ObjC a su clase
  4. Implementar initialize en categoría

Ejemplo

Archivos Swift:

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

Archivos 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


Aquí hay una solución que funciona en 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!); }() }

Luego en AppDelegate:

UIViewController.swizzleViewWillAppear()

Tomando de la siguiente publicación


Creo que es una forma alternativa.

También podemos escribir la función initialize() en el código de Objective-C, luego usarlo por referencia de puente

Espero la mejor manera .....


Ligera adición a la excelente clase de @ JordanSmith que asegura que cada awake() solo se llame una vez:

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


Mi enfoque es esencialmente el mismo que el de Adib. Aquí hay un ejemplo de una aplicación de escritorio que usa Core Data; El objetivo aquí es registrar nuestro transformador personalizado antes de que un código lo mencione:

@NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { override init() { super.init() AppDelegate.doInitialize } static let doInitialize : Void = { // set up transformer ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer) }() // ... }

Lo bueno es que esto funciona para cualquier clase, tal como lo hizo initialize , siempre que cubra todas sus bases, es decir, debe implementar cada inicializador. Aquí hay un ejemplo de una vista de texto que configura su propio proxy de apariencia una vez antes de que cualquier instancia tenga la oportunidad de aparecer en pantalla; El ejemplo es artificial pero la encapsulación es extremadamente agradable:

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

Eso demuestra la ventaja de este enfoque mucho mejor que el delegado de la aplicación. Solo hay una instancia de delegado de aplicación, por lo que el problema no es muy interesante; pero puede haber muchas instancias CustomTextView. Sin embargo, la línea CustomTextView.appearance().backgroundColor = .green se ejecutará solo una vez , ya que se crea la primera instancia, porque es parte del inicializador de una propiedad estática. Eso es muy similar al comportamiento del método de clase initialize .


Si desea arreglar su Método Swizzling de manera 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 } }

Dado que objc_getClassList es Objective-C y no puede obtener la superclase (p. Ej., UILabel) sino todas las subclases, pero solo para el swizzling relacionado con UIKit, solo queremos ejecutarlo una vez en la superclase. Simplemente ejecute inject () en cada clase de destino en lugar de for-looping todas sus clases de proyecto.


Si prefieres Pure Swift ™! entonces mi solución a este tipo de cosas se está ejecutando en el momento _UIApplicationMainPreparations para comenzar:

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

El patrón aquí es que estoy evitando el problema del delegado de aplicaciones masivas al descomponer UIApplicationDelegate en varios protocolos que los manejadores individuales pueden adoptar, en caso de que se lo pregunten. Pero el punto importante es que una forma pura y rápida de llegar al trabajo lo antes posible es enviar sus tareas de tipo +initialize en la inicialización de su clase @UIApplicationMain , como la construcción de allHandlers aquí. _UIApplicationMainPreparations tiempo debe ser lo suficientemente temprano para casi cualquier persona!


También puede usar variables estáticas, ya que esas son flojas y referirlas en los inicializadores de sus objetos de nivel superior. Esto sería útil para extensiones de aplicaciones y similares que no tienen un delegado de aplicaciones.

class Foo { static let classInit : () = { // do your global initialization here }() init() { // just reference it so that the variable is initialized Foo.classInit } }