Como lidar com protocolos Objective-C que contêm propriedades?

131

Vi o uso de protocolos Objective-C serem usados ​​de uma maneira como a seguinte:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

Eu já vi esse formato usado em vez de escrever uma superclasse concreta que as subclasses se estendem. A questão é: se você está em conformidade com este protocolo, precisa sintetizar as propriedades você mesmo? Se você está estendendo uma superclasse, a resposta é obviamente não, você não precisa. Mas como lidar com propriedades que um protocolo requer para se conformar?

Para meu entendimento, você ainda precisa declarar as variáveis ​​de instância no arquivo de cabeçalho de um objeto que esteja em conformidade com um protocolo que exija essas propriedades. Nesse caso, podemos assumir que eles são apenas um princípio norteador? Claramente o mesmo não é o caso de um método necessário. O compilador dará um tapa no seu pulso por excluir um método necessário listado por um protocolo. Qual é a história por trás das propriedades?

Aqui está um exemplo que gera um erro de compilação (Nota: reduzi o código que não reflete o problema em questão):

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
fonte

Respostas:

135

O protocolo está apenas dizendo a todos que conhecem sua classe através do protocolo, que a propriedade anObjectestará lá. Os protocolos não são reais, eles não têm variáveis ​​ou métodos próprios - eles apenas descrevem um conjunto específico de atributos que são verdadeiros sobre a sua classe, para que objetos com referências a eles possam usá-los de maneiras específicas.

Isso significa que, em sua classe, que está em conformidade com o seu protocolo, você precisa fazer tudo para garantir que um objeto funcione.

@propertye @synthesizeestão no coração dois mecanismos que geram código para você. @propertyestá apenas dizendo que haverá um método getter (e / ou setter) para esse nome de propriedade. Hoje em dia @property, basta apenas ter métodos e uma variável de armazenamento criada para você pelo sistema (você costumava adicionar @sythesize). Mas você precisa ter algo para acessar e armazenar a variável.

Kendall Helmstetter Gelner
fonte
80
Para propriedades definidas em um protocolo, você ainda precisa de um "@synthesize", mesmo no tempo de execução moderno, ou precisa duplicar o "@property" na sua definição de interface para obter a auto-síntese.
Jeffrey Harris
@JeffreyHarris Que tal o mesmo em Swift ??
Karan Alangat
@KaranAlangat - não existe \ @synthesize no Swift, mas, assim como o ObjC, você precisa declarar a propriedade em uma classe que afirma estar em conformidade com o protocolo. No Swift, você pode criar uma categoria que define uma implementação padrão de uma função, mas, até onde eu sei, não é possível ter uma propriedade padrão para um protocolo.
Kendall Helmstetter Gelner
31

Aqui está um exemplo meu que funciona perfeitamente, a definição do protocolo antes de tudo:

@class ExampleClass;

@protocol ExampleProtocol

@required

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

@end

Abaixo está um exemplo de trabalho de uma classe que suporta 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
fonte
14

tudo o que você precisa fazer é soltar um

@synthesize title;

em sua implementação e você deve estar pronto. funciona da mesma maneira que apenas colocar a propriedade na interface da classe.

Editar:

Você pode fazer isso mais especificamente:

@synthesize title = _title;

Isso ficará alinhado com o modo como a síntese automática do xcode cria propriedades e ivars se você usar a auto-síntese, de modo que, se sua classe tiver propriedades de um protocolo e uma classe, alguns de seus ivars não terão o formato diferente que pode afetar legibilidade.

Kevlar
fonte
1
Você está completamente certo? Eu tenho uma propriedade opcional configurada em um protocolo e quando eu apenas a sintetizo em uma classe concreta em conformidade com esse protocolo - recebo um erro do compilador alegando que é uma variável não declarada. Nenhum erro de digitação confirmado.
Coocoo4Cocoa
não tenho certeza sobre propriedades opcionais, mas uma coisa que esqueci de mencionar, como o mralex disse, é que você precisa vinculá-lo a uma variável de membro, nomeando esse título de variável ou dizendo @synthesize title = myinstancevar;
Kevlar #
2
Se você estiver usando o tempo de execução moderno, @synthesize é tudo que você precisa, os ivars subjacentes serão criados para você. Se você estiver segmentando x86 de 32 bits, será mencionado o erro do compilador, porque está direcionando o tempo de execução herdado.
Jeffrey Harris
1
A síntese automática foi introduzida no Xcode 4.4, mas de acordo com um tweet de Graham Lee , ele não cobre as propriedades declaradas nos protocolos. Portanto, você ainda precisará sintetizar manualmente essas propriedades.
Cbowns
Este é um ótimo ponto, não percebeu que adicionar o synthesizesuficiente. Legal!
Dan Rosenstark
9

Dê uma olhada no meu artigo PROPRIEDADE NO PROTOCOLO

Suponha que eu possua MyProtocol que declara uma propriedade de nome e MyClass que esteja em conformidade com este protocolo

Coisas dignas de nota

  1. A propriedade identifier em MyClass declara e gera a variável getter, setter e backing _identifier
  2. A propriedade name declara apenas que MyClass tem um getter, setter no cabeçalho. Ele não gera a implementação de getter, setter e variável de backup.
  3. Não consigo redefinir essa propriedade de nome, como já declarada pelo protocolo. Faça isso gritará um erro

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

Como usar a propriedade no protocolo

Então, para usar MyClass com essa propriedade name, precisamos fazer

  1. Declare a propriedade novamente (AppDelegate.h faz dessa maneira)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Sintetizar a nós mesmos

    @implementation MyClass
    
    @synthesize name;
    
    @end
onmyway133
fonte
Os blocos de código aninhados nas listas precisam ser recuados em oito espaços por linha. É uma singularidade relativamente desconhecida da sintaxe do Markdown. Editei sua resposta para você.
BoltClock
1

Arquitetura de protocolo

Exemplo: 2 classes (Pessoa e Serial) desejam usar o serviço do Viewer ... e devem estar em conformidade com o ViewerProtocol. viewerTypeOfDescription é uma propriedade obrigatória que as classes de assinante devem obedecer.

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

Um outro exemplo com herança de protocolo sobre subclasse

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
fonte
0

A variável anObject precisa ser definida na sua definição de classe TestProtocolsViewController, o protocolo está apenas informando que deve estar lá.

Os erros do compilador estão dizendo a verdade - a variável não existe. Afinal, @properties são apenas ajudantes.

mralex
fonte