你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS進階(二)Objective

iOS進階(二)Objective

編輯:IOS開發基礎

這篇讀書筆記主要介紹了Objective-C底層的一些東西,比如Objective-C對象模型、objc_msgSend消息發送原理、方法混寫(Method Swizzling)和ISA混寫(ISA Swizzling)。

Objective-C對象模型

我們都知道Objective-C是一門動態性語言,這種動態性的核心是objc提供的Objective-C運行時,比如objc_msgSend就是一個核心函數,每次使用[object message]語法都會調用它。我們先來了解下Objective-C對象模型。

Objective-C是一門面向對象的編程語言,每一個對象都是一個類的實例,在Objective-C中,每一個對象都有一個名為isa的指針,指向該對象的類。每一個類描述了一系列它的實例的特點,包括成員變量的列表、成員函數的列表等。每一個對象都可以接受到消息,而對象能夠接受到的消息列表保存在它所對應的類中。

注意:

  • 每一個對象都有一個isa指針,這個指針指向的是它的類。

  • 類中包括成員變量、成員函數列表等。

在Xcode中打開objc.h文件,會看到如下代碼:

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

通過注釋我們看到objc_object代表一個對象的實例,在對象實例中我們看到了isa指針,驗證了我們剛才說的話。

根據面向對象的設計原則,所有事物都應該是對象,所以在Objective-C中,每一個類實際上也是一個對象,每一個類也有一個名為isa的指針,每一個類也可以接收消息,例如代碼[NSObject alloc],就是向NSObject這個類發送名為alloc的消息。

在Xcode中打開runtime.h文件,會看到如下代碼:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
22
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
22
} OBJC2_UNAVAILABLE;

objc_class代表一個類,從上面代碼中可以看出類中有一個isa指針的。前面說到isa指針會執行它的類,那類中的isa指針指向什麼呢?因為類也是一個對象,所以它也必須是另一個類的實例,這個類就是元類(metaclass),所以isa指針指向的是它的元類。元類保存了類的方法列表。當一個類的方法被調用時,元類會首先查找它本身是否有該類方法的實現,如果沒有,則該元類會向它的父類查找該方法,這樣可以一直找到繼承鏈的頭。

如前面所說元類也是一個對象,那麼元類的isa指針指向誰呢?Objective-C為了設計上的完整,所有的元類的isa指針都會指向一個根元類(root metaclass),根元類的isa指針指向自己,這樣就形成一個閉環。上面說到,一個對象能夠接收的消息列表是保存在它所對應的類中的。在實際編程中,我們幾乎不會遇到向元類發消息的情況,那它的isa指針在實際上很少用到。

再來看看繼承關系,由於類方法的定義是保存在元類中,而方法調用的規則是,如果該類沒有一個方法的實現,則向它的父類繼續查找。所以,為了保證父類的類方法在子類中可以被調用,所有子類的元類都會繼承父類的元類,簡單來說就是類對象和元類對象有著同樣的繼承關系。

最後用一張圖對對象模型做一個總結,如下圖:

416556-8c8e3a4e9b7ab21d.png

1-1 對象模型.png

objc_msgSend

Objective-C運行時的核心就在於消息分派器objc_msgSend,消息分派器把選擇器映射為函數指針,並調用被引用的函數。 要想理解objc_msgSend的背後原理,先來理解下NSInvocation這個類。

NSInvocation是命令模式的一種傳統實現,它把一個目標、一個選擇器、一個方法簽名和所有的參數都塞進一個對象裡,這個對象可以先存儲起來,以備將來調用。當NSInvocation被調用時,它會發送信息,Objective-C運行時會找到正確的方法實現來執行。我們通過一個例子來理解下NSInvocation的作用,比如[NSObject alloc],此時會發送一個alloc消息,這條消息都包含什麼內容呢?它怎麼找到alloc的實現方法呢?這些都是通過NSInvocation來完成的,它包含了消息要傳遞的內容,也告訴了該怎麼找到對應的方法實現

解釋一下什麼是方法實現?一個方法實現(IMP)是一個指向具有如下簽名的C函數的函數指針,注意是指針

id function(id self, SEL _cmd, ...)

NSInvocation包含了一個目標和選擇器,目標是一個可接受的對象,選擇器則是被發送的消息。比如[NSObject alloc],目標就是NSObject,選擇器就是alloc。一個選擇器大致是一個方法的名稱,之所以說是大致是因為選擇器不必精確映射到方法。比如[NSString length]和[NSData length]會映射到不同方法的實現,但他們擁有相同的選擇器。

NSInvocation還包含一個方法簽名(NSMethodSignature),它封裝了一個方法的返回類型和參數類型,記住它不包括方法名稱,只有返回類型和參數類型。你可以手動創建一個方法簽名,如下:

NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"@@:*"];

但是應該盡可能少使用signatureWithObjCTypes:方法,獲得方法簽名常用的方法是為它請求一個類或實例,比如可以使用methodSignatureForSelector:方法從實例中請求實例方法簽名,或者從類中請求類方法簽名。也可以使用instanceMethodSignatureForSelector:方法從一個類中獲取實例方法簽名。兩個方法有點繞口,我們通過一個例子來看下區別:

SEL initSEL = @selector(init);
SEL allocSEL = @selector(alloc);

// 從NSString類中獲取實例方法(init)的方法簽名
NSMethodSignature *initSig = [NSString instanceMethodSignatureForSelector:initSEL];
// 從test實例中獲取實例方法(init)的方法簽名
initSig = [@"test" methodSignatureForSelector:initSEL];
// 從NSString類中獲取類方法簽名
NSMethodSignature *allocSig = [NSString methodSignatureForSelector:allocSEL];

最後,NSInvocation還包含了所有的參數。至此,對於[NSString length]和[NSData length]就可以通過NSInvocation對象包含的信息,找到它們分別對應的方法實現。我們來看一個具體的例子,如下:

NSMutableSet *set = [NSMutableSet set];
NSString *stuff = @"stuff";
SEL selector = @selector(addObject:);
NSMethodSignature *sig = [set methodSignatureForSelector:selector];
44
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setTarget:set];
[invocation setSelector:selector];
[invocation setArgument:&stuff atIndex:2];
[invocation invoke];
44
NSLog(@"set is : %@", set);

注意,第一個參數被置於索引2處,索引0是目標(self),索引1是選擇器(_cmd),NSInvocation會自動設置它們。另外,必須把參數指針傳遞給參數,而不能傳遞參數本身。

接下來重點要介紹下消息傳遞是如何工作的?

在Objective-C中調用方法最終會翻譯成調用方法實現的函數指針,並傳遞給這個方法實現一個對象指針、一個選擇器和一組函數參數。每個Objective-C消息表達式都會轉化為對objc_msgSend的調用,看下objc_msgSend的工作方式:

1. 檢查接受對象是否為nil,如果是nil,調用nil處理程序。

2. 檢查緩存中是不是已經有方法實現了,有的話,直接調用。

3. 比較請求的選擇器和類中定義的選擇器,如果找到了,調用方法實現。

4.比較請求的選擇器和父類中定義的選擇器,然後是父類的父類,以此類推,如果找到了選擇器,調用方法實現。

5. 調用resolveInstanceMethod:(或resolveClassMethod)。如果它返回YES,那麼重新開始。這一次對象會響應這個選擇器,一般是因為它已經調用過class_addMethod。

6. 調用forwardingTargetForSelector:,如果返回非nil,那就把消息發送到返回的對象上,這裡不要返回self,否則會形成死循環的。

7.調用methodSignatureForSelector:,如果返回非nil,創建一個NSInvocation並傳給forwardInvocation:。

8. 調用doesNotRecognizeSelector:,默認的實現是拋出異常。

先看下第5步,首先可以想到的就是用resolveInstanceMethod:和resolveClassMethod:在運行時提供實現,這通常是@dynamic合成屬性的處理方式。簡單來說,就是需要自己實現屬性的getter和setter方法,通過resolveInstanceMethod:方法來把setter方法和getter方法和屬性綁定在一起。

如果第5步返回NO的話,系統接著會首先嘗試一次快速轉發,也就是調用forwardingTargetForSelector:,看其能否返回一個對象,如果有對象返回,就轉發給返回的對象。快速轉發的原理其實就是先從緩存裡找下是否存在對應的選擇器。

如果快速轉發返回nil的話,接下來就進行普通的轉發,調用forwardInvocation進行普通的轉發。

objc_msgSend還有幾個相關的函數:objc_msgSend_fpret、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret。SendSuper格式的函數很明顯是把消息發送給父類,而帶stret的在返回結構體時處理大部分情況。在Intel處理器上返回浮點數時,帶fpret的函數處理大部分情況。

方法混寫(Method Swizzling)與ISA混寫(Isa Swizzling)

在Objective-C中,混寫(Swizzling)是指透明地把一個東西換成另一個,我們可以利用Objective-C中的運行時來實現混寫。我們先看下方法混寫,Objective-C提供了以下API來動態替換類方法或實例方法的實現:

  • class_replaceMethod替換類方法的定義。

  • method_exchangeImplementations交換兩個方法的實現。

  • method_setImplementation設置一個方法的實現。

我們來看下三者的區別:

  • class_replaceMethod,當需要替換的方法有可能不存在時,可以考慮使用該方法。

  • method_exchangeImplementations,當需要交換兩個方法的實現時使用。

  • method_setImplementation是最簡單的用法,當僅僅需要為一個方法設置其實現方式時使用。

系統中提供的KVO使用到了isa混寫,具體是怎麼實現的呢?當你觀察一個對象時,一個新的類會被自動創建,這個新類繼承自該對象的原本的類,並且重寫了被觀察屬性setter方法。重寫setter方法會負責在調用原setter方法之前和之後,通知所有觀察對象:值的更改。最後通過isa混寫,把這個對象的isa指針指向這個新創建的子類,對象就神奇的變成了新創建的子類的實例

注意一點:把isa指針指向新創建的子類,被觀察的對象就變成了新創建子類的對象實例,這是由於isa指針永遠指向其對應的類。用一張圖來說明下:

416556-e513fe41773fe332.png

1-2 KVO.png

鍵值觀察通知依賴於NSObject的兩個方法:willChangeValueForKey:和didChangeValueForKey:。在一個被觀察屬性發生改變之前,willChangeValueForKey:一定會被調用,這就會記錄舊的值。而當改變之後,didChangeValueForKey:會被調用,繼而obserValueForKey:ofObject:change:context:也會被調用。可以手動實現這些調用,但很少有人這麼做。一般我們希望能控制回調的調用時機時才會這麼做。

使用KVO的一個明顯的優勢就是零開銷觀察的優勢,如果給定的實例沒有觀察者,那麼KVO不會有任何消耗,因為根本沒有KVO代碼。而即使沒有觀察者,對於委托方法和NSNotification還得工作。

參考文章:

招聘一個靠譜的iOS開發者

唐巧 iOS開發進階書籍

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved