¿Cómo manejar los protocolos Objective-C que contienen propiedades?

131

He visto que el uso de los protocolos Objective-C se usa de una manera como la siguiente:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

He visto este formato usado en lugar de escribir una superclase concreta que las subclases extiendan. La pregunta es, si cumple con este protocolo, ¿necesita sintetizar las propiedades usted mismo? Si está extendiendo una superclase, la respuesta es obviamente no, no es necesario. Pero, ¿cómo se manejan las propiedades que un protocolo requiere para cumplir?

Según tengo entendido, aún necesita declarar las variables de instancia en el archivo de encabezado de un objeto que se ajusta a un protocolo que requiere estas propiedades. En ese caso, ¿podemos suponer que son solo un principio rector? CLARAMENTE lo mismo no es el caso para un método requerido. El compilador le dará una palmada en la muñeca por excluir un método requerido que enumera un protocolo. Sin embargo, ¿cuál es la historia detrás de las propiedades?

Aquí hay un ejemplo que genera un error de compilación (Nota: he recortado el código que no refleja el problema en cuestión):

MyProtocol.h

@protocol MyProtocol <NSObject>

@required
@property (nonatomic, retain) id anObject;

@optional

TestProtocolsViewController.h

- (void)iDoCoolStuff;

@end

#import <MyProtocol.h>

@interface TestProtocolsViewController : UIViewController <MyProtocol> {

}

@end

TestProtocolsViewController.m

#import "TestProtocolsViewController.h"

@implementation TestProtocolsViewController
@synthesize anObject; // anObject doesn't exist, even though we conform to MyProtocol.

- (void)dealloc {
    [anObject release]; //anObject doesn't exist, even though we conform to MyProtocol.
    [super dealloc];
}

@end     
Coocoo4Cocoa
fuente

Respuestas:

135

El protocolo solo le dice a todos los que conocen su clase a través del protocolo, que la propiedad anObjectestará allí. Los protocolos no son reales, no tienen variables o métodos en sí mismos; solo describen un conjunto específico de atributos que es cierto acerca de su clase para que los objetos que tienen referencias a ellos puedan usarlos de maneras específicas.

Eso significa que en su clase que se ajusta a su protocolo, debe hacer todo lo posible para asegurarse de que un objeto funcione.

@propertyy @synthesizeson en esencia dos mecanismos que generan código para usted. @propertysolo dice que habrá un método getter (y / o setter) para ese nombre de propiedad. @propertySolo estos días es suficiente para tener también métodos y una variable de almacenamiento creada por el sistema (solía tener que agregar @sythesize). Pero debe tener algo para acceder y almacenar la variable.

Kendall Helmstetter Gelner
fuente
80
Para las propiedades definidas en un protocolo, aún necesita un "@synthesize" incluso en el tiempo de ejecución moderno, o necesita duplicar el "@property" en la definición de su interfaz para obtener la síntesis automática.
Jeffrey Harris
@JeffreyHarris ¿Qué pasa con lo mismo en Swift?
Karan Alangat
@KaranAlangat: no hay tal cosa como \ @synthesize en Swift, pero al igual que ObjC, debe declarar la propiedad en una clase que afirma cumplir con el protocolo. En Swift, puede crear una categoría que defina una implementación predeterminada de una función, pero, por lo que he podido decir, no puede tener una propiedad predeterminada para un protocolo.
Kendall Helmstetter Gelner
31

Aquí hay un ejemplo mío que funciona perfectamente, la definición del protocolo en primer lugar:

@class ExampleClass;

@protocol ExampleProtocol

@required

// Properties
@property (nonatomic, retain) ExampleClass *item;

@end

A continuación se muestra un ejemplo funcional de una clase que admite este protocolo:

#import <UIKit/UIKit.h>
#import "Protocols.h"

@class ExampleClass;

@interface MyObject : NSObject <ExampleProtocol> {

    // Property backing store
    ExampleClass        *item;

}


@implementation MyObject

// Synthesize properties
@synthesize item;

@end
reddersky
fuente
14

todo lo que tienes que hacer es soltar un

@synthesize title;

en su implementación y ya debería estar listo. funciona de la misma manera que simplemente colocando la propiedad en su interfaz de clase.

Editar:

Es posible que desee hacer esto más específicamente:

@synthesize title = _title;

Esto coincidirá con la forma en que la síntesis automática de xcode crea propiedades e ivars si usa la síntesis automática, de modo que si su clase tiene propiedades de un protocolo y una clase, algunos de sus ivars no tendrán el formato diferente que podría afectar legibilidad.

Kevlar
fuente
1
¿Estás completamente seguro? Tengo una propiedad opcional establecida en un protocolo, y cuando solo lo sintetizo en una clase concreta que se ajusta a ese protocolo, recibo un error del compilador alegando que es una variable no declarada. No hay errores tipográficos confirmados.
Coocoo4Cocoa
No estoy seguro acerca de las propiedades opcionales, pero una cosa que olvidé mencionar como dijo mralex es que debe vincularlo a una variable miembro, ya sea nombrando el título de la variable o diciendo @synthesize title = myinstancevar;
Kevlar
2
Si está utilizando el tiempo de ejecución moderno, @synthesize es todo lo que necesita, los ivars subyacentes se crearán para usted. Si está apuntando a x86 de 32 bits, recibirá el error del compilador mencionado, porque está apuntando al tiempo de ejecución heredado.
Jeffrey Harris el
1
La síntesis automática se introdujo en Xcode 4.4, pero según un tweet de Graham Lee , no cubre las propiedades declaradas en los protocolos. Por lo tanto, aún deberá sintetizar manualmente esas propiedades.
cbowns
Este es un gran punto, no me di cuenta de que agregar synthesizeera suficiente. ¡Frio!
Dan Rosenstark
9

Echa un vistazo a mi artículo PROPIEDAD EN PROTOCOLO

Supongamos que tengo MyProtocol que declara una propiedad de nombre y MyClass que se ajusta a este protocolo

Cosas que vale la pena notar

  1. La propiedad de identificador en MyClass declara y genera getter, setter y backing _identifier variable
  2. La propiedad de nombre solo declara que MyClass tiene un captador, establecedor en el encabezado. No genera getter, implementación de setter y variable de respaldo.
  3. No puedo volver a declarar esta propiedad de nombre, como ya lo declaró el protocolo. Hacer esto gritará un error

    @interface MyClass () // Class extension
    
    @property (nonatomic, strong) NSString *name;
    
    @end

Cómo usar la propiedad en el protocolo

Entonces, para usar MyClass con esa propiedad de nombre, tenemos que hacer

  1. Declare la propiedad nuevamente (AppDelegate.h lo hace de esta manera)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Sintetizarnos

    @implementation MyClass
    
    @synthesize name;
    
    @end
onmyway133
fuente
Los bloques de código anidados dentro de las listas deben tener una sangría de ocho espacios por línea. Es una rareza relativamente desconocida de la sintaxis de Markdown. He editado tu respuesta por ti.
BoltClock
1

Arquitectura de protocolo

Ejemplo: 2 clases (Persona y Serie) desean usar el servicio de Viewer ... y deben cumplir con ViewerProtocol. viewerTypeOfDescription es una propiedad obligatoria que las clases de suscriptores deben cumplir.

typedef enum ViewerTypeOfDescription {
    ViewerDataType_NSString,
    ViewerDataType_NSNumber,
} ViewerTypeOfDescription;

@protocol ViewerProtocol
@property ViewerTypeOfDescription viewerTypeOfDescription;
- (id)initConforming;
- (NSString*)nameOfClass;
- (id)dataRepresentation;
@end

@interface Viewer : NSObject
+ (void) printLargeDescription:(id <ViewerProtocol>)object;
@end

@implementation Viewer
+ (void) printLargeDescription:(id <ViewerProtocol>)object {
    NSString *data;
    NSString *type;
    switch ([object viewerTypeOfDescription]) {
        case ViewerDataType_NSString: {
            data=[object dataRepresentation];
            type=@"String";
            break;
        }
        case ViewerDataType_NSNumber: {
            data=[(NSNumber*)[object dataRepresentation] stringValue];
            type=@"Number";
            break;
        }
        default: {
            data=@"";
            type=@"Undefined";
            break;
        }
    }
    printf("%s [%s(%s)]\n",[data cStringUsingEncoding:NSUTF8StringEncoding],
           [[object nameOfClass] cStringUsingEncoding:NSUTF8StringEncoding],
           [type cStringUsingEncoding:NSUTF8StringEncoding]);
}
@end


/* A Class Person */

@interface Person : NSObject <ViewerProtocol>
@property NSString *firstname;
@property NSString *lastname;
@end

@implementation Person
// >>
@synthesize viewerTypeOfDescription;
// <<
@synthesize firstname;
@synthesize lastname;
// >>
- (id)initConforming {
    if (self=[super init]) {
        viewerTypeOfDescription=ViewerDataType_NSString;
    }
    return self;
}
- (NSString*)nameOfClass {
    return [self className];
}
- (NSString*) dataRepresentation {
    if (firstname!=nil && lastname!=nil) {
        return [NSString stringWithFormat:@"%@ %@", firstname, lastname];
    } else if (firstname!=nil) {
        return [NSString stringWithFormat:@"%@", firstname];
    }
    return [NSString stringWithFormat:@"%@", lastname];
}
// <<
@end



/* A Class Serial */

@interface Serial : NSObject <ViewerProtocol>
@property NSInteger amount;
@property NSInteger factor;
@end

@implementation Serial
// >>
@synthesize viewerTypeOfDescription;
// <<
@synthesize amount;
@synthesize factor;
// >>
- (id)initConforming {
    if (self=[super init]) {
        amount=0; factor=0;
        viewerTypeOfDescription=ViewerDataType_NSNumber;
    }
    return self;
}
- (NSString*)nameOfClass {
    return [self className];
}
- (NSNumber*) dataRepresentation {
    if (factor==0) {
        return [NSNumber numberWithInteger:amount];
    } else if (amount==0) {
        return [NSNumber numberWithInteger:0];
    }
    return [NSNumber numberWithInteger:(factor*amount)];
}
// <<
@end




int main(int argc, const char * argv[])
{

    @autoreleasepool {

        Person *duncan=[[Person alloc]initConforming];
        duncan.firstname=@"Duncan";
        duncan.lastname=@"Smith";

        [Viewer printLargeDescription:duncan];

        Serial *x890tyu=[[Serial alloc]initConforming];
        x890tyu.amount=1564;

        [Viewer printLargeDescription:x890tyu];

        NSObject *anobject=[[NSObject alloc]init];

        //[Viewer printLargeDescription:anobject];
        //<< compilator claim an issue the object does not conform to protocol

    }
    return 0;
}

Otro ejemplo con herencia de protocolo sobre subclases

typedef enum {
    LogerDataType_null,
    LogerDataType_int,
    LogerDataType_string,
} LogerDataType;

@protocol LogerProtocol
@property size_t numberOfDataItems;
@property LogerDataType dataType;
@property void** data;
@end

@interface Loger : NSObject
+ (void) print:(id<LogerProtocol>)object;
@end

@implementation Loger
+ (void) print:(id<LogerProtocol>)object {
    if ([object numberOfDataItems]==0) return;
    void **data=[object data];
    for (size_t i=0; i<[object numberOfDataItems]; i++) {
        switch ([object dataType]) {
            case LogerDataType_int: {
                printf("%d\n",(int)data[i]);
            break;
            }
            case LogerDataType_string: {
                printf("%s\n",(char*)data[i]);
                break;
            }
            default:
            break;
        }
    }
}
@end


// A Master Class

@interface ArrayOfItems : NSObject  <LogerProtocol>
@end

@implementation ArrayOfItems
@synthesize dataType;
@synthesize numberOfDataItems;
@synthesize data;
- (id)init {
    if (self=[super init]) {
        dataType=LogerDataType_null;
        numberOfDataItems=0;
    }
    return self;
}
@end

// A SubClass

@interface ArrayOfInts : ArrayOfItems
@end

@implementation ArrayOfInts
- (id)init {
    if (self=[super init]) {
        self.dataType=LogerDataType_int;
    }
    return self;
}
@end

// An other SubClass

@interface ArrayOfStrings : ArrayOfItems
@end

@implementation ArrayOfStrings
- (id)init {
    if (self=[super init]) {
        self.dataType=LogerDataType_string;
    }
    return self;
}
@end


int main(int argc, const char * argv[])
{

    @autoreleasepool {

        ArrayOfInts *arr=[[ArrayOfInts alloc]init];
        arr.data=(void*[]){(int*)14,(int*)25,(int*)74};
        arr.numberOfDataItems=3;

        [Loger print:arr];

        ArrayOfStrings *arrstr=[[ArrayOfStrings alloc]init];
        arrstr.data=(void*[]){(char*)"string1",(char*)"string2"};
        arrstr.numberOfDataItems=2;

        [Loger print:arrstr];

    }
    return 0;
}
Luc-Olivier
fuente
0

La variable, unObjeto, debe definirse en su definición de clase TestProtocolsViewController, el protocolo solo le informa que debería estar allí.

Los errores del compilador te dicen la verdad: la variable no existe. @properties son solo ayudantes después de todo.

mralex
fuente