objective-c swift interop objective-c-protocol objective-c-nullability

objective c - ¿Cómo crear métodos de clase que se ajusten a un protocolo compartido entre Swift y Objective-C?



interop objective-c-protocol (1)

He estado aprendiendo Swift últimamente.

Decidí escribir una aplicación híbrida Swift / Objective-C que hiciera tareas intensivas en cómputo usando el mismo algoritmo implementado en ambos idiomas.

El programa calcula una gran variedad de números primos.

Definí un protocolo que se supone que deben cumplir tanto la versión Swift como la versión Objective-C del objeto de cálculo.

Los objetos son singletons, así que creé un método de acceso singleton típico en Objective-C:

+ (NSObject <CalcPrimesProtocol> *) sharedInstance;

Todo el protocolo se ve así:

#import <Foundation/Foundation.h> @class ComputeRecord; typedef void (^updateDisplayBlock)(void); typedef void (^calcPrimesCompletionBlock)(void); @protocol CalcPrimesProtocol <NSObject> - (void) calcPrimesWithComputeRecord: (ComputeRecord *) aComputeRecord withUpdateDisplayBlock: (updateDisplayBlock) theUpdateDisplayBlock andCompletionBlock: (calcPrimesCompletionBlock) theCalcPrimesCompletionBlock; @optional //Without this @optional line, the build fails. + (NSObject <CalcPrimesProtocol> *) sharedInstance; @end

La versión Objective-C de la clase implementa los métodos exactamente como se definieron anteriormente, no se preocupe.

La versión rápida tiene un método:

class func sharedInstance() -> CalcPrimesProtocol

Sin embargo, si convierto ese método en un método requerido del protocolo, me sale un error del compilador "Tipo" CalcPrimesSwift no se ajusta al protocolo ''CalcPrimesProtocol''.

Sin embargo, si marco el método de clase singleton sharedInstance como opcional en el protocolo, funciona, y puedo invocar ese método en mi clase Swift o en mi clase Objective-C.

¿Me perdí algo de sutileza en la definición de mi método de clase Swift? Parece poco probable, dado que puedo invocar el método de clase sharedInstance () en mi clase Swift o mi clase Objective-C.

Puede descargar el proyecto desde Github y verificarlo si lo desea. Se llama SwiftPerformanceBenchmark . (enlazar)


En Objective-C, siempre estábamos pasando punteros, y los punteros siempre podían ser nil . Muchos programadores de Objective-C hicieron uso del hecho de que enviar un mensaje a nil no hizo nada y devolvió 0 / nil / NO . Swift maneja nil completamente diferente. Los objetos existen (nunca nil ), o se desconoce si existen o no (que es donde entran en juego las opciones Swift).

Antes de Xcode 6.3, esto significaba que cualquier código Swift que usara cualquier código Objective-C tendría que tratar todas las referencias de objetos como opciones Swift. Nada sobre las reglas del lenguaje de Objective-C impidió que un puntero de objeto fuera nil .

Lo que esto significó para usar protocolos, clases, etc. de Objective-C de Swift es que fue un desastre masivo. Tuvimos que elegir entre soluciones no perfectas.

Dado el siguiente protocolo Objective-C:

@protocol ObjCProtocol <NSObject> @required + (id<ObjCProtocol>)classMethod; @required - (id<ObjCProtocol>)instanceMethod; @required - (void)methodWithArgs:(NSObject *)args; @end

Podemos aceptar que la definición del método contenga opciones implícitas sin envolver:

class MyClass: NSObject, ObjCProtocol { func methodWithArgs(args: NSObject!) { // do stuff with args } }

Esto hace que el código resultante sea más limpio (nunca tenemos que desenvolverlo dentro del cuerpo), sin embargo, siempre correremos el riesgo de que aparezca el error "nulo encontrado al desenvolver opcional".

O, alternativamente, podemos definir el método como un verdadero opcional:

class MyClass: NSObject, ObjCProtocol { func methodWithArgs(args: NSObject?) { // unwrap do stuff with args } }

Pero esto nos deja con un montón de código de desempaquetado.

Xcode 6.3 corrige este problema y agrega "Anotaciones de anulabilidad" para el código Objective-C.

Las dos palabras clave recién introducidas son nullable y no nonnull . Estos se usan en el mismo lugar donde declara el tipo de retorno o el tipo de parámetro para su código Objective-C.

- (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter; - (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter; - (nullable NSObject *)methodReturningNullableOptionalValue; - (nonnull NSObject *)methodReturningNonNullNonOptionalValue;

Además de estas dos palabras clave de anotación, Xcode 6.3 también introduce un conjunto de macros que hace que sea fácil marcar grandes secciones de código Objective-C como no nonnull (los archivos sin anotaciones se asumen efectivamente como nullable ). Para esto, usamos NS_ASSUME_NONNULL_BEGIN en la parte superior de la sección y NS_ASSUME_NONNULL_END en la parte inferior de la sección que deseamos marcar.

Entonces, por ejemplo, podríamos envolver todo su protocolo dentro de este macro par.

NS_ASSUME_NONNULL_BEGIN @protocol CalcPrimesProtocol <NSObject> - (void) calcPrimesWithComputeRecord: (ComputeRecord *) aComputeRecord withUpdateDisplayBlock: (updateDisplayBlock) theUpdateDisplayBlock andCompletionBlock: (calcPrimesCompletionBlock) theCalcPrimesCompletionBlock; + (id <CalcPrimesProtocol> ) sharedInstance; @end NS_ASSUME_NONNULL_END

Esto tiene el mismo efecto que marcar todos los parámetros de puntero y tipos de retorno como no nonnull ( con algunas excepciones, ya que esta entrada en el blog Swift de Apple toma nota de ).

Pre-Xcode 6.3

Una clase Swift que se ajuste a un protocolo Objective-C debe tratar los tipos de Objective-C en ese protocolo como opcionales.

Al tratar de resolver esto, creé el siguiente protocolo Objective-C:

@protocol ObjCProtocol <NSObject> @required + (id<ObjCProtocol>)classMethod; @required - (id<ObjCProtocol>)instanceMethod; @required - (void)methodWithArgs:(NSObject *)args; @end

Y luego, creó una clase Swift que heredó de NSObject y se declaró conforme a este ObjCProtocol .

Luego procedí a escribir estos nombres de métodos y dejé que Swift los completara automáticamente, y esto es lo que obtuve (puse en los cuerpos de los métodos, el resto si se completa automáticamente):

class ASwiftClass : NSObject, ObjCProtocol { class func classMethod() -> ObjCProtocol! { return nil } func instanceMethod() -> ObjCProtocol! { return nil } func methodWithArgs(args: NSObject!) { // do stuff } }

Ahora, podemos usar opcionales regulares (con el ? ) En lugar de estos opcionales automáticamente desempaquetados si lo deseamos. El compilador está perfectamente feliz con cualquiera de los dos. Sin embargo, el punto es que debemos permitir la posibilidad de nil , porque los protocolos Objective-C no pueden evitar que se pase nil .

Si este protocolo se implementara en Swift, podríamos elegir si el tipo de retorno es opcional o no y Swift nos impediría regresar nil a un método que no definió un tipo de retorno no opcional.