Objective-C對象模型及應(yīng)用

Objective-C對象模型及應(yīng)用

發(fā)表于 2013-10-15 20:31

轉(zhuǎn)自http://blog.devtang.com/2013/10/15/objective-c-object-model/

文章目錄

1.前言

2.ISA 指針

3.類的成員變量

4.可變與不可變

5.系統(tǒng)相關(guān) API 及應(yīng)用

5.1.isa swizzling 的應(yīng)用

5.2.Method Swizzling API 說明

5.3.使用示例

5.4.開源界的使用

6.總結(jié)

7.后記

7.1.參考鏈接

前言

原創(chuàng)文章,轉(zhuǎn)載請注明出自唐巧的技術(shù)博客。

本文主要介紹 Objective-C 對象模型的實(shí)現(xiàn)細(xì)節(jié),以及 Objective-C 語言對象模型中對isa swizzling和method swizzling的支持。希望本文能加深你對 Objective-C 對象的理解。

ISA 指針

Objective-C

是一門面向?qū)ο蟮木幊陶Z言。每一個對象都是一個類的實(shí)例。在 Objective-C 語言的內(nèi)部,每一個對象都有一個名為 isa

的指針,指向該對象的類。每一個類描述了一系列它的實(shí)例的特點(diǎn),包括成員變量的列表,成員函數(shù)的列表等。每一個對象都可以接受消息,而對象能夠接收的消息列表是保存在它所對應(yīng)的類中。

在 XCode 中按Shift + Command + O, 然后輸入 NSObject.h 和 objc.h,可以打開 NSObject 的定義頭文件,通過頭文件我們可以看到,NSObject 就是一個包含 isa 指針的結(jié)構(gòu)體,如下圖所示:

按照面向?qū)ο笳Z言的設(shè)計原則,所有事物都應(yīng)該是對象(嚴(yán)格來說 Objective-C 并沒有完全做到這一點(diǎn),因?yàn)樗邢?int, double 這樣的簡單變量類型)。在 Objective-C 語言中,每一個類實(shí)際上也是一個對象。每一個類也有一個名為 isa 的指針。每一個類也可以接受消息,例如[NSObject alloc],就是向 NSObject 這個類發(fā)送名為alloc消息。

在 XCode 中按Shift + Command + O, 然后輸入 runtime.h,可以打開 Class 的定義頭文件,通過頭文件我們可以看到,Class 也是一個包含 isa 指針的結(jié)構(gòu)體,如下圖所示。(圖中除了 isa 外還有其它成員變量,但那是為了兼容非 2.0 版的 Objective-C 的遺留邏輯,大家可以忽略它。)

因?yàn)轭愐彩且粋€對象,那它也必須是另一個類的實(shí)列,這個類就是元類 (metaclass)。元類保存了類方法的列表。當(dāng)一個類方法被調(diào)用時,元類會首先查找它本身是否有該類方法的實(shí)現(xiàn),如果沒有,則該元類會向它的父類查找該方法,直到一直找到繼承鏈的頭。

元類 (metaclass) 也是一個對象,那么元類的 isa 指針又指向哪里呢?為了設(shè)計上的完整,所有的元類的 isa 指針都會指向一個根元類 (rootmetaclass)。根元類 (root metaclass) 本身的 isa 指針指向自己,這樣就行成了一個閉環(huán)。上面提到,一個對象能夠接收的消息列表是保存在它所對應(yīng)的類中的。在實(shí)際編程中,我們幾乎不會遇到向元類發(fā)消息的情況,那它的 isa 指針在實(shí)際上很少用到。不過這么設(shè)計保證了面向?qū)ο蟮母蓛簦此惺挛锒际菍ο?,都?isa 指針。

我們再來看看繼承關(guān)系,由于類方法的定義是保存在元類 (metaclass) 中,而方法調(diào)用的規(guī)則是,如果該類沒有一個方法的實(shí)現(xiàn),則向它的父類繼續(xù)查找。所以,為了保證父類的類方法可以在子類中可以被調(diào)用,所以子類的元類會繼承父類的元類,換而言之,類對象和元類對象有著同樣的繼承關(guān)系。

我很想把關(guān)系說清楚一些,但是這塊兒確實(shí)有點(diǎn)繞,下面這張圖或許能夠讓大家對 isa 和繼承的關(guān)系清楚一些(該圖片來自這里

該圖中,最讓人困惑的莫過于 Root Class 了。在實(shí)現(xiàn)中,Root Class 是指 NSObject,我們可以從圖中看出:

NSObject 類包括它的對象實(shí)例方法。

NSObject 的元類包括它的類方法,例如 alloc 方法。

NSObject 的元類繼承自 NSObject 類。

一個 NSObject 的類中的方法同時也會被 NSObject 的子類在查找方法時找到。

類的成員變量

如果把類的實(shí)例看成一個

C 語言的結(jié)構(gòu)體(struct),上面說的 isa

指針就是這個結(jié)構(gòu)體的第一個成員變量,而類的其它成員變量依次排列在結(jié)構(gòu)體中。排列順序如下圖所示(圖片來自《iOS 6 Programming

Pushing the Limits》):

為了驗(yàn)證該說法,我們在 XCode 中新建一個工程,在 main.m 中運(yùn)行如下代碼:

#import

@interfaceFather:NSObject{

int_father;

}

@end

@implementationFather

@end

@interfaceChild:Father{

int_child;

}

@end

@implementationChild

@end

intmain(intargc,char* argv[])

{

Child * child = [[Child alloc] init];

@autoreleasepool{

// ...

}

}

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

(lldb) p *child

(Child) $0 = {

(Father) Father = {

(NSObject) NSObject = {

(Class) isa = Child

}

(int) _father = 0

}

(int) _child = 0

}

可變與不可變

因?yàn)閷ο笤趦?nèi)存中的排布可以看成一個結(jié)構(gòu)體,該結(jié)構(gòu)體的大小并不能動態(tài)變化。所以無法在運(yùn)行時動態(tài)給對象增加成員變量。

相對的,對象的方法定義都保存在類的可變區(qū)域中。Objective-C 2.0 并未在頭文件中將實(shí)現(xiàn)暴露出來,但在 Objective-C 1.0 中,我們可以看到方法的定義列表是一個名為methodLists的指針的指針(如下圖所示)。通過修改該指針指向的指針的值,就可以實(shí)現(xiàn)動態(tài)地為某一個類增加成員方法。這也是Category實(shí)現(xiàn)的原理。同時也說明了為什么Category只可為對象增加成員方法,卻不能增加成員變量。

需要特別說明一下,通過objc_setAssociatedObject和objc_getAssociatedObject方法可以變相地給對象增加成員變量,但由于實(shí)現(xiàn)機(jī)制不一樣,所以并不是真正改變了對象的內(nèi)存結(jié)構(gòu)。

除了對象的方法可以動態(tài)修改,因?yàn)?isa 本身也只是一個指針,所以我們也可以在運(yùn)行時動態(tài)地修改 isa 指針的值,達(dá)到替換對象整個行為的目的。不過該應(yīng)用場景較少。

系統(tǒng)相關(guān) API 及應(yīng)用

isa swizzling 的應(yīng)用

系統(tǒng)提供的 KVO 的實(shí)現(xiàn),就利用了動態(tài)地修改 isa 指針的值的技術(shù)。在蘋果的文檔中可以看到如下描述:

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.

類似的,使用 isa swizzling 的技術(shù)的還有系統(tǒng)提供的 Key-Value Coding(KVC)。(謝謝大家指出錯誤,KVC 并沒有使用到 isa swizzling)

Method Swizzling API 說明

Objective-C 提供了以下 API 來動態(tài)替換類方法或?qū)嵗椒ǖ膶?shí)現(xiàn):

class_replaceMethod替換類方法的定義

method_exchangeImplementations交換 2 個方法的實(shí)現(xiàn)

method_setImplementation設(shè)置 1 個方法的實(shí)現(xiàn)

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

class_replaceMethod在蘋果的文檔(如下圖所示)中能看到,它有兩種不同的行為。當(dāng)類中沒有想替換的原方法時,該方法會調(diào)用class_addMethod來為該類增加一個新方法,也因?yàn)槿绱耍琧lass_replaceMethod在調(diào)用時需要傳入types參數(shù),而method_exchangeImplementations和method_setImplementation卻不需要。

method_exchangeImplementations的內(nèi)部實(shí)現(xiàn)相當(dāng)于調(diào)用了 2 次method_setImplementation方法,從蘋果的文檔中能清晰地了解到(如下圖所示)

從以上的區(qū)別我們可以總結(jié)出這 3 個 API 的使用場景:

class_replaceMethod, 當(dāng)需要替換的方法可能有不存在的情況時,可以考慮使用該方法。

method_exchangeImplementations,當(dāng)需要交換 2 個方法的實(shí)現(xiàn)時使用。

method_setImplementation最簡單的用法,當(dāng)僅僅需要為一個方法設(shè)置其實(shí)現(xiàn)方式時使用。

以上 3 個方法的源碼在這里,感興趣的同學(xué)可以讀一讀。

使用示例

我們在開發(fā)猿題庫客戶端的筆記功能時,需要使用系統(tǒng)的UIImagePickerController。但是,我們發(fā)現(xiàn),在 iOS6.0.2 系統(tǒng)下,系統(tǒng)提供的UIImagePickerController在 iPad 橫屏下有轉(zhuǎn)屏的 Bug,造成其方向錯誤。具體的 Bug 詳情可以見這里。

為了修復(fù)該 Bug,我們需要替換UIImagePickerController的如下 2 個方法

- (BOOL)shouldAutorotate;

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;

我們先實(shí)現(xiàn)了一個名為ImagePickerReplaceMethodsHolder的類,用于定義替換后的方法和實(shí)現(xiàn)。如下所示:

// ImagePickerReplaceMethodsHolder.h

@interfaceImagePickerReplaceMethodsHolder:NSObject

- (BOOL)shouldAutorotate;

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;

@end

// ImagePickerReplaceMethodsHolder.m

@implementationImagePickerReplaceMethodsHolder

- (BOOL)shouldAutorotate {

returnNO;

}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {

returnUIInterfaceOrientationPortrait;

}

@end

然后,我們在調(diào)用處,判斷當(dāng)前的 iOS 版本,對于 [iOS6.0, iOS6.1) 之間的版本,我們將UIImagePickerController的有問題的方法替換。具體代碼如下:

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)? ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

#define SYSTEM_VERSION_LESS_THAN(v)? ? ? ? ? ? ? ? ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)

+ (void)load {

staticdispatch_once_tonceToken;

dispatch_once(&onceToken, ^{

[selfhackForImagePicker];

});

}

+ (void)hackForImagePicker {

// fix bug of image picker under iOS 6.0

// http://stackoverflow.com/questions/12522491/crash-on-presenting-uiimagepickercontroller-under-ios-6-0

if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")

&& SYSTEM_VERSION_LESS_THAN(@"6.1")) {

Method oldMethod1 = class_getInstanceMethod([UIImagePickerControllerclass],@selector(shouldAutorotate));

Method newMethod1 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class],@selector(shouldAutorotate));

method_setImplementation(oldMethod1, method_getImplementation(newMethod1));

Method oldMethod2 = class_getInstanceMethod([UIImagePickerControllerclass],@selector(preferredInterfaceOrientationForPresentation));

Method newMethod2 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class],@selector(preferredInterfaceOrientationForPresentation));

method_setImplementation(oldMethod2, method_getImplementation(newMethod2));

}

}

通過如上代碼,我們就針對 iOS 特定版本的有問題的系統(tǒng)庫函數(shù)打了 Patch,使問題得到解決。

開源界的使用

有少量不明真相的同學(xué)以為蘋果在審核時會拒絕 App 使用以上 API,這其實(shí)是對蘋果的誤解。使用如上 API 是安全的。另外,開源界也對以上方法都適當(dāng)?shù)氖褂谩@纾?/p>

著名的網(wǎng)絡(luò)庫AFNetworking。AFNetworking 網(wǎng)絡(luò)庫 (v1.x 版本) 使用了 class_replaceMethod 方法(AFHTTPRequestOperation.m 文件第 105 行)

Nimbus。Nimbus 是著名的工具類庫,它在其 core 模塊中提供了NIRuntimeClassModifications.h文件,用于提供上述 API 的封裝。

國內(nèi)的大眾點(diǎn)評 iOS 客戶端。該客戶端使用了他們自己開發(fā)的基于 Wax 修改而來的WaxPatch,WaxPatch 可以實(shí)現(xiàn)通過服務(wù)器更新來動態(tài)修改客戶端的邏輯。而 WaxPatch 主要是修改了 wax 中的 wax_instance.m 文件,在其中加入了 class_replaceMethod 來替換原始實(shí)現(xiàn),從而實(shí)現(xiàn)修改客戶端的原有行為。

總結(jié)

通過本文,我們了解到了 Objective-C 語言的對象模型,以及 Objective-C 語言對象模型中對isa swizzling和method swizzling的支持。本文也通過具體的實(shí)例代碼和開源項目,讓我們對該對象模型提供的動態(tài)性有了更加深刻的認(rèn)識。

后記

文章發(fā)表后,一些同行指出在 ARM64 的 CPU 下,isa 的內(nèi)部結(jié)構(gòu)有變化。這點(diǎn)我是知道的,不過希望以后再撰文討論。感興趣的同學(xué)可以查看蘋果今年 WWDC2013 的視頻:《Session 404 Advanced in Objective-C》。

參考鏈接

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html

http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html

http://www.devalot.com/articles/2011/11/objc-object-model.html

http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html

http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html

gunstep 的實(shí)現(xiàn)源碼

http://algorithm.com.au/downloads/talks/objective-c-internals/objective-c-internals.pdf

http://opensource.apple.com/source/objc4/objc4-532/runtime/

https://github.com/AFNetworking/AFNetworking

https://github.com/jverkoey/nimbus

https://github.com/mmin18/WaxPatch

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文參考自 http://blog.devtang.com/2013/10/15/objective-c-obje...
    VinZZZZ閱讀 574評論 0 0
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,101評論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 842評論 0 2
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 870評論 0 4
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,351評論 0 7

友情鏈接更多精彩內(nèi)容