【问题标题】:How to handle Objective-C protocols that contain properties?如何处理包含属性的 Objective-C 协议?
【发布时间】:2010-10-25 02:04:26
【问题描述】:

我已经看到 Objective-C 协议的使用方式如下:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

我已经看到使用这种格式而不是编写子类扩展的具体超类。问题是,如果你遵守这个协议,你需要自己合成属性吗?如果你正在扩展一个超类,答案显然是否定的,你不需要。但是如何处理协议需要遵守的属性呢?

据我了解,您仍然需要在符合需要这些属性的协议的对象的头文件中声明实例变量。在那种情况下,我们可以假设它们只是一个指导原则吗?显然,所需方法的情况并非如此。编译器会为您排除协议列出的所需方法而拍打您的手腕。属性背后的故事是什么?

这是一个生成编译错误的示例(注意:我已经修剪了没有反映手头问题的代码):

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 objective-c cocoa-touch


    【解决方案1】:

    协议只是告诉通过协议知道你的类的每个人,属性anObject 将在那里。协议不是真实的,它们本身没有变量或方法——它们只描述了一组特定的属性,这些属性对你的类是正确的,因此持有对它们的引用的对象可以以特定的方式使用它们。

    这意味着在符合协议的类中,您必须尽一切努力确保 anObject 正常工作。

    @property@synthesize 本质上是为您生成代码的两种机制。 @property 只是说该属性名称将有一个 getter(和/或 setter)方法。如今,仅@property 就足以拥有系统为您创建的方法和存储变量(您以前必须添加@sythesize)。但是你必须有一些东西来访问和存储变量。

    【讨论】:

    • 对于协议中定义的属性,即使在现代运行时,您仍然需要“@synthesize”,或者您需要在接口定义中复制“@property”以获得自动综合。
    • @JeffreyHarris 在 Swift 中也一样吗??
    • @KaranAlangat - 在 Swift 中没有 \@synthesize 这样的东西,但就像 ObjC 一样,您确实需要在声称符合协议的类中声明属性。在 Swift 中,您可以创建一个定义函数默认实现的类别,但据我所知,您不能拥有协议的默认属性。
    【解决方案2】:

    看看我的文章PROPERTY IN PROTOCOL

    假设我有一个声明 name 属性的 MyProtocol,以及符合这个协议的 MyClass

    值得注意的事情

    1. MyClass 中的 identifier 属性声明并生成 getter、setter 和支持 _identifier 变量
    2. name 属性仅声明 MyClass 在标头中有一个 getter、setter。它不会生成 getter、setter 实现和支持变量。
    3. 我不能重新声明这个 name 属性,因为它已经被协议声明了。这样做会报错

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

    如何在协议中使用属性

    所以要使用带有该名称属性的 MyClass,我们必须这样做

    1. 再次声明该属性(AppDelegate.h 就是这样做的)

      @interface MyClass : NSObject <MyProtocol>
      
      @property (nonatomic, strong) NSString *name;
      
      @property (nonatomic, strong) NSString *identifier;
      
      @end
      
    2. 合成我们自己

      @implementation MyClass
      
      @synthesize name;
      
      @end
      

    【讨论】:

    • 嵌套在列表中的代码块需要每行缩进八个空格。这是 Markdown 语法中一个相对不为人知的奇怪之处。我已经为你编辑了你的答案。
    【解决方案3】:

    你真正要做的就是放下一个

    @synthesize title;
    

    在您的实施中,您应该已准备就绪。它的工作方式与将属性放入类接口中的方式相同。

    编辑:

    您可能想要更具体地执行此操作:

    @synthesize title = _title;
    

    如果您使用自动合成,这将符合 xcode 的自动合成如何创建属性和 ivars,因此如果您的类具有来自协议和类的属性,那么您的一些 ivars 将不会具有不同的格式这可能会影响可读性。

    【讨论】:

    • 您完全确定吗?我在协议中设置了一个可选属性,当我只在符合该协议的具体类中 @synthesize 时——我收到一个编译器错误,声称它是一个未声明的变量。没有确认错别字。
    • 我不确定可选属性,但我忘记提到的一件事就像 mralex 所说的那样,你需要将它绑定到一个成员变量,或者通过命名该变量标题,或者说 @synthesize标题 = myinstancevar;
    • 如果您使用的是现代运行时,@synthesize 就是您所需要的,底层 ivars 将为您创建。如果您的目标是 32 位 x86,您会收到提到的编译器错误,因为您的目标是旧版运行时。
    • Automatic synthesis 是在 Xcode 4.4 中引入的,但根据 tweet by Graham Lee,它不包括协议中声明的属性。所以你仍然需要手动合成这些属性。
    • 这很好,没有意识到添加synthesize 就足够了。酷!
    【解决方案4】:

    示例:2 个类(Person 和 Serial)想要使用 Viewer 的服务...并且必须符合 ViewerProtocol。 viewerTypeOfDescription 是订阅者类必须符合的强制属性。

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

    另一个通过子类继承协议的示例

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

    【讨论】:

      【解决方案5】:

      这是我的一个完美运行的示例,首先是协议定义:

      @class ExampleClass;
      
      @protocol ExampleProtocol
      
      @required
      
      // Properties
      @property (nonatomic, retain) ExampleClass *item;
      
      @end
      

      以下是支持此协议的类的工作示例:

      #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
      

      【讨论】:

        【解决方案6】:

        变量 anObject 需要在您的 TestProtocolsViewController 类定义中定义,协议只是通知您它应该在那里。

        编译器错误告诉你真相 - 变量不存在。 @properties 毕竟只是帮手。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-12-22
          • 2020-08-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多