基于Runtime的三方框架NullSafe

Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的。
执行一个方法时如果系统找不到方法会给几次机会寻找方法,实在没有此方法就会抛出异常。

运行时查找函数的步骤

由图可见

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

这两个函数是最后一个寻找IML的机会。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行。

源码解读


#ifndef NULLSAFE_ENABLED
#define NULLSAFE_ENABLED 1
#endif

// 忽略warning
// 三木运算符忽略中间一木导致的警告
#pragma GCC diagnostic ignored "-Wgnu-conditional-omitted-operand"

关闭警告

// 调用methodSignatureForSelector 方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    // 保持原子性,添加同步锁,防止被修改
    @synchronized([self class])
    {
     }
}

 // 本类父类种寻找是否拥有此方法,拥有方法则直接返回 signature
        NSMethodSignature *signature = [super methodSignatureForSelector:selector];
        if (!signature)
        {
        }
        return signature;
// 本类父类种寻找是否拥有此方法
        NSMethodSignature *signature = [super methodSignatureForSelector:selector];
        if (!signature)
        {
            // 方法列表
            static NSMutableSet *classList = nil;
            // 缓存方法字典
            static NSMutableDictionary *signatureCache = nil;
            if (signatureCache == nil)
            {
                classList = [[NSMutableSet alloc] init];
                signatureCache = [[NSMutableDictionary alloc] init];
                
                //get class list
                
                /*
                 分析:该函数的作用是获取已经注册的类,它需要传入两个参数,第一个参数 buffer :已分配好内存空间的数组,第二个参数 bufferCount :数组中可存放元素的个数,返回值是注册的类的总数。
                 当参数 bufferCount 值小于注册的类的总数时,获取到的是注册类的集合的任意子集
                 第一个参数传 NULL 时将会获取到当前注册的所有的类,此时可存放元素的个数为0,因此第二个参数可传0,返回值为当前注册的所有类的总数。
                 */
                 // 获取项目中所有类的个数
                int numClasses = objc_getClassList(NULL, 0);
                
                // 调整一个classes的大小   =   获取一个class的 size  * 所有的class
                Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
                
                // 获取项目中class的个数
                numClasses = objc_getClassList(classes, numClasses);
                
                // 初始化被排除的 NSMutableSet
                NSMutableSet *excluded = [NSMutableSet set];
                
                for (int i = 0; i < numClasses; i++)
                {
                    // 判断classes【i】 是否有superclass
                    Class someClass = classes[i];
                    Class superclass = class_getSuperclass(someClass);

                    // 循环找出 someClass 的所有的superclass
                    while (superclass)
                    {
                        // 当superclass存在  判断是否等于 NSObject
                        if (superclass == [NSObject class])
                        {
                            // 等于 NSObject 加入 classList
                            [classList addObject:someClass];
                            break;
                        }
                        [excluded addObject:NSStringFromClass(superclass)];
                        superclass = class_getSuperclass(superclass);
                    }
                }

                // 上面循环走完之后 查找到所有继承自NSObject的类
                
                // 基于 NSObject 的类 中 删除 不基于 NSObject 类
                for (Class someClass in excluded)
                {
                    [classList removeObject:someClass];
                }

                // 释放上面创建的 classes
                free(classes);
            }

经过上面代码获取项目中的类的列表和缓存

  
            // check implementation cache first
            NSString *selectorString = NSStringFromSelector(selector);
            signature = signatureCache[selectorString];
            if (!signature)
            {
                for (Class someClass in classList)
                {
                    // 判断 这个基于NSObject类的子类是否能够响应传入的方法
                    if ([someClass instancesRespondToSelector:selector])
                    {
                        // someClass类能够响应selector方法
                        // 返回NSMethodSignature对象,这个对象包含被标示的实例方法的描述。
                        signature = [someClass instanceMethodSignatureForSelector:selector];
                        break;
                    }
                }
                
                // cache for next time
                signatureCache[selectorString] = signature ?: [NSNull null];
            }
            else if ([signature isKindOfClass:[NSNull class]])
            {
                // 缓存是NSNull类型的话 将需要执行的方法置为nil
                signature = nil;
            }
// forwardInvocation:将选择器转发给一个真正实现了该消息的对象。
- (void)forwardInvocation:(NSInvocation *)invocation
{
    // 将target = nil ,不发送
    invocation.target = nil;
    [invocation invoke];
}

总结:
当我们给一个NSNull对象发送消息的话,可能会崩溃(null是有内存的),而发送给nil的话,是不会崩溃的。

作者就是使用了这么一个原理,把发送给NSNull的而NSNull又无法处理的消息经过如下几步处理:

  1. 创建缓存,缓存项目中类的所有类名。
  2. 遍历缓存,寻找是否已经有可以执行此方法的类。
  3. 如果有的话,返回这个NSMethodSignature。
  4. 如果没有的话,返回nil,崩溃
  5. 如果有的话,[invocation invokeWithTarget:nil];将消息转发给nil。

那么,如何判断NSNull无法处理这个消息呢,在OC中,系统如果对某个实例发送消息之后,它(及其父类)无法处理(比如,没有这个方法等),系统就会发送methodSignatureForSelector消息,如果这个方法返回非空,那么就去执行返回的方法,如果为nil,则发送forwardInvocation消息。

这样就完成整个转发链了。

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,672评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,525评论 33 466
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,502评论 18 139
  • 1.理解NSObject和元类 1.1 在OC中的对象和类是什么 对象是在objc.h中定义的 类是在runtim...
    HWenj阅读 914评论 0 3
  • iOS提供了使用其他app预览文件的支持,这就是Document Interaction Controller。此...
    空白Null阅读 4,533评论 2 6