iOS:Protocol详解
2019-05-17 本文已影响0人
码小菜
目录
一,基本概念
二,如何使用
三,特点
四,使用场景
五,系统常见协议
六,底层实现
一,基本概念
-
定义:将一些公共的方法抽取出来封装成协议,若某个类想拥有这些方法就需要先遵守此协议
-
为什么不用继承而用协议?
答:下图中若D
类跟E
类有公共的方法可以放在父类B
中,但如果D
类跟G
类有公共的方法放在父类的父类A
中显然是不合适的,这时协议就能很优雅的解决

二,如何使用
- 修饰符
NSObject
:在这里不是基类而是根协议,其他协议都要先遵守它
@required
:遵守此协议的类必须实现它修饰的方法(默认修饰符)
@optional
:遵守此协议的类可以不实现它修饰的方法
@protocol PersonProtocol <NSObject>
@required
- (void)eat;
@optional
- (void)run;
@end
- 能声明属性,但是不会自动生成
get/set
方法和带下划线的成员变量,需要在遵守协议的类中显示的调用@syntheszie ivar = _ivar;
,这样系统就会自动生成get/set
方法
@protocol PersonProtocol <NSObject>
@property (nonatomic, copy) NSString *name;
@end
@interface Person : NSObject <PersonProtocol>
@end

- 不能声明成员变量

- 在何处定义
1,在某类的.m
文件中(一般不这样使用)
2,在某类的.h
文件中(只服务于此类)
3,在单独文件中(服务于多个类)

三,特点
-
分类是非正式协议
-
协议只有方法的声明,没有方法的实现
-
遵守协议只能在类的声明
@interface
上,不能在类的实现@implementation
上 -
一个协议可以遵守多个其他协议
-
一个协议可以被任何类遵守,一个类可以遵守多个协议
-
一个协议若遵守了其他协议,就拥有其他协议所有方法的声明
-
一个类若遵守了某个协议,就必须实现协议中由
@required
修饰的方法 -
若父类遵守了某个协议,子类也就遵守了此协议
四,使用场景
- 不同的类使用统一入口传递数据
// CustomViewProtocol
@protocol CustomViewProtocol <NSObject>
- (void)setModel:(id)model;
@end
// CustomView
@interface CustomView : UIView <CustomViewProtocol>
@end
@implementation CustomView
- (void)setModel:(id)model { // 实现协议方法
NSLog(@"view---%@", model);
}
@end
// CustomTableView
@interface CustomTableView : UITableView <CustomViewProtocol>
@end
@implementation CustomTableView
- (void)setModel:(id)model { // 实现协议方法
NSLog(@"tableView---%@", model);
}
@end
// 传递数据
NSArray *views = @[[CustomView new], [CustomTableView new]];
NSArray *models = @[@"111", @"222"];
[views enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 判断是否遵守此协议
if ([obj conformsToProtocol:@protocol(CustomViewProtocol)]) {
[obj setModel:models[idx]];
}
}];
// 打印结果
view---111
tableView---222
- 面向接口编程:将接口和实现分离,对外只暴露接口

1,bridge
:关联接口和实现
// .h文件
@interface ServerBridge : NSObject
+ (void)bindServer:(id)server andProtocol:(Protocol *)protocol;
+ (id)serverForProtocol:(Protocol *)protocol;
@end
// .m文件
@interface ServerBridge ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, id> *serverStore;
@end
@implementation ServerBridge
- (NSMutableDictionary<NSString *,id> *)serverStore {
if (!_serverStore) {
_serverStore = [NSMutableDictionary new];
}
return _serverStore;
}
+ (instancetype)shared {
static id _bridge = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_bridge = [[self alloc] init];
});
return _bridge;
}
+ (void)bindServer:(id)server andProtocol:(Protocol *)protocol {
if ([server conformsToProtocol:protocol]) {
[[ServerBridge shared].serverStore setValue:server
forKey:NSStringFromProtocol(protocol)];
}
}
+ (id)serverForProtocol:(Protocol *)protocol {
return [[ServerBridge shared].serverStore valueForKey:NSStringFromProtocol(protocol)];
}
@end
2,protocol
:对外暴露的接口
@protocol ServerProtocol <NSObject>
@property (nonatomic, copy) NSString *provideData;
- (void)doSomething;
@end
3,server
:接口的具体实现
@interface Server () <ServerProtocol>
@end
@implementation Server
@synthesize provideData;
+ (void)load {
[ServerBridge bindServer:[self new]
andProtocol:@protocol(ServerProtocol)];
}
- (NSString *)provideData {
return @"server provide data";
}
- (void)doSomething {
NSLog(@"server do something");
}
@end
4,business
:使用接口的业务
id<ServerProtocol> server = [ServerBridge serverForProtocol:@protocol(ServerProtocol)];
NSLog(@"%@", server.provideData);
[server doSomething];
// 打印结果
server provide data
server do something
- 代理模式:在下一篇文章单独讲解
五,系统常见协议
-
NSObject
:根协议,一般协议都要遵守它,它提供了很多基本的方法,基类NSObject
已经遵守了它并实现了它的方法,所以我们在平时开发中可以直接使用这些方法
@protocol NSObject
- (id)performSelector:(SEL)aSelector;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)respondsToSelector:(SEL)aSelector;
...
@end
-
NSCopying
:是一个与对象拷贝有关的协议,如果想让自定义类对象支持拷贝,就需要让该类遵守此协议并实现copyWithZone
方法,当自定义类对象调用copy方法时,copy方法就会去调用copyWithZone
方法来完成拷贝
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
1,继承关系
// Man继承自Person,Person遵守了NSCopying协议
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *p = [[self.class allocWithZone:zone] init];
return p;
}
@end
@implementation Man
- (id)copyWithZone:(NSZone *)zone {
// person没有实现copyWithZone方法
//Man *m = [[self.class allocWithZone:zone] init];
// person实现了copyWithZone方法
Man *m = [super copyWithZone:zone];
return m;
}
@end
/*
为什么用self.class?
答:为了保证创建正确的对象,父类调用就创建父类对象,子类调用就创建子类对象
*/
2,初始化方法
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
// 无自定义初始化方法
Person *p = [[self.class allocWithZone:zone] init];
// 有自定义初始化方法
Man *p = [[self.class allocWithZone:zone] initWithName:self.name];
return p;
}
3,属性赋值
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *p = [[self.class allocWithZone:zone] init];
// 基本数据类型
p.age = self.age;
// 对象类型
p.name = [self.name copyWithZone:zone];
// 私有属性
p -> _height = _height;
return p;
}
@end
-
NSMutableCopying
:跟NSCopying
的主要区别在于返回的对象是否是可变的
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(NSZone *)zone;
@end
@implementation Person
- (id)mutableCopyWithZone:(NSZone *)zone {
Person *p = [[self.class allocWithZone:zone] init];
p.name = [self.name mutableCopyWithZone:zone];
return p;
}
@end
-
NSCoding
:如果想用归档存储自定义类的对象,那么该类必须遵守此协议并实现协议方法
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
@end
// Person
@interface Person : NSObject <NSCoding>
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (void)encodeWithCoder:(NSCoder *)aCoder { // 实现协议方法
[aCoder encodeObject:self.name forKey:@"name"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder { // 实现协议方法
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
@end
// 存储Person对象
- (void)viewDidLoad {
[super viewDidLoad];
[self save];
[self read];
}
- (NSString *)filePath {
NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
return [document stringByAppendingPathComponent:@"person.data"];
}
- (void)save {
Person *p = [Person new];
p.name = @"zhangsan";
[NSKeyedArchiver archiveRootObject:p toFile:self.filePath];
}
- (void)read {
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:self.filePath];
NSLog(@"%@", p.name);
}
// 打印结果
zhangsan
-
NSSecureCoding
:在NSCoding
基础上增加了安全性
@protocol NSSecureCoding <NSCoding>
@property (class, readonly) BOOL supportsSecureCoding;
@end
@interface Person : NSObject <NSSecureCoding>
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
+ (BOOL)supportsSecureCoding { // 对编码解码进行加密
return YES;
}
- (void)encodeWithCoder:(NSCoder *)aCoder { // 编码
[aCoder encodeObject:self.name forKey:@"name"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder { // 解码
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
@end
六,底层实现
- protocol的内部结构
struct protocol_t : objc_object {
const char *mangledName; // 重整的名称
const char *_demangledName; // 没有重整的名称
struct protocol_list_t *protocols; // 遵守的协议
method_list_t *classMethods; // 类方法
method_list_t *optionalClassMethods; // 可选的类方法
method_list_t *instanceMethods; // 实例方法
method_list_t *optionalInstanceMethods; // 可选的实例方法
property_list_t *_classProperties; // 类属性(用class修饰的)
property_list_t *instanceProperties; // 实例属性
...
}
-
conformsToProtocol
方法的内部实现
1,类方法和实例方法调用的都是class_conformsToProtocol
方法
+ (BOOL)conformsToProtocol:(Protocol *)protocol {
if (!protocol) return NO;
for (Class tcls = self; tcls; tcls = tcls->superclass) {
if (class_conformsToProtocol(tcls, protocol)) return YES;
}
return NO;
}
- (BOOL)conformsToProtocol:(Protocol *)protocol {
if (!protocol) return NO;
for (Class tcls = self.class; tcls; tcls = tcls->superclass) {
if (class_conformsToProtocol(tcls, protocol)) return YES;
}
return NO;
}
2,从class中取出protocols(类遵守的所有协议),然后逐个跟传入的protocol进行比较,判断是否相等或者调用protocol_conformsToProtocol_nolock
方法
BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) {
protocol_t *proto = newprotocol(proto_gen);
if (!cls) return NO;
if (!proto_gen) return NO;
rwlock_reader_t lock(runtimeLock);
assert(cls->isRealized());
for (const auto& proto_ref : cls->data()->protocols) {
protocol_t *p = remapProtocol(proto_ref);
if (p == proto || protocol_conformsToProtocol_nolock(p, proto)) {
return YES;
}
}
return NO;
}
3,从当前协议中取出protocols(当前协议遵守的所有协议),然后逐个跟传入的protocol进行比较,判断mangledName
是否相等,如果不相等就递归调用自身继续比较
static bool protocol_conformsToProtocol_nolock(protocol_t *self, protocol_t *other) {
runtimeLock.assertLocked();
if (!self || !other) {
return NO;
}
if (0 == strcmp(self->mangledName, other->mangledName)) {
return YES;
}
if (self->protocols) {
uintptr_t i;
for (i = 0; i < self->protocols->count; i++) {
protocol_t *proto = remapProtocol(self->protocols->list[i]);
if (0 == strcmp(other->mangledName, proto->mangledName)) {
return YES;
}
if (protocol_conformsToProtocol_nolock(proto, other)) {
return YES;
}
}
}
return NO;
}