本文參考自 http://blog.devtang.com/2013/10/15/objective-c-object-model/ 以及 http://ios.jobbole.com/81657/ 模狭。 純粹是對(duì)文章內(nèi)容的整理和整合,供自己以后查閱评矩,版權(quán)歸原作者所有结洼。
isa 指針
什么數(shù)據(jù)結(jié)構(gòu)才能稱之為對(duì)象勿她?
每個(gè)對(duì)象都有類。這是面向?qū)ο蟮幕靖拍睿窃贠bjective-C中谤饭,它對(duì)數(shù)據(jù)結(jié)構(gòu)也一樣年枕。含有一個(gè)指針且該指針可以正確指向類的數(shù)據(jù)結(jié)構(gòu)炫欺,都可以被視作為對(duì)象。
在Objective-C中熏兄,對(duì)象的類是isa指針決定的品洛。isa指針指向?qū)ο笏鶎俚念悺?/p>
實(shí)際上,Objective-C中對(duì)象最基本的定義是這樣的:
這說(shuō)的是:任何帶有以指針開始并指向類結(jié)構(gòu)的結(jié)構(gòu)都可以被視作objc_object摩桶。
我們還可以看到桥状,Class 也是一個(gè)包含 isa 指針的結(jié)構(gòu)體。(圖中除了 isa 外還有其它成員變量硝清,但那是為了兼容非 2.0 版的 Objective-C 的遺留邏輯辅斟,大家可以忽略它。)
Objective-C中對(duì)象最重要的特點(diǎn)是你可以發(fā)送消息給它們:
[@"stringValue" writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
這能工作是因?yàn)镺bjective-C對(duì)象(這兒是NSCFString)在發(fā)送消息時(shí)芦拿,運(yùn)行時(shí)庫(kù)會(huì)追尋著對(duì)象的isa指針得到了對(duì)象所屬的類(這兒是NSCFString類)士飒。這個(gè)類包含了能應(yīng)用于這個(gè)類的所有實(shí)例方法和指向超類的指針以便可以找到父類的實(shí)例方法查邢。運(yùn)行時(shí)庫(kù)檢查這個(gè)類和其超類的方法列表,找到一個(gè)匹配這條消息的方法(在上面的代碼里变汪,是NSString類的writeToFile:atomically:encoding:error方法)侠坎。運(yùn)行時(shí)庫(kù)基于那個(gè)方法調(diào)用函數(shù)(IMP)。重點(diǎn)就是類要定義這個(gè)你發(fā)送給對(duì)象的消息裙盾。
什么是元類(meta class)实胸?
你可以發(fā)送消息給一個(gè)類:
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
在這個(gè)示例里,defaultStringEncoding被發(fā)送給了 NSString類番官。
因此Objective-C中每個(gè)類本身(Class)也是一個(gè)對(duì)象庐完。如上面圖Class所展示的,這意味著類結(jié)構(gòu)必須以一個(gè)isa指針開始徘熔,從而可以和objc_object在二進(jìn)制層面兼容门躯。為了調(diào)用類里的方法,類的isa指針必須指向包含這些類方法的類結(jié)構(gòu)體酷师。這個(gè)類結(jié)構(gòu)體就是元類 (metaclass)讶凉。
簡(jiǎn)單說(shuō)就是:
- 當(dāng)你給對(duì)象發(fā)送消息時(shí),消息是在尋找這個(gè)對(duì)象的類的方法列表山孔。
- 當(dāng)你給類發(fā)消息時(shí)懂讯,消息是在尋找這個(gè)類的元類的方法列表。
元類是必不可少的台颠,因?yàn)樗鎯?chǔ)了類的類方法褐望。每個(gè)類都必須有獨(dú)一無(wú)二的元類,因?yàn)槊總€(gè)類都有獨(dú)一無(wú)二的類方法串前。每個(gè)對(duì)象的isa所指的是一個(gè)元類的實(shí)例瘫里。那么這個(gè)實(shí)例所屬的類是如何定義的呢?這就引出了:
元類的類是什么荡碾?
元類谨读,就像之前的類一樣,它也是一個(gè)對(duì)象坛吁。你也可以調(diào)用它的方法漆腌。自然的,這就意味著他必須也有一個(gè)類阶冈。
如類結(jié)構(gòu)圖所示闷尿,所有的元類都使用根元類(繼承體系中處于頂端的類的元類)作為他們的類。這就意味著所有NSObject的子類(大多數(shù)類)的元類都會(huì)以NSObject的元類作為他們的類女坑。
根據(jù)這個(gè)規(guī)則填具,所有的元類使用根元類作為他們的類,根元類的元類則就是它自己。也就是說(shuō)基類的元類的isa指針指向他自己劳景。
驗(yàn)證
下面的代碼在運(yùn)行時(shí)創(chuàng)建了一個(gè)NSError的子類誉简,并且添加了一個(gè)方法:
Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
ReportFunction函數(shù)就是添加的實(shí)例方法,具體實(shí)現(xiàn)如下
void ReportFunction(id self, SEL _cmd)
{
NSLog(@"This object is %p.", self);
NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 1; i < 5; i++)
{
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = object_getClass(currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}
表面上看來(lái)盟广,這相當(dāng)簡(jiǎn)單闷串。在運(yùn)行時(shí)創(chuàng)建一個(gè)類只需要3個(gè)步驟:
- 為”class pair”分配內(nèi)存 (使用objc_allocateClassPair).
- 添加方法或成員變量到有需要的類里 (我已經(jīng)使用class_addMethod添加了一個(gè)方法).
- 注冊(cè)類以便它能使用 (使用objc_registerClassPair).
這里解釋一下 SEL和IMP
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
其中,Apple源碼里并沒有給出objc_selector的定義筋量,這里用例子來(lái)說(shuō)明:
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo
{
NSLog(@"IMP: -[NSObject(Sark) foo]");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
SEL sel = @selector(foo);
NSLog(@"%s", (char *)sel);
NSLog(@"%p", sel);
const char *selName = [@"foo" UTF8String];
SEL sel2 = sel_registerName(selName);
NSLog(@"%s", (char *)sel2);
NSLog(@"%p", sel2);
}
return 0;
}
輸出結(jié)果:
2014-11-06 13:46:08.058 Test[15053:1132268] foo
2014-11-06 13:46:08.058 Test[15053:1132268] 0x7fff8fde5114
2014-11-06 13:46:08.058 Test[15053:1132268] foo
2014-11-06 13:46:08.058 Test[15053:1132268] 0x7fff8fde5114
因此可以發(fā)現(xiàn)烹吵,Objective-C在編譯時(shí),會(huì)根據(jù)方法的名字生成一個(gè)用來(lái)區(qū)分這個(gè)方法的唯一的一個(gè)ID桨武。只要方法名稱相同肋拔,那么它們的ID就是相同的。
兩個(gè)類之間呀酸,不管它們是父類與子類的關(guān)系凉蜂,還是之間沒有這種關(guān)系,只要方法名相同性誉,那么它的SEL就是一樣的窿吩。每一個(gè)方法都對(duì)應(yīng)著一個(gè)SEL。編譯器會(huì)根據(jù)每個(gè)方法的方法名為那個(gè)方法生成唯一的SEL错览。這些SEL組成了一個(gè)Set集合爆存,當(dāng)我們?cè)谶@個(gè)集合中查找某個(gè)方法時(shí),只需要去找這個(gè)方法對(duì)應(yīng)的SEL即可蝗砾。而SEL本質(zhì)是一個(gè)字符串,所以直接比較它們的地址即可携冤。
那么什么是IMP呢悼粮?
看其定義, IMP本質(zhì)就是一個(gè)函數(shù)指針曾棕,這個(gè)被指向的函數(shù)包含一個(gè)接收消息的對(duì)象id扣猫,調(diào)用方法的SEL,以及一些方法參數(shù)翘地,并返回一個(gè)id申尤。因此我們可以通過SEL獲得它所對(duì)應(yīng)的IMP,在取得了函數(shù)指針之后衙耕,也就意味著我們?nèi)〉昧诵枰獔?zhí)行方法的代碼入口昧穿,這樣我們就可以像普通的C語(yǔ)言函數(shù)調(diào)用一樣使用這個(gè)函數(shù)指針。
那么什么是方法列表呢橙喘?
方法列表就是在圖objc_object里时鸵,objc_class結(jié)構(gòu)中的成員 struct objc_method_list **methodLists.
重點(diǎn)參考自:http://chun.tips/blog/2014/11/06/bao-gen-wen-di-objective[nil]c-runtime(3)[nil]-xiao-xi-he-category/
運(yùn)行ReportFunction,我們需要?jiǎng)?chuàng)建一個(gè)動(dòng)態(tài)實(shí)例來(lái)創(chuàng)建類調(diào)用report方法:
id instanceOfNewClass = [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
這里沒有聲明report方法,但我使用performSelector:調(diào)用它饰潜,所以編譯器不會(huì)給出警告初坠。函數(shù)使用object_getClass跟蹤isa指針,因?yàn)閕sa指針是類的保護(hù)成員(你不能直接接收其他對(duì)象的isa指針)彭雾。ReportFunction不使用類方法碟刺,因?yàn)樵陬悓?duì)象里調(diào)用類方法不能返回元類,它會(huì)再次返回這個(gè)類(因此[NSString class]會(huì)返回NSString類而不是NSString元類).
ReportFunction函數(shù)會(huì)沿著isa進(jìn)行檢索薯酝,來(lái)告訴我們class半沽,meta-class以及meta-class的class是什么樣的情況:
This object is 0x10010c810.
Class is RuntimeErrorSubclass, and super is NSError.
Following the isa pointer 1 times gives 0x10010c600
Following the isa pointer 2 times gives 0x10010c630
Following the isa pointer 3 times gives 0x7fff71038480
Following the isa pointer 4 times gives 0x7fff71038480
NSObject's class is 0x7fff710384a8
NSObject's meta class is 0x7fff71038480
觀察isa到達(dá)過的地址的值:
- 對(duì)象的地址是 0x10010c810
- 類的地址是 0x10010c600
- 元類的地址是 0x10010c630
- 根元類(NSObject的元類)的地址是 0x7fff71038480
- NSObject元類的類是它本身.
這些地址的值并不重要,重要的是它們說(shuō)明了文中討論的從類到meta-class到NSObject的meta-class的整個(gè)流程蜜托。
系統(tǒng)相關(guān)API及應(yīng)用
ias swizzling的應(yīng)用
系統(tǒng)提供的 KVO 的實(shí)現(xiàn)抄囚,就利用了動(dòng)態(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.
Method Swizzling API 說(shuō)明
Objective-C 提供了以下 API 來(lái)動(dòng)態(tài)替換類方法或?qū)嵗椒ǖ膶?shí)現(xiàn):
class_replaceMethod 替換類方法的定義
method_exchangeImplementations 交換 2 個(gè)方法的實(shí)現(xiàn)
method_setImplementation 設(shè)置 1 個(gè)方法的實(shí)現(xiàn)
這 3 個(gè)方法有一些細(xì)微的差別橄务,給大家介紹如下:
- class_replaceMethod
在蘋果的文檔(如下圖所示)中能看到幔托,它有兩種不同的行為。當(dāng)類中沒有想替換的原方法時(shí)蜂挪,該方法會(huì)調(diào)用class_addMethod
來(lái)為該類增加一個(gè)新方法重挑,也因?yàn)槿绱耍琧lass_replaceMethod在調(diào)用時(shí)需要傳入types參數(shù)棠涮,而method_exchangeImplementations和method_setImplementation卻不需要谬哀。
- method_exchangeImplementations 的內(nèi)部實(shí)現(xiàn)相當(dāng)于調(diào)用了 2 次method_setImplementation方法,從蘋果的文檔中能清晰地了解到(如下圖所示)
從以上的區(qū)別我們可以總結(jié)出這 3 個(gè) API 的使用場(chǎng)景:
- class_replaceMethod, 當(dāng)需要替換的方法可能有不存在的情況時(shí)严肪,可以考慮使用該方法史煎。
- method_exchangeImplementations,當(dāng)需要交換 2 個(gè)方法的實(shí)現(xiàn)時(shí)使用驳糯。(常用于使用自定義的類的方法來(lái)hack掉iOS SDK提供的方法)
- method_setImplementation 最簡(jiǎn)單的用法篇梭,當(dāng)僅僅需要為一個(gè)方法設(shè)置其實(shí)現(xiàn)方式時(shí)使用。