题记:Each man is the architect of his own fate.——每个人都是自己命运的建筑师。
今天要复习的是类别和扩展、协议和委托,这些在我们编写的应用程序中会经常用到,我想大家都会使用,在这里主要复习一下它们的原理,我们要不仅会用,还要理解它为什么这么用。
首先讲一下类别和扩展的联系和区别
类别和扩展
1. 类别
(1)为已有的类添加新的方法
(2)可以在类别中添加新属性,但是不能为类别添加新的实例变量。
-
这里需要注意的是:可以在类别中添加新属性,但是类别中无法把实现属性所需的实例变量及其存取方法合成出来,此时需要在类别中将属性的存取方法声明为
@dynamic
,即这些方法等到程序运行时再提供,如果不添加@dynamic
声明属性,编辑器将发出警告如下图:
解决
将接口文件中声明的属性,在实现文件中声明为@dynamic
,即@dynamic myFriend;
,此时意味着在程序运行时再为为myFriend属性添加存取方法,代码如下:
// 示例代码
// 接口文件代码
@interface NSObject (Friend)
@property(nonatomic, copy) NSString *myFriend;
-(void)testMyFriend;
@end
// 实现文件的代码
#import "NSObject+Friend.h"
@implementation NSObject (Friend)
@dynamic myFriend;
-(void)testMyFriend{
self.myFriend = @"Lynn";
NSLog(@"%@",self.myFriend);
}
@end
还有一种解决策略就是直接在类的实现文件中添加该属性的存取方法,但是始终无法合成实例变量
#import "NSObject+Friend.h"
#import <objc/message.h>
@implementation NSObject (Friend)
-(void)setMyFriend:(NSString *)myFriend{
objc_setAssociatedObject(self,"abc",myFriend,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)myFriend{
return objc_getAssociatedObject(self,"abc");
}
-(void)testMyFriend{
self.myFriend = @"Lynn";
NSLog(@"%@",self.myFriend);
}
@end
(3)缺陷:类别有两个局限
-1. 无法向类中添加实例变量,类别中没有空间容纳实例变量。
-2. 存在名称冲突问题,即类别中的实力方法与现有方法重名,类别具有更高优先级,造成方法覆盖。
(4) 优势:
-1.可以将类的实现代码放到不同的文件或框架中,需要的时候引用特定的文件即可。
-2.可以创建对私有方法的前行引用。
这里想要解释一下什么是前向引用。
我们总说在OC中并没用真正的私有方法,那在OC中又是如何实现对私有方法的调用的呢?
这里有两种方法实现对私有方法的调用:
-(1) 通过performSelector
方法在程序运行时动态调用,避开编译器的检查(在文章的最后讲到一些performSelector
方法的一些特性)
-(2) 通过类别定义前向引用,即类A中定义的私有方法是无法直接访问的,但是可以通过创建类A的一个类别B,在类别B的接口文件中声明这个私有方法,当需要调用这个私有方法是,在那个实现文件中引入该类别的头文件即可。
2. 扩展
- 扩展与类别类似,其实就相当与匿名的类别
- 扩展可用于临时对某个类的借口进行扩展,在类的实现文件中定义,扩展的定义如下:
// 在类的实现文件中
@interface XUCenterViewController ()
// 定义私有示例变量、属性和方法
@end
类的实现文件同时实现类接口中声明的方法和扩展中定义的方法。
接下来讲一下协议和委托,但还需要指出的是协议还可以进一步划分,分成正式协议和非正式协议
协议与委托
1. 协议
什么是协议
协议的作用与类别类似,都是用于对类的扩展。
一提到协议,总会出现另一个词与之紧密相连,就是规范。
协议是用于定义多个类应该遵守的规范,并不提供实现的细节,协议中的方法的实现留给遵守它的类实现,巧妙的将规范和实现分离,实现松耦合。非正式协议
什么是非正式协议呢?
非正式协议的实现离不开类别,可以使用类别实现非正式协议,实现非正式协议的类别需要满足是NSObject的类别,在这个类别的接口文件中声明方法,但不需要实现,当某个类实现该NSObject的类别时,可以在实现文件中实现该类别中声明的方法,此时该NSObject的类别就可以视为非正式协议。
示例代码如下:
// 分类的接口文件
@interface NSObject (Friend)
// 未实现
-(void)testUnformal;
@end
// 继承分类的TestUnform类的接口
#import "NSObject+Friend.h"
@interface TestUnform : NSObject
@end
// TestUnform类的实现
#import "TestUnform.h"
@implementation TestUnform
-(void)testUnformal{
NSLog(@"这是非正式协议");
}
@end
此时Friend分类就是非正式协议
3. 正式协议
(1)正式协议具有继承性,可以继承其他正式协议,不能继承类,支持多继承,协议中的方法均是共有的方法,只有方法的声明,没有实现,实现部分交给遵守它的类来完成。
@protocol TestUnformProtocal <NSObject>
// 声明方法
@end
(2)协议的实现
如果自定义的类希望遵守某个协议,此时需要在自定义类的接口部分或扩展部分遵守该协议,支持同时遵守多个协议
// 接口文件中
#import "NSObject+Friend.h"
@protocol TestUnformProtocal <NSObject>
// 声明方法
@end
@interface TestUnform : NSObject<TestUnformProtocal>
@end
// 实现文件中实现协议中的声明的方法
// 或在实现文件中的扩展中遵守该协议
#import "TestUnform.h"
@interface TestUnform ()<TestUnformProtocal>
@end
@implementation TestUnform
// 实现协议中的声明的方法
@end
(3)协议中的关键字 @require
和@optioanal
关键字 @require
:自定义类遵守该协议后,必须实现
关键字 @optioanal
:自定义类遵守该协议后,非必须实现,根据需要有选择的实现
4. 委托与代理
使用委托与代理的方法,可以实现视图、控制器间的数据传输。实现的思想就是:
(1)定义一个正式协议
(2)自定义类ClassA的接口文件中声明一个遵守协议的变量
@property(nonatomic, weak) id<TestUnformProtocal> delegate;
(3)在另一个自定义ClassB类中定义一个ClassA的属性objA,在ClassB类中设置ClassA类的代理为ClassB,即objA.delegate = self;
(4)让ClassB类遵守TestUnformProtocal协议,并实现TestUnformProtocal协议中定义的方法
(5)这时就可以在ClassA类的实现文件中调用这个在ClassB类中实现的协议的方法,ClassA类中
if([self.delegate respondsToSelector:@selector(/*代理方法*/)]){
[self.delegate /*代理方法*/];//调用代理方法
}
performSelector
方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
这里的aSelector参数是一个选择子,就是一个方法名,这个选择子在运行时决定,在ARC机制下使用performSelector
方法可能存在内存泄漏,是因为编译器并不知道将要调用的选择子是什么,无法运用ARC的内存管理规则来判断,所以在编译时不添加释放的操作。
对于返回值的类型只能是void或对象类型,允许传递的参数的个数也存在局限性(0到2个)
总结
类别和扩展、协议和委托,实现都不是很难,放在一起要梳理好各部分的知识,搞清楚弄明白才能运用自如。