How to handle Objective-C protocols that contain properties?

IphoneObjective CCocoa Touch

Iphone Problem Overview


I've seen usage of Objective-C protocols get used in a fashion such as the following:

@protocol MyProtocol <NSObject>
							  
@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

I've seen this format used instead of writing a concrete superclass that subclasses extend. The question is, if you conform to this protocol, do you need to synthesize the properties yourself? If you're extending a superclass, the answer is obviously no, you do not need to. But how does one deal with properties that a protocol requires to conform to?

To my understanding, you still need to declare the instance variables in the header file of an object that conforms to a protocol that requires these properties. In that case, can we assume that they're just a guiding principle? CLearly the same isn't the case for a required method. The compiler will slap your wrist for excluding a required method that a protocol lists. What's the story behind properties though?

Here's an example that generates a compile error (Note: I've trimmed the code which doesn't reflect upon the problem at hand):

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     

Iphone Solutions


Solution 1 - Iphone

The protocol is just telling everyone that knows about your class through the protocol, that the property anObject will be there. Protocols are not real, they have no variables or methods themselves - they only describe a specific set of attributes that is true about your class so that objects holding references to them can use them in specific ways.

That means in your class that conforms to your protocol, you have to do everything to make sure anObject works.

@property and @synthesize are at heart two mechanisms that generate code for you. @property is just saying there will be a getter (and/or setter) method for that property name. These days @property alone is enough to also have methods and a storage variable created for you by the system (you used to have to add @sythesize). But you have to have something to access and store the variable.

Solution 2 - Iphone

Here's an example of mine that works perfectly, the protocol definition first of all:

@class ExampleClass;

@protocol ExampleProtocol

@required

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

@end

Below is a working example of a class supporting this protocol:

#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

Solution 3 - Iphone

all you have to do really is to drop a

@synthesize title;

in your implementation and you should be all set. it works the same way as just putting the property in your class interface.

Edit:

You may want to do this more specifically:

@synthesize title = _title;

This will fall in line with how xcode's automatic synthesis creates properties and ivars if you use auto-synthesis, so that way if your class has properties from a protocol and a class, some of your ivars won't have the different format which could impact readability.

Solution 4 - Iphone

Suppose I have MyProtocol that declares a name property, and MyClass that conforms to this protocol

Things worth noted

  1. The identifier property in MyClass declares and generates getter, setter and backing _identifier variable

  2. The name property only declares that MyClass has a getter, setter in the header. It does not generate getter, setter implementation and backing variable.

  3. I can’t redeclare this name property, as it already declared by the protocol. Do this will yell an error

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

How to use property in protocol

So to use MyClass with that name property, we have to do either

  1. Declare the property again (AppDelegate.h does this way)

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

     @implementation MyClass
      
     @synthesize name;
      
     @end
    

Solution 5 - Iphone

Protocol Architecture

Example: 2 classes (Person and Serial) want use service of Viewer... and must conform to ViewerProtocol. viewerTypeOfDescription is a mandatory property subscriber classes must conform.

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

An other Example with Protocol inheritance over subClassing

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

Solution 6 - Iphone

The variable, anObject, needs to be defined in your TestProtocolsViewController class definition, the protocol is just informing you that it should be there.

The compiler errors are telling you the truth - the variable doesn't exist. @properties are just helpers after all.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCoocoo4CocoaView Question on Stackoverflow
Solution 1 - IphoneKendall Helmstetter GelnerView Answer on Stackoverflow
Solution 2 - IphoneredderskyView Answer on Stackoverflow
Solution 3 - IphoneKevlarView Answer on Stackoverflow
Solution 4 - Iphoneonmyway133View Answer on Stackoverflow
Solution 5 - IphoneLuc-OlivierView Answer on Stackoverflow
Solution 6 - IphonemralexView Answer on Stackoverflow