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"

 

關閉警告spa

// 調用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的話,是不會崩潰的。code

做者就是使用了這麼一個原理,把發送給NSNull的而NSNull又沒法處理的消息通過以下幾步處理:對象

  1. 建立緩存,緩存項目中類的全部類名。
  2. 遍歷緩存,尋找是否已經有能夠執行此方法的類。
  3. 若是有的話,返回這個NSMethodSignature。
  4. 若是沒有的話,返回nil,崩潰
  5. 若是有的話,[invocation invokeWithTarget:nil];將消息轉發給nil。

那麼,如何判斷NSNull沒法處理這個消息呢,在OC中,系統若是對某個實例發送消息以後,它(及其父類)沒法處理(好比,沒有這個方法等),系統就會發送methodSignatureForSelector消息,若是這個方法返回非空,那麼就去執行返回的方法,若是爲nil,則發送forwardInvocation消息。blog

這樣就完成整個轉發鏈了。繼承

相關文章
相關標籤/搜索