之前已經(jīng)講過方法加載的全過程,protocol的加載過程與method是一樣的,就不再贅述了。不清楚的可以參考Runtime源碼 —— 方法加載的過程。
那么這篇說些啥呢?
- protocol在runtime層的表示
- 舉例驗證
- 分析和protocol相關(guān)的常用方法的源代碼
總體來說protocol相關(guān)的內(nèi)容還是比較簡單的,也可能是因為前面分析method的時候打好了基礎(chǔ)。
protocol在runtime層的表示
protocol在runtime層表示為protocol_t,這個結(jié)構(gòu)體是這樣的:
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
...
}
所以protocol本質(zhì)上也是一個對象,結(jié)構(gòu)體的參數(shù)還是挺符合直覺的,一個個說一下。
mangledName和_demangledName
這個東西來源于c++的name mangling(命名重整)技術(shù),在c++里面是用來區(qū)別重載時的函數(shù)。instanceMethods和optionalInstanceMethods
對應(yīng)的是實例方法,可選實例方法,可選就是寫在@optional之后的方法。classMethods和optionalClassMethods
與上面對應(yīng),分別是類方法,可選類方法instanceProperties
實例屬性。奇怪的是這里為什么不區(qū)分必須還是可選?_classProperties
類屬性。挺少見的,舉個例子:
// 這是常見的屬性聲明,也就是對象屬性
@property (nonatomic, assign) NSInteger count;
// 這是類屬性,與類方法一樣,通過類名調(diào)用
@property (class, nonatomic, copy) NSString *name;
- protocols
此協(xié)議遵循的協(xié)議
結(jié)構(gòu)體本身并不復(fù)雜,也沒有什么嵌套的數(shù)據(jù)結(jié)構(gòu)。
例子
為了驗證protocol的結(jié)構(gòu),創(chuàng)建一個OS X的項目,添加如下的類:
// ZNObject.h
#import <Foundation/Foundation.h>
@protocol ZNProtocol <NSObject>
- (void)protocolMethod;
@end
@protocol ZNProtocolLow <ZNProtocol, NSObject>
- (void)protocolMethodLow;
@end
@interface ZNObject : NSObject
@property (nonatomic, weak) id<ZNProtocolLow> delegate;
- (void)hello;
@end
// ZNObject.m
#import "ZNObject.h"
@implementation ZNObject
- (void)hello {
[self.delegate protocolMethod];
[self.delegate protocolMethodLow];
}
@end
修改ViewController方法中的viewDidLoad(),其他方法略去:
@interface ViewController() <ZNProtocolLow>
@end
- (void)viewDidLoad {
[super viewDidLoad];
ZNObject *znObject = [ZNObject new];
NSLog(@"%p", [ViewController class]);
(*) znObject.delegate = self;
[znObject hello];
}
在有(*)標(biāo)識的地方添加斷點,運行程序進入斷點,這個時候通過lldb進行操作:
// ViewController在內(nèi)存中的地址
2017-02-17 16:13:29.436610 TestOSX[37980:1706893] 0x100003090
// 如果這一步看不懂,請參考本文最開始給出的方法加載的那篇文章
(lldb) p (class_data_bits_t *)0x1000030b0
(class_data_bits_t *) $0 = 0x00000001000030b0
(lldb) p $0->data()
(class_rw_t *) $1 = 0x00006080000753c0
// 獲取ViewController遵循的protocol
(lldb) p $1->protocols
(protocol_array_t) $2 = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x00000001000025a0
arrayAndFlag = 4294976928
}
}
}
(lldb) p $2.beginLists()
(protocol_list_t **) $3 = 0x00006080000753e0
(lldb) p **$3
(protocol_list_t) $4 = (count = 1, list = protocol_ref_t [] @ 0x00007f7f52d301d8)
(lldb) p $4.list[0]
(protocol_ref_t) $5 = 4294980080
(lldb) p (protocol_t *)$5
(protocol_t *) $6 = 0x00000001000031f0
// 打印協(xié)議的名字,可以看到就是ZNProtocolLow
// 打印protocol_t的全部內(nèi)容,可以看到所屬類是Protocol
(lldb) p *$6
(protocol_t) $7 = {
objc_object = {
isa = {
cls = Protocol
bits = 4299890208
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 537486276
magic = 0
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x0000000100001ab5 "ZNProtocolLow"
protocols = 0x0000000100002558
instanceMethods = 0x0000000100002578
classMethods = 0x0000000000000000
optionalInstanceMethods = 0x0000000000000000
optionalClassMethods = 0x0000000000000000
instanceProperties = 0x0000000000000000
size = 96
flags = 0
_extendedMethodTypes = 0x0000000100002598
_demangledName = 0x0000000000000000 <no value available>
_classProperties = 0x0000000000000000
}
// 獲取實例方法
(lldb) p $7.instanceMethods
(method_list_t *) $8 = 0x0000000100002578
// 打印方法,結(jié)果與定義在ZNProtocolLow中的方法相同
(lldb) p $8->get(0)
(method_t) $9 = {
name = "protocolMethodLow"
types = 0x0000000100001af9 "v16@0:8"
imp = 0x0000000000000000
}
// 獲取此協(xié)議遵循的協(xié)議
(lldb) p $8.protocols
(protocol_list_t *) $9 = 0x0000000100002558
(lldb) p *$9
(protocol_list_t) $10 = (count = 2, list = protocol_ref_t [] @ 0x00007f7f552f9878)
// 打印出名字,與ZNProtocolLow的定義是符合的
(lldb) p ((protocol_t *)($10.list[0]))->mangledName
(const char *) $25 = 0x0000000100001ac3 "ZNProtocol"
(lldb) p ((protocol_t *)($11.list[1]))->mangledName
(const char *) $26 = 0x0000000100001ace "NSObject"
驗證的結(jié)果和預(yù)期的一樣,都還是比較簡單的。
常用方法的源代碼
先來看一下在分析protocol_t結(jié)構(gòu)體時的一個疑問,就是屬性為什么不區(qū)分是不是可選的?
- protocol_copyPropertyList()
runtime給我們提供了兩個方法
objc_property_t *
protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
{
return protocol_copyPropertyList2(proto, outCount,
YES/*required*/, YES/*instance*/);
}
// 第一個方法啥事也沒干,就調(diào)用了第二個方法
objc_property_t *
protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount,
BOOL isRequiredProperty, BOOL isInstanceProperty)
{
if (!proto || !isRequiredProperty) {
// Optional properties are not currently supported.
if (outCount) *outCount = 0;
return nil;
}
rwlock_reader_t lock(runtimeLock);
property_list_t *plist = isInstanceProperty
? newprotocol(proto)->instanceProperties
: newprotocol(proto)->classProperties();
return (objc_property_t *)copyPropertyList(plist, outCount);
}
這里就奇怪了,看到那行注釋了嗎,可選屬性現(xiàn)在還不支持,但是在NSObject協(xié)議中明明白白的有這么一段:
@optional
@property (readonly, copy) NSString *debugDescription;
寫個例子測試了一下,即使在@property之前加一個@optional,在獲取的時候還是會當(dāng)做@required,也就是@optional對其后的屬性是不起作用的。
- 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;
}
對類或者對象調(diào)用此方法,作用是一樣的,都是取class進行處理:
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;
}
實現(xiàn)很簡單,把class的protocols取出來,并與傳入的protocol做比較,如果地址相同直接返回,或者協(xié)議"繼承"的層級中滿足條件:
static bool
protocol_conformsToProtocol_nolock(protocol_t *self, protocol_t *other)
{
runtimeLock.assertLocked();
if (!self || !other) {
return NO;
}
// protocols need not be fixed up
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;
}
遞歸處理,對比協(xié)議的mangledName,有相同的就返回YES。這個方法總體流程還是很中規(guī)中矩的。
protocol的方法還有不少,這里就不羅列了,感興趣的自己翻出源碼看一看吧。
總結(jié)
本來還想寫一寫protocol方法的調(diào)用流程,因為也很符合直觀理解,就不細說了,說白了對protocol方法的調(diào)用最終都會轉(zhuǎn)換成遵循該協(xié)議的類對方法的調(diào)用。
protocol總體還是很簡單的。下一篇準(zhǔn)備看一看property和iVar。