Objective-C物件模型及應用

來源:互聯網
上載者:User

標籤:

本文主要介紹Objective-C物件模型的實現細節,以及Objective-C語言物件模型中對isa swizzling和method swizzling的支援。希望本文能加深你對Objective-C對象的理解。

ISA指標

Objective-C是一門物件導向的程式設計語言。每一個對象都是一個類的執行個體。在Objective-C語言的內部,每一個對象都有一個名為isa的指標,指向該對象的類。每一個類描述了一系列它的執行個體的特點,包括成員變數的列表,成員函數的列表等。每一個對象都可以接受訊息,而對象能夠接收的訊息列表是儲存在它所對應的類中。

在XCode中按Shift + Command + O, 然後輸入NSObject.h和objc.h,可以開啟NSObject的定義標頭檔,通過標頭檔我們可以看到,NSObject就是一個包含isa指標的結構體,如所示:

 

按照物件導向語言的設計原則,所有事物都應該是對象(嚴格來說Objective-C並沒有完全做到這一點,因為它有象int, double這樣的簡單變數類型)。在Objective-C語言中,每一個類實際上也是一個對象。每一個類也有一個名為isa的指標。每一個類也可以接受訊息,例如[NSObject alloc],就是向NSObject這個類發送名為alloc訊息。

在XCode中按Shift + Command + O, 然後輸入runtime.h,可以開啟Class的定義標頭檔,通過標頭檔我們可以看到,Class也是一個包含isa指標的結構體,如所示。(圖中除了isa外還有其它成員變數,但那是為了相容非2.0版的Objective-C的遺留邏輯,大家可以忽略它。)

因為類也是一個對象,那它也必須是另一個類的實列,這個類就是元類(metaclass)。元類儲存了類方法的列表。當一個類方法被調用時,元類會首先尋找它本身是否有該類方法的實現,如果沒有,則該元類會向它的父類尋找該方法,直到一直找到繼承鏈的頭。

元類(metaclass)也是一個對象,那麼元類的isa指標又指向哪裡呢?為了設計上的完整,所有的元類的isa指標都會指向一個根元類(root metaclass)。根元類(root metaclass)本身的isa指標指向自己,這樣就行成了一個閉環。上面提到,一個對象能夠接收的訊息列表是儲存在它所對應的類中的。在實際編程中,我們幾乎不會遇到向元類發訊息的情況,那它的isa指標在實際上很少用到。不過這麼設計保證了物件導向的乾淨,即所有事物都是對象,都有isa指標。

我們再來看看繼承關係,由於類方法的定義是儲存在元類(metaclass)中,而方法調用的規則是,如果該類沒有一個方法的實現,則向它的父類繼續尋找。所以,為了保證父類的類方法可以在子類中可以被調用,所以子類的元類會繼承父類的元類,換而言之,類對象和元類對象有著同樣的繼承關係。

我很想把關係說清楚一些,但是這塊兒確實有點繞,下面這張圖或許能夠讓大家對isa和繼承的關係清楚一些

該圖中,最讓人困惑的莫過於Root Class了。在實現中,Root Class是指NSObject,我們可以看出:

  1. NSObject類包括它的對象執行個體方法。
  2. NSObject的元類包括它的類方法,例如alloc方法。
  3. NSObject的元類繼承自NSObject類。
  4. 一個NSObject的類中的方法同時也會被NSObject的子類在尋找方法時找到。
類的成員變數

如果把類的執行個體看成一個C語言的結構體(struct),上面說的isa指標就是這個結構體的第一個成員變數,而類的其它成員變數依次排列在結構體中。排列順序如所示(圖片來自《iOS 6 Programming Pushing the Limits》):

為了驗證該說法,我們在XCode中建立一個工程,在main.m中運行如下代碼:

 

#import <UIKit/UIKit.h>@interface Father : NSObject {    int _father;}@end@implementation Father@end@interface Child : Father {    int _child;}@end@implementation Child@endint main(int argc, char * argv[]){  Child * child = [[Child alloc] init];  @autoreleasepool {      // ...  }}

 

我們將斷點下在 @autoreleasepool 處,然後在Console中輸入p *child,則可以看到Xcode輸出如下內容,這與我們上面的說法一致。

 

(lldb) p *child(Child) $0 = {  (Father) Father = {    (NSObject) NSObject = {      (Class) isa = Child    }    (int) _father = 0  }  (int) _child = 0}
可變與不可變

因為對象在記憶體中的排布可以看成一個結構體,該結構體的大小並不能動態變化。所以無法在運行時動態給對象增加成員變數。

相對的,對象的方法定義都儲存在類的可變地區中。Objective-C 2.0並未在標頭檔中將實現暴露出來,但在Objective-C 1.0中,我們可以看到方法的定義列表是一個名為 methodLists的指標的指標(如所示)。通過修改該指標指向的指標的值,就可以實現動態地為某一個類增加成員方法。這也是Category實現的原理。同時也說明了為什麼Category只可為對象增加成員方法,卻不能增加成員變數。

需要特別說明一下,通過objc_setAssociatedObject 和 objc_getAssociatedObject方法可以變相地給對象增加成員變數,但由於實現機制不一樣,所以並不是真正改變了對象的記憶體結構。

除了對象的方法可以動態修改,因為isa本身也只是一個指標,所以我們也可以在運行時動態地修改isa指標的值,達到替換對象整個行為的目的。不過該應用情境較少。

系統相關API及應用isa swizzling的應用

系統提供的KVO的實現,就利用了動態地修改isa指標的值的技術。在蘋果的文檔中可以看到如下描述:

Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique called isa-swizzling.

The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

Method Swizzling API說明

Objective-C提供了以下API來動態替換類方法或執行個體方法的實現:

  • class_replaceMethod 替換類方法的定義
  • method_exchangeImplementations 交換2個方法的實現
  • method_setImplementation 設定1個方法的實現

這3個方法有一些細微的差別,給大家介紹如下:

  • class_replaceMethod在蘋果的文檔(如所示)中能看到,它有兩種不同的行為。當類中沒有想替換的原方法時,該方法會調用class_addMethod來為該類增加一個新方法,也因為如此,class_replaceMethod在調用時需要傳入types參數,而method_exchangeImplementations和method_setImplementation卻不需要。

  • method_exchangeImplementations 的內部實現相當於調用了2次method_setImplementation方法,從蘋果的文檔中能清晰地瞭解到(如所示)

從以上的區別我們可以總結出這3個API的使用情境:

  • class_replaceMethod, 當需要替換的方法可能有不存在的情況時,可以考慮使用該方法。
  • method_exchangeImplementations,當需要交換2個方法的實現時使用。
  • method_setImplementation 最簡單的用法,當僅僅需要為一個方法設定其實現方式時使用。

以上3個方法的源碼在這裡,感興趣的同學可以讀一讀。

使用樣本

我們在開發用戶端的筆記功能時,需要使用系統的UIImagePickerController。但是,我們發現,在iOS6.0.2系統下,系統提供的UIImagePickerController在iPad橫屏下有轉屏的Bug,造成其方向錯誤。具體的Bug詳情可以見這裡。

為了修複該Bug,我們需要替換UIImagePickerController的如下2個方法

 

- (BOOL)shouldAutorotate;- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;

 

我們先實現了一個名為ImagePickerReplaceMethodsHolder的類,用於定義替換後的方法和實現。如下所示:

 

// ImagePickerReplaceMethodsHolder.h@interface ImagePickerReplaceMethodsHolder : NSObject- (BOOL)shouldAutorotate;- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;@end// ImagePickerReplaceMethodsHolder.m@implementation ImagePickerReplaceMethodsHolder- (BOOL)shouldAutorotate {    return NO;}- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {    return UIInterfaceOrientationPortrait;}@end
開源界的使用

有少量不明真相的同學以為蘋果在審核時會拒絕App使用以上API,這其實是對蘋果的誤解。使用如上API是安全的。另外,開源界也對以上方法都適當的使用。例如:

  • 著名的網路程式庫AFNetworking。AFNetworking網路程式庫(v1.x版本)使用了class_replaceMethod方法(AFHTTPRequestOperation.m檔案第105行)
  • Nimbus。Nimbus是著名的工具類庫,它在其core模組中提供了NIRuntimeClassModifications.h檔案,用於提供上述API的封裝。
  • 國內的福士點評iOS用戶端。該用戶端使用了他們自己開發的基於Wax修改而來的WaxPatch,WaxPatch可以實現通過伺服器更新來動態修改用戶端的邏輯。而WaxPatch主要是修改了wax中的wax_instance.m檔案,在其中加入了class_replaceMethod來替換原始實現,從而實現修改用戶端的原有行為。
總結

通過本文,我們瞭解到了Objective-C語言的物件模型,以及Objective-C語言物件模型中對isa swizzling和method swizzling的支援。本文也通過具體的執行個體代碼和開源項目,讓我們對該物件模型提供的動態性有了更加深刻的認識。

Objective-C物件模型及應用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.