Runtime源码 —— 协议protocol

之前已经讲过方法加载的全过程,protocol的加载过程与method是一样的,就不再赘述了。不清楚的可以参考Runtime源码 —— 方法加载的过程

那么这篇说些啥呢?

  1. protocol在runtime层的表示
  2. 举例验证
  3. 分析和protocol相关的常用方法的源代码

总体来说protocol相关的内容还是比较简单的,也可能是因为前面分析method的时候打好了基础。

protocol在runtime层的表示

protocol在runtime层表示为protocol_t,这个结构体是这样的:

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本质上也是一个对象,结构体的参数还是挺符合直觉的,一个个说一下。

  • mangledName和_demangledName
    这个东西来源于c++的name mangling(命名重整)技术,在c++里面是用来区别重载时的函数。

  • instanceMethods和optionalInstanceMethods
    对应的是实例方法,可选实例方法,可选就是写在@optional之后的方法。

  • classMethods和optionalClassMethods
    与上面对应,分别是类方法,可选类方法

  • instanceProperties
    实例属性。奇怪的是这里为什么不区分必须还是可选?

  • _classProperties
    类属性。挺少见的,举个例子:

// 这是常见的属性声明,也就是对象属性
@property (nonatomic, assign) NSInteger count;
// 这是类属性,与类方法一样,通过类名调用
@property (class, nonatomic, copy) NSString *name;
  • protocols
    此协议遵循的协议

结构体本身并不复杂,也没有什么嵌套的数据结构。

例子

为了验证protocol的结构,创建一个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];
}

在有(*)标识的地方添加断点,运行程序进入断点,这个时候通过lldb进行操作:

// ViewController在内存中的地址
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
// 打印协议的名字,可以看到就是ZNProtocolLow
// 打印protocol_t的全部内容,可以看到所属类是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
// 打印方法,结果与定义在ZNProtocolLow中的方法相同
(lldb) p $8->get(0)
(method_t) $9 = {
  name = "protocolMethodLow"
  types = 0x0000000100001af9 "v16@0:8"
  imp = 0x0000000000000000
}
// 获取此协议遵循的协议
(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"

验证的结果和预期的一样,都还是比较简单的。

常用方法的源代码

先来看一下在分析protocol_t结构体时的一个疑问,就是属性为什么不区分是不是可选的?

  • protocol_copyPropertyList()
    runtime给我们提供了两个方法
objc_property_t *
protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
{
    return protocol_copyPropertyList2(proto, outCount, 
                                      YES/*required*/, YES/*instance*/);
}
// 第一个方法啥事也没干,就调用了第二个方法
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);
}

这里就奇怪了,看到那行注释了吗,可选属性现在还不支持,但是在NSObject协议中明明白白的有这么一段:

@optional
@property (readonly, copy) NSString *debugDescription;

写个例子测试了一下,即使在@property之前加一个@optional,在获取的时候还是会当做@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;
}

对类或者对象调用此方法,作用是一样的,都是取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;
}

实现很简单,把class的protocols取出来,并与传入的protocol做比较,如果地址相同直接返回,或者协议"继承"的层级中满足条件:

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;
}

递归处理,对比协议的mangledName,有相同的就返回YES。这个方法总体流程还是很中规中矩的。

protocol的方法还有不少,这里就不罗列了,感兴趣的自己翻出源码看一看吧。

总结

本来还想写一写protocol方法的调用流程,因为也很符合直观理解,就不细说了,说白了对protocol方法的调用最终都会转换成遵循该协议的类对方法的调用。

protocol总体还是很简单的。下一篇准备看一看property和iVar。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • Objective-C语言是一门动态语言,他将很多静态语言在编译和链接时期做的事情放到了运行时来处理。这种动态语言...
    tigger丨阅读 1,381评论 0 8
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,170评论 0 7
  • 一、前言 OC是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。即说明OC需要一个编译器和...
    雨润听潮阅读 694评论 1 0
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 727评论 0 2