Runtime源碼 —— 協(xié)議protocol

之前已經(jīng)講過方法加載的全過程,protocol的加載過程與method是一樣的,就不再贅述了。不清楚的可以參考Runtime源碼 —— 方法加載的過程

那么這篇說些啥呢?

  1. protocol在runtime層的表示
  2. 舉例驗證
  3. 分析和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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,775評論 0 9
  • Objective-C語言是一門動態(tài)語言,他將很多靜態(tài)語言在編譯和鏈接時期做的事情放到了運行時來處理。這種動態(tài)語言...
    tigger丨閱讀 1,432評論 0 8
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,232評論 0 7
  • 一、前言 OC是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理。即說明OC需要一個編譯器和...
    雨潤聽潮閱讀 733評論 1 0
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 769評論 0 2