zoukankan      html  css  js  c++  java
  • NullSafe基于Runtime的深度解析

    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消息。

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

  • 相关阅读:
    单点登录学习的教程
    单点登录
    Linux下VI的使用
    伪分布式下的hadoop简单配置
    Linux下配置Java环境变量
    spring mvc 重新定向到一个新的Url
    LeetCode --- 字符串系列 ---“气球” 的最大数量
    http 简述
    dpr 与 移动端 1px 问题
    rem 与 vm 布局
  • 原文地址:https://www.cnblogs.com/weiming4219/p/7928094.html
Copyright © 2011-2022 走看看