6-19 协议
一.协议
1.协议作用:完成两个类的对象的通信。
------------------------------------------------------------------------------------
2.定义方法:
例子:
@protocol 协议名 <遵守的其他协议的名称>
方法声明;
@end
注意:a.以上内容可以写在任意位置,但如同类一样,一般都写在一个头文件里。
b.协议的内容只能有方法声明,不能有变量声明
------------------------------------------------------------------------------------
3.如何让类签署(or称遵循)某协议呢?
答:1)遵守协议的某个类里,要包含协议的头文件。这时就代表该类签署了该协议,且必须实现协议里的一些方法。(协议中的方法,类中可以不用再次声明。)
2)在类的声明中,要加入<协议名>,当有多个要遵守的协议时,协议名之间用逗号","隔开。
例子:
@interface Coder : NSObject <Program> //程序员类遵守了Program协议
------------------------------------------------------------------------------------
4.如何让已经遵循某协议的类去调用协议里的方法(即:使用代理)呢?
答:
前提说明:Coder类遵守了Program协议;Program协议中有program方法。
主要函数: id <Program> delegate = [tom retain];
例子:
/* 有个程序员会编程,他把这个能力出卖给了协议,然后作为代理去编程*/
Coder * tom = [[Coder alloc]init];//一个类遵守了协议,那么这个类的也就遵守了协议
//设置代理:id 指向的对象,要遵守program协议;
id <Program> delegate = [tom retain];//把程序员Tom作为一个代理,(Tom指向的对象计数器+1,然后把对象地址赋值给delegate).
[delegate program];
[delegate release];
[tom release];
注意:a.代理只能调用协议里的方法
b.当代理没有调用协议里的方法是,有警告,有语法错误,尽量不要这样用,但也可以执行。
-----------------------------------------------------------------------------------------------------------------------------------
5.协议的属性: @required & @optional
@required:修饰的方法,遵循这个协议的类必须要实现,这个属性是协议的默认属性,可以不写这个关键字。
@optional:修饰的方法是可选的,遵循这个协议的类可以实现,也可以不实现。也就是说,如果遵守协议的类,有这个方法,那么就出卖,没有就什么都不做。
------------------------------------------------------------------------------------
6.关于继承
我们可以看到,创建一个协议的时候,它会自动遵循<NSObject>协议,但我们自己并没有<NSObject>协议里的方法,这是如何实现的呢?
答:以自定义WorkerConform协议,Coder类遵循WorkerConform协议为例。
虽然WorkerConform遵守NSObject协议,但由于NSObject类中,已经实现了NSObject协议中的方法,而且Coder类继承于NSObject类,那么就代表Coder类实现了NSObject协议中的方法。
------------------------------------------------------------------------------------
7.单向代理
------代理本质:就是一个对象指针,只不过是只能执行协议中的内容
------何时使用代理,如何设置代理?
答:情况一:一个事物A委托另外一个事物B
则:A是主动方;
B是被动方;将B设置为代理。
情况二:一个事物A给B发送消息,让B做某些事
则:B是代理。
------单向代理的具体实例:见老师orMy课堂代码OC4
------程序说明:
有一个公司的老板,boss zhang,不会买木头;
worker wang 会买木头;
boss zhang 通知 worker wang去买木头;
<==>boss zhang 委托 worker wang 去买木头
委托 == 代理;
------实现思路:
1)设计两个类,Boss类和Worker类;设计一个协议BuyWood协议。
2)BuyWood协议中:声明方法-(void)buyWood:(int)_w;//买多少木头。
3)Worker类遵循BuyWood协议,实现买木头方法。
4)Boss类:a.头文件要包含BuyWood协议的头文件。
b.定义成员变量:id <BuyWood> delegate;//把遵守买木头协议的worker作为代理
c.因为成员变量(delegate)本质是对象指针,因此它的setter函数要特殊处理。
------手动实现方法:
-(void)setDelggate:(id<BuyWood>)_delegate
{
if(delegate!= _delegate)//先判断新值旧值是否一致
{
[delegate release];//先release旧值
delegate = [_delegate retain]; //再赋新值
}
}
------自动实现方法:
@property (retain)id <BuyWood>delegate;
@synthesize delegate;
------注意:delegate是成员变量,成员变量在整个boss消失的时候才会消失,又因为这里为delegate retain 了一次,那么必须要在dealloc中release一次。因此要重写dealloc方法。
------重写dealloc方法:
-(void)dealloc
{
[delegate release];
[super dealloc];
NSLog(@"boss木有了!");
}
d.要写一个给代理发送消息的方法:Boss通知Worker买木头。
-(void)sendBuyWood:(int)_w
{
NSLog(@"send buy wood");
[delegate buyWood:_w];
}
5)主函数的实现:
Worker * wang = [[Worker alloc]init];
Boss * zhang = [[Boss alloc]init];
[zhang setDelggate:wang];//把wang设置为代理
[zhang sendBuyWood:10];
[wang release];
[zhang release];
------------------------------------------------------------------------------------
8.双向代理:互相委托对方做一些事情。
------程序说明:
小狗算数:
委托
小狗 叫 一次 <---------- 驯兽师 挥手
委托
----------> 记一次数
------错误设计思路:
按照单向代理的方式,相互将对方设置为代理。
造成后果:两个类的对象最后都没有得到释放。(具体代码OC6-error)
-------什么是相互引用,如何进行相互引用?
答:相互引用:当两个类,把对方的对象指针互作为成员变量,设置或赋值时,就会引起相互引用。
涉及到相互引用,那么必须一强一弱
强引用:会使计数器加1,或者置为1(retain alloc) 对应@property的参数(retain/copy/strong)
弱引用:直接赋值操作,其他什么都不操作。(不会修改计数器的值)对应@property的参数(assign)
双向代理:就是一个相互引用/循环引用
-------正确设计思路:
答:采用一强一弱的设计方式。具体实例见(OC6-OK)
------------------------------------------------------------------------------------
9.代理的常用写法
步骤如下:
a.确定主动方与被动方(被动方为代理,需要遵守协议)
b.将协议写在主动方的头文件中
c.主动方类的实现:
-主动方的对象指针采用强引用来实现。
-成员变量中,要有一个代理。
-实现向被动方发消息的方法。
d.被动方类的实现:
-遵守协议
-成员变量中,要有一个主动方的一个对象指针。
-代理采用弱引用来实现。
-实现主动方发来消息后,被动方要实现的动作。
注意:这里主动方强引用&被动方弱引用,实现的方法,都是在对方类中用到,在对方类中实现的,不要写反了。
具体实例见OC7
********************************************************************************************
二.其他知识点补充
1.头文件相互引用问题
-----什么是头文件相互引用?
答:例如:在ClassA的头文件中写了#import"ClassB";同时,在ClassB的头文件中写了#import"ClassA",这种情况就是头文件相互引用。
-----会造成什么后果?
答:两个类都没有创建成功。
-----如何解决这个问题?
答:#import,#include都不能解决这个问题。要使用向前引用声明一个类来解决。
例如:在ClassA的头文件中,写@class ClassB;它表明:提前声明了ClassB类,假设这个类已经实现了,下面的函数可以使用这个类;(意思是在这个头文件中只能使用这个类名,因为头文件中都是函数声明,所以只能是使用了这个ClassB的类名,不会使用其他东西,其他的使用,在ClassA的实现中使用)。
接下来,在ClassA的.m文件中,写#import"ClassB.h",这样,在ClassA的实现中,就可以使用ClassB里的方法了。
按照上种方式进行编程,没有头文件相互引用,解决了这个问题。在OC中,不可以写出头文件相互引用的形式。
------------------------------------------------------------------------------------
2.关于@property的属性nonatomic
我们已经知道,当一个类的成员变量是对象指针时,在@property声明时,要加入"retain"这个关键字来修饰。这时@property的默认属性是:retain、atomic。
@synthesize时实现的setter & getter函数等价于下面两个函数:
-(void)setEngine:(Engine *)_engine
{
if (engine != _engine)
{
[engine release];//在赋新值之前,要把旧值release一次。
engine = [_engine retain];
}
}
-(Engine *)engine
{
return [[engine retain]autorelease];
}
而,当我们用@property(nonatomic,retain)来修饰一个是对象指针的成员变量时,
@synthesize时,自动实现的setter & getter函数等价于下面两个函数:
-(void)setEngine:(Engine *)_engine
{
if (engine != _engine)
{
[engine release];//在赋新值之前,要把旧值release一次。
engine = [_engine retain];
}
}
-(Engine *)engine
{
return engine ;
}
注意:两者getter函数的不同。后者getter函数没有retain,那么当使用它对另外一个对象指针赋值时,就要调用retain来计数,且结束后,记得要释放。
例如:对后者的调用方法如下:
Engine * engine2 = [car.engine retain] ;
tail2 release];
********************************************************************************************