谈objective-c中的协议

objective-c中的协议有多种,其中正式协议要求显示的采用协议。采用协议的办法是在类的@interface声明中列出协议的名称。采用协议意味着你承诺实现改协议的所有方法。

声明协议的语法看起来与声明类或类别的语法有点像。不过这里使用的是@protocol告诉编译器这是一个新的正式协议。@protocol之后是协议名称,名称必须唯一。然后是一个方法声明列表,协议的每个采用者都必须实现这些方法。
协议声明以@end结束,使用协议不引入新的实例变量。下面是NSCoding协议:

@protocol NSCoding
-(void) encodeWithCoder:(NSCoder*)aCoder;

-(id) initWithCoder:(NSCoder*)aDecoder;
@end

当某个类采用NSCoding协议时,该类则承诺实现这两个方法。encodeWithCoder:方法用于接受对象的实例变量并将其转换为NSCoder类的对象。initWithCoder:方法从NSCoder类的对象中提取经过转换的冻结的实例变量并使用它们初始化一个新对象。这两个方法总是成对实现的。如果你从来不将一个对象转换为另一个新对象,那么对该对象进行编码是毫无疑义的,如果你从不对对象进行编码,那么你也无法创建一个新对象。

要采用某个协议,需要在类的声明中列出该协议的名称,并用尖括号将协议名称括起来。例如,Car类要采用NSCopying协议,则其类声明如下:

@interface Car : NSObject <NSCopying>
{
  //instance variables
}
 // methods
@end 

采用某个协议表示该类的对象可以完成两个非常重要的操作:一是能够对自身进行编码或解码,二是能够复制自身。
要复制一个car对象,需要复制engine和tire对象。
Engine类的新接口如下:
@interface Engine : NSObject <NSCopying>
@end
因为Engine类采用了NSCopying协议,所以必须实现copyWithZone方法。zone是NSZone类的一个对象,指向一块可供分配的内存区域。当你向一个对象发送copy消息时,该copy消息在到达你的代码之前被转换为copyWithZone方法。
Engine类的copyWithZone方法实现如下:

-(id) copyWithZone:(NSZone*)zone
{
   Engine*engineCopy;
   engineCopy =[[[self class]
                           allocWithZone:zone]
                         init];
return(engineCopy);
}

由于Engine类没有实例变量,因此必须创建一个新的engine对象。但是engineCopy对象右边的声明是非常复杂的,消息发送嵌套深度多达3层。
copyWithZone:方法的首要任务是获得self对象所属的类。然后copyWithZone方法向self对象所属的类发送allocWithZone消息,以分配内存并创建一个该类的新对象。最后copyWithZone方法给这个新对象发送init消息以使其初始化。下面讨论为什么要使用复杂的消息嵌套,尤其是[self class]这种用法。
alloc是一个类方法,由于allocWithZone方法的声明是以加号开头的,因此它也是一个类方法:

+(id)allocWithZone:(NSZone*)zone;

需要将该消息发送给一个类,而不是一个实例变量。考虑一下Engine的子类Slant6。如果给一个Slant6类的对象发送copy消息,因为我们最后使用的是Engine类的复制方法,所以这行代码最终在Engine类的copyWithZone方法中结束。若直接给Engine类发送allocWithZone:消息,则将创建一个新的Engine类的对象,而不是Slant6类的对象。如果Slant6类增加一些实例变量,Engine类对象将无法容纳额外的变量,从而导致内存溢出错误。
所以通过[self class],allocWithZone消息将会被发送给正在接收copy消息的对象所属的类,如果self是一个slant6类的对象,那这里将创建一个slant6类的新对象。如果我们的程序在将来添加了一些全新的engine对象(如MatterAntiMatterReactor),则这些新的engine对象也会被正确复制。
allocWithZone方法的最后一行返回新创建的对象。
Tire类有两个实例变量pressure和threadDepth,这两个实例变量需要被复制到Tire类的新对象中,而且AllWeatherRadial子类又引入了两个额外的实例变量,rainHandling和snowHandling,这两个变量也需要被复制到新对象中。首先Tire类遵守协议采用语法的接口如下:

@interface Tire:NSObject<NSCopying>

{
  float presure;
  float treadDepth;
 }
@end

下面是copyWithZone方法的实现:

-(id) copyWithZone:(NSZone*)zone
{
Tire *tireCopy;
tireCopy=[[[self class] allocWithZone:zone]
                  initWithPressure:pressure
                  threadDepth:threadDepth];
return (tireCopy);
}

由于创建对象时必须调用init方法,所以将新的tire对象的pressure和threadDepth设置为我们正在复制的tire对象的值。该方法正好是Tire类的指定初始化函数,但并不是必须要使用指定初始化函数来执行复制操作。也可以使用简单的init方法和访问器方法来修改对象的属性。
因为AllWeatherRadial是一个可以复制的类的子类,所以它既不需要实现allocWithZone方法,也不需要使用[self class]形式。AllWeatherRadial类只需要请求其父类执行copy操作,并期望父类正确复制以及在分配对象时使用[self class]技术。因为Tire类的copyWithZone方法使用[self class]来确定要复制的对象所属的类,所以该方法将创建一个AllWeatherRadial类的新对象。
由于要履行对NSCopying协议的承诺,Car类必须实现友元方法copyWithZone。下面是Car类的copyWithZone方法的实现:

-(id) copyWithZone:(NSZone*)zone
{
Car*carCopy;
carCopy = [[[self class]
                    allocWithzone:zone]
                init];

carCopy.name=self.name;
Engine*engineCopy;
engineCopy=[[engine copy]auto release];
carCopy.engine=engineCopy;
int i;
for(i=0;i<4;i++){
Tire*tireCopy;

tireCopy=[[self tireAtIndex:i]copy];
[tireCopy autore lease];

[carCopy setTire:tireCopy
           atIndex:i];
}
return (carCopy);
}

首先,通过给正在接收copy消息的对象所属的类发送allocWithZone消息分配一个新的car对象:

Car *carCopy;
carCopy=[[[self class]
                 allocWithZone:zone]
              init];

虽然CarParts-copy项目现在不包含Car类的子类,但还是要通过使用self所属的类分配新对象来保障Car类的未来,需要复制car对象的名称:
carCopy.name=self.name;
name属性复制其字符串对象,因此新的car对象将拥有正确的名称。
接下来复制engine对象,并通知carCopy使用复制的engine对象作为自己的engine属性:

Engine*engineCopy;
engineCopy=[[engine copy]auto release];
carCopy.engine=engineCopy;

engine对象为什么要自动释放?因为通过自动释放engine对象,其保留计数器的值将在未来某个时间自动释放池被销毁时减少1。
我们能够使用简单的[engineCopy release]替代engine对象的自动释放,这样就必须在setEngine方法被调用以后再释放该engineCopy对象。否则engineCopy对象可能在使用之前就被销毁了。
在carCopy保留了新的engine对象以后,copyWithZone方法执行4次for循环,分别复制每个tire对象并将复制的对象安装到新的car中:

 int  i;
for (i=0;i<4;i++){
   Tire*tireCopy;
tireCopy =[[self tireAtIndex:i]copy];
[tireCopy auto release];

[carCopy setTire:tireCopy
           atIndex:i];
}

循环中的代码使用访问器方法在每趟循环中先后获得位置0的tire对象,位置1的tire对象,然后这些tire对象被复制并被自动释放,因而它们的内存可以被正确回收。接下黎carCopy被告之使用同一位置的新的tire对象。因为我们已经在Tire类和AllWeatherRadial类中精心构造了copyWithZone方法,所以这段代码可以使用这两个类的tire对象正常工作。
最后是完整的main()函数:

int main (int argc,const char*argv[])
{
NSAuto releasePool*pool;
pool=[[NSAuto releasePool alloc] init];

Car*car =[[Car alloc]init];
car.name =@“Herbie”;

int i;
for (i=0;i<4;i++){
  AllWeatherRadial*tire;

  tire=[[AllWeatherRadial alloc] init];
[car setTire:tire
   atIndex:i];
[tire release];
}
Slant6*engine=[[Slant6 alloc] init];
car.engine =engine;
[engine release];

[car print];

car*carCopy=[car copy];
[carCopy print];

[car release];
[carCopy release];

[pool release];

return (0);

}

该程序在输出原始的car对象以后,复制该car对象并将其输出。
你可以在使用的数据类型中为实例变量和方法参数指定协议名称。在id类型表示一个可以指向任何类型的对象的指针,它是一个泛型对象类型。你可以将任何对象赋值给一个id类型的变量,也可以将一个id类型的变量赋值给任何类型的对象指针。如果一个用尖括号括起来的协议名称跟随在id之后,则编译器讲知道你期望任意类型的对象。例如,NSControl类中有一个名为setObjectValue的方法,该方法要求对象遵守NSCopying协议:
-(void)setObjectValue:(id<NSCopying)obj;
编译器将检查参数类型并提出警告。
objective-C2.0增加类两个新的协议修饰符:@optional和@required。一个BaseballPlayer协议的类有两个要求实现的方法:-drawHugeSalary和
-swingBat ,还有3个不可选择实现的方法slideHome,catchBall 和throwBall

Cocoa中的非正式协议逐渐被带有许多@optional方法正式协议所代替。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 1.什么是复合?在Objective-C 中,复合是通过包含作为实例变量的对象指针实现的:(我们定义一辆车有一个发...
    poo_om阅读 308评论 0 0
  • Objective-C 1. import的用法 拷贝文件内容可以自动防止文件的内容被重复拷贝(#define宏定...
    马文涛阅读 5,317评论 3 17
  • 1.项目经验 2.基础问题 3.指南认识 4.解决思路 ios开发三大块: 1.Oc基础 2.CocoaTouch...
    阳光的大男孩儿阅读 4,957评论 0 13
  • 下面是我最近两年学习OC中的一些基础知识,对于学习OC基础知识的人可能有些帮助,拿出来分享一下,还是那句话不喜勿喷...
    小小赵纸农阅读 2,544评论 1 7
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,270评论 0 6