習(xí)題內(nèi)容
下面的代碼會(huì)钥组?Compile Error / Runtime Crash / NSLog…?
@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 {
[NSObject foo];
[[NSObject new] foo];
}
return 0;
}
答案:代碼正常輸出狭瞎,輸出結(jié)果如下:
2014-11-06 13:11:46.694 Test[14872:1110786] IMP: -[NSObject(Sark) foo]
2014-11-06 13:11:46.695 Test[14872:1110786] IMP: -[NSObject(Sark) foo]
使用clang -rewrite-objc main.m重寫(xiě),我們可以發(fā)現(xiàn) main 函數(shù)中兩個(gè)方法調(diào)用被轉(zhuǎn)換成如下代碼:
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("foo"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")), sel_registerName("foo"));
我們發(fā)現(xiàn)上述兩個(gè)方法最終轉(zhuǎn)換成使用 objc_msgSend 函數(shù)傳遞消息欢唾。
這里先看幾個(gè)概念
objc_msgSend函數(shù)定義如下:
id objc_msgSend(id self, SEL op, ...)
關(guān)于 id 的解釋請(qǐng)看objc runtime系列第二篇博文: objc runtime中Object & Class & Meta Class的細(xì)節(jié)
什么是 SEL
打開(kāi)objc.h文件且警,看下SEL的定義如下:
`
typedef struct objc_selector *SEL;`
SEL是一個(gè)指向objc_selector結(jié)構(gòu)體的指針。而 objc_selector 的定義并沒(méi)有在runtime.h中給出定義礁遣。我們可以嘗試運(yùn)行如下代碼:
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);
輸出如下:
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
Objective-C在編譯時(shí)振湾,會(huì)根據(jù)方法的名字生成一個(gè)用來(lái)區(qū)分這個(gè)方法的唯一的一個(gè)ID。只要方法名稱(chēng)相同亡脸,那么它們的ID就是相同的押搪。
兩個(gè)類(lèi)之間树酪,不管它們是父類(lèi)與子類(lèi)的關(guān)系,還是之間沒(méi)有這種關(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è)字符串,所以直接比較它們的地址即可排嫌。
當(dāng)然畸裳,不同的類(lèi)可以擁有相同的selector。不同類(lèi)的實(shí)例對(duì)象執(zhí)行相同的selector時(shí)淳地,會(huì)在各自的方法列表中去根據(jù)selector去尋找自己對(duì)應(yīng)的IMP怖糊。
那么什么是IMP呢
繼續(xù)看定義:
typedef id (*IMP)(id, SEL, ...);
IMP本質(zhì)就是一個(gè)函數(shù)指針,這個(gè)被指向的函數(shù)包含一個(gè)接收消息的對(duì)象id颇象,調(diào)用方法的SEL伍伤,以及一些方法參數(shù),并返回一個(gè)id遣钳。因此我們可以通過(guò)SEL獲得它所對(duì)應(yīng)的IMP扰魂,在取得了函數(shù)指針之后,也就意味著我們?nèi)〉昧诵枰獔?zhí)行方法的代碼入口蕴茴,這樣我們就可以像普通的C語(yǔ)言函數(shù)調(diào)用一樣使用這個(gè)函數(shù)指針劝评。
那么 objc_msgSend 到底是怎么工作的呢?
在Objective-C中荐开,消息直到運(yùn)行時(shí)才會(huì)綁定到方法的實(shí)現(xiàn)上付翁。編譯器會(huì)把代碼中[target doSth]轉(zhuǎn)換成 objc_msgSend消息函數(shù)简肴,這個(gè)函數(shù)完成了動(dòng)態(tài)綁定的所有事情晃听。它的運(yùn)行流程如下:
檢查selector是否需要忽略。(ps: Mac開(kāi)發(fā)中開(kāi)啟GC就會(huì)忽略retain,release方法砰识。)
檢查target是否為nil能扒。如果為nil,直接cleanup辫狼,然后return初斑。(這就是我們可以向nil發(fā)送消息的原因。)
然后在target的Class中根據(jù)Selector去找IMP
尋找IMP的過(guò)程:
先從當(dāng)前class的cache方法列表(cache methodLists)里去找
找到了膨处,跳到對(duì)應(yīng)函數(shù)實(shí)現(xiàn)
沒(méi)找到见秤,就從class的方法列表(methodLists)里找
還找不到砂竖,就到super class的方法列表里找,直到找到基類(lèi)(NSObject)為止
最后再找不到鹃答,就會(huì)進(jìn)入動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)的機(jī)制乎澄。(這部分知識(shí),下次再細(xì)談)
那么什么是方法列表呢测摔?
上一篇博文中提到了objc_class結(jié)構(gòu)體定義置济,如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#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
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}```
1) objc_method_list 就是用來(lái)存儲(chǔ)當(dāng)前類(lèi)的方法鏈表,objc_method存儲(chǔ)了類(lèi)的某個(gè)方法的信息锋八。
**Method**
`typedef struct objc_method *Method;`
Method 是用來(lái)代表類(lèi)中某個(gè)方法的類(lèi)型浙于,它實(shí)際就指向objc_method結(jié)構(gòu)體,如下:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
method_types是個(gè)char指針挟纱,存儲(chǔ)著方法的參數(shù)類(lèi)型和返回值類(lèi)型羞酗。
SEL 和 IMP 就是我們上文提到的,所以我們可以理解為objc_class中 method list保存了一組SEL<->IMP的映射樊销。
2)objc_cache 用來(lái)緩存用過(guò)的方法整慎,提高性能。
**Cache**
`typedef struct objc_cache *Cache OBJC2_UNAVAILABLE;`
實(shí)際指向objc_cache結(jié)構(gòu)體围苫,如下:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
mask: 指定分配cache buckets的總數(shù)裤园。在方法查找中,Runtime使用這個(gè)字段確定數(shù)組的索引位置
occupied: 實(shí)際占用cache buckets的總數(shù)
buckets: 指定Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組剂府。這個(gè)數(shù)組可能包含不超過(guò)mask+1個(gè)元素拧揽。需要注意的是,指針可能是NULL腺占,表示這個(gè)緩存bucket沒(méi)有被占用淤袜,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長(zhǎng)衰伯。
objc_msgSend每調(diào)用一次方法后铡羡,就會(huì)把該方法緩存到cache列表中,下次的時(shí)候意鲸,就直接優(yōu)先從cache列表中尋找烦周,如果cache沒(méi)有,才從methodLists中查找方法怎顾。
**說(shuō)完了 objc_msgSend读慎, 那么題目中的Category又是怎么工作的呢?**
**繼續(xù)看概念**
我們知道Catagory可以動(dòng)態(tài)地為已經(jīng)存在的類(lèi)添加新的方法。這樣可以保證類(lèi)的原始設(shè)計(jì)規(guī)模較小槐雾,功能增加時(shí)再逐步擴(kuò)展夭委。在runtime.h中查看定義:
`
typedef struct objc_category *Category;`
同樣也是指向一個(gè) objc_category 的C 結(jié)構(gòu)體,定義如下:
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
通過(guò)上面的結(jié)構(gòu)體募强,大家可以很清楚的看出存儲(chǔ)的內(nèi)容株灸。我們繼續(xù)往下看崇摄,打開(kāi)objc源代碼,在 objc-runtime-new.h中我們可以發(fā)現(xiàn)如下定義:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
上面的定義需要提到的地方有三點(diǎn):
name 是指 class_name 而不是 category_name
cls是要擴(kuò)展的類(lèi)對(duì)象慌烧,編譯期間是不會(huì)定義的配猫,而是在Runtime階段通過(guò)name對(duì)應(yīng)到對(duì)應(yīng)的類(lèi)對(duì)象
instanceProperties表示Category里所有的properties,這就是我們可以通過(guò)objc_setAssociatedObject和objc_getAssociatedObject增加實(shí)例變量的原因杏死,不過(guò)這個(gè)和一般的實(shí)例變量是不一樣的
為了驗(yàn)證上述內(nèi)容泵肄,我們使用clang -rewrite-objc main.m重寫(xiě),題目中的Category被編譯器轉(zhuǎn)換成了這樣:
// @interface NSObject (Sark)
// + (void)foo;
/* @end */
// @implementation NSObject (Sark)
static void _I_NSObject_Sark_foo(NSObject * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_dd1ee3_mi_0);
}
// @end
static struct _category_t _OBJC__CATEGORY_NSObject__Sark attribute ((used, section ("__DATA,__objc_const"))) =
{
"NSObject",
0, // &OBJC_CLASS__NSObject,
(const struct _method_list_t *)&_OBJC__CATEGORY_INSTANCE_METHODS_NSObject__Sark,
0,
0,
0,
};
static struct category_t *L_OBJC_LABEL_CATEGORY [1] attribute((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC__CATEGORY_NSObject__Sark,
};
_OBJC__CATEGORY_NSObject__Sark是按規(guī)則生成的字符串淑翼,我們可以清楚的看到是NSObject類(lèi),且Sark是NSObject類(lèi)的Category
_category_t結(jié)構(gòu)體第二項(xiàng) classref_t 沒(méi)有數(shù)據(jù)腐巢,驗(yàn)證了我們上面的說(shuō)法
由于題目中只有 - (void)foo方法,所以結(jié)構(gòu)體中存儲(chǔ)的list只有第三項(xiàng)instanceMethods被填充玄括。
_I_NSObject_Sark_foo代表了Category的foo方法冯丙,I表示實(shí)例方法
最后這個(gè)類(lèi)的Category生成了一個(gè)數(shù)組,存在了__objc_catlist里遭京,目前數(shù)組的內(nèi)容只有一個(gè)&_OBJC__CATEGORY_NSObject__Sark
**最終這些Category里面的方法是如何被加載的呢?**
1.打開(kāi)objc源代碼胃惜,找到 objc-os.mm, 函數(shù)_objc_init為runtime的加載入口,由libSystem調(diào)用哪雕,進(jìn)行初始化操作船殉。
2.之后調(diào)用objc-runtime-new.mm -> map_images加載map到內(nèi)存
3.之后調(diào)用objc-runtime-new.mm->_read_images初始化內(nèi)存中的map, 這個(gè)時(shí)候?qū)?huì)load所有的類(lèi),協(xié)議還有Category斯嚎。NSOBject的+load方法就是這個(gè)時(shí)候調(diào)用的
這里貼上Category被加載的代碼:
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category ???(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
BOOL classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
/ || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
1) 循環(huán)調(diào)用了 _getObjc2CategoryList方法利虫,這個(gè)方法的實(shí)現(xiàn)是:
1
GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
方法中最后一個(gè)參數(shù)__objc_catlist就是編譯器剛剛生成的category數(shù)組
2) load完所有的categories之后,開(kāi)始對(duì)Category進(jìn)行處理堡僻。
從上面的代碼中我們可以發(fā)現(xiàn):實(shí)例方法被加入到了當(dāng)前的類(lèi)對(duì)象中, 類(lèi)方法被加入到了當(dāng)前類(lèi)的Meta Class中 (cls->ISA)
Step 1. 調(diào)用addUnattachedCategoryForClass方法
Step 2. 調(diào)用remethodizeClass方法, 在remethodizeClass的實(shí)現(xiàn)里調(diào)用attachCategoryMethods
static void
attachCategoryMethods(Class cls, category_list *cats, bool flushCaches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
method_list_t **mlists = (method_list_t *)
_malloc_internal(cats->count * sizeof(mlists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int i = cats->count;
BOOL fromBundle = NO;
while (i--) {
method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= cats->list[i].fromBundle;
}
}
attachMethodLists(cls, mlists, mcount, NO, fromBundle, flushCaches);
_free_internal(mlists);
}
這里把一個(gè)類(lèi)的category_list的所有方法取出來(lái)生成了method list糠惫。這里是倒序添加的,也就是說(shuō)钉疫,新生成的category的方法會(huì)先于舊的category的方法插入硼讽。
之后調(diào)用attachMethodLists將所有方法前序添加進(jìn)類(lèi)的method list中,如果原來(lái)類(lèi)的方法列表是a牲阁,b固阁,Category的方法列表是c,d咨油。那么插入之后的方法列表將會(huì)是c您炉,d柒爵,a役电,b。
**小發(fā)現(xiàn)**
看上面被編譯器轉(zhuǎn)換的代碼棉胀,我們發(fā)現(xiàn)Category頭文件被注釋掉了法瑟,結(jié)合上面category的加載過(guò)程冀膝。這就是我們即使沒(méi)有import category的頭文件,都能夠成功調(diào)用到Category方法的原因霎挟。
runtime加載完成后窝剖,Category的原始信息在類(lèi)結(jié)構(gòu)中將不會(huì)存在。
**解惑**
根據(jù)上面提到的知識(shí)酥夭,我們對(duì)題目中的代碼進(jìn)行分析赐纱。
1) objc runtime加載完后,NSObject的Sark Category被加載熬北。而NSObject的Sark Category的頭文件 + (void)foo 并沒(méi)有實(shí)質(zhì)參與到工作中疙描,只是給編譯器進(jìn)行靜態(tài)檢查,所有我們編譯上述代碼會(huì)出現(xiàn)警告讶隐,提示我們沒(méi)有實(shí)現(xiàn) + (void)foo 方法起胰。而在代碼編譯中,它已經(jīng)被注釋掉了巫延。
2) 實(shí)際被加入到Class的method list的方法是 - (void)foo效五,它是一個(gè)實(shí)例方法,所以加入到當(dāng)前類(lèi)對(duì)象NSObject的方法列表中炉峰,而不是NSObject Meta class的方法列表中畏妖。
3) 當(dāng)執(zhí)行 [NSObject foo]時(shí),我們看下整個(gè)objc_msgSend的過(guò)程:
結(jié)合上一篇Meta Class的知識(shí):
objc_msgSend 第一個(gè)參數(shù)是 “(id)objc_getClass("NSObject")”疼阔,獲得NSObject Class的對(duì)象瓜客。
類(lèi)方法在Meta Class的方法列表中找,我們?cè)趌oad Category方法時(shí)加入的是- (void)foo實(shí)例方法竿开,所以并不在NSOBject Meta Class的方法列表中
繼續(xù)往 super class中找谱仪,在上一篇博客中我們知道,NSObject Meta Class的super class是NSObject本身否彩。所以疯攒,這個(gè)時(shí)候我們能夠找到- (void)foo 這個(gè)方法。
所以正常輸出結(jié)果
4) 當(dāng)執(zhí)行[[NSObject new] foo]列荔,我們看下整個(gè)objc_msgSend的過(guò)程:
[NSObject new]生成一個(gè)NSObject對(duì)象敬尺。
直接在該對(duì)象的類(lèi)(NSObject)的方法列表里找。
能夠找到贴浙,所以正常輸出結(jié)果砂吞。
**刨根問(wèn)底Objective-C Runtime(4)- 成員變量與屬性**
本篇筆記主要是講述objc runtime的 成員變量和屬性。
**習(xí)題內(nèi)容**
下面代碼會(huì)? Compile Error / Runtime Crash / NSLog…?
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak
{
NSLog(@"my name is %@", self.name);
}
@end
@interface Test : NSObject
@end
@implementation Test - (instancetype)init
{
self = [super init];
if (self) {
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
}
return self;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[Test alloc] init];
}
return 0;
}
答案:代碼正常輸出崎溃,輸出結(jié)果為:
2014-11-07 14:08:25.698 Test[1097:57255] my name is
**為什么呢?**
前幾節(jié)博文中多次講到了objc_class結(jié)構(gòu)體蜻直,今天我們?cè)倌贸鰜?lái)看一下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
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
} OBJC2_UNAVAILABLE;
其中objc_ivar_list結(jié)構(gòu)體存儲(chǔ)著objc_ivar數(shù)組列表,而objc_ivar結(jié)構(gòu)體存儲(chǔ)了類(lèi)的單個(gè)成員變量的信息。
**那么什么是Ivar呢概而?**
Ivar 在objc中被定義為:
`typedef struct objc_ivar *Ivar;`
它是一個(gè)指向objc_ivar結(jié)構(gòu)體的指針呼巷,結(jié)構(gòu)體有如下定義:
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
ifdef LP64
int space OBJC2_UNAVAILABLE;
endif
} OBJC2_UNAVAILABLE;
這里我們注意第三個(gè)成員 ivar_offset。它表示基地址偏移字節(jié)赎瑰。
在編譯我們的類(lèi)時(shí)王悍,編譯器生成了一個(gè) ivar布局,顯示了在類(lèi)中從哪可以訪問(wèn)我們的 ivars 餐曼⊙勾ⅲ看下圖:
![1419386363580125.png](http://upload-images.jianshu.io/upload_images/1864395-ef3ced36ac3e1213.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
上圖中,左側(cè)的數(shù)據(jù)就是地址偏移字節(jié)源譬,我們對(duì) ivar 的訪問(wèn)就可以通過(guò) 對(duì)象地址 + ivar偏移字節(jié)的方法渠脉。但是這又引發(fā)一個(gè)問(wèn)題,看下圖:
![1419386376731373.png](http://upload-images.jianshu.io/upload_images/1864395-da77e9c2ba7585d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我們?cè)黾恿烁割?lèi)的ivar瓶佳,這個(gè)時(shí)候布局就出錯(cuò)了芋膘,我們就不得不重新編譯子類(lèi)來(lái)恢復(fù)兼容性。
而Objective-C Runtime中使用了Non Fragile ivars霸饲,看下圖:
![1419386388780906.png](http://upload-images.jianshu.io/upload_images/1864395-fa05d2ebf66729a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
使用Non Fragile ivars時(shí)为朋,Runtime會(huì)進(jìn)行檢測(cè)來(lái)調(diào)整類(lèi)中新增的ivar的偏移量。 這樣我們就可以通過(guò) 對(duì)象地址 + 基類(lèi)大小 + ivar偏移字節(jié)的方法來(lái)計(jì)算出ivar相應(yīng)的地址厚脉,并訪問(wèn)到相應(yīng)的ivar习寸。
我們來(lái)看一個(gè)例子:
@interface Student : NSObject
{
@private
NSInteger age;
}
@end
@implementation Student
- (NSString *)description
{
return [NSString stringWithFormat:@"age = %d", age];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *student = [[Student alloc] init];
student->age = 24;
}
return 0;
}
上述代碼,Student有兩個(gè)被標(biāo)記為private的ivar傻工,這個(gè)時(shí)候當(dāng)我們使用 -> 訪問(wèn)時(shí)霞溪,編譯器會(huì)報(bào)錯(cuò)。那么我們?nèi)绾卧O(shè)置一個(gè)被標(biāo)記為private的ivar的值呢?
通過(guò)上面的描述中捆,我們知道ivar是通過(guò)計(jì)算字節(jié)偏量來(lái)確定地址鸯匹,并訪問(wèn)的。我們可以改成這樣:
@interface Student : NSObject
{
@private
int age;
}
@end
@implementation Student
- (NSString *)description
{
NSLog(@"current pointer = %p", self);
NSLog(@"age pointer = %p", &age);
return [NSString stringWithFormat:@"age = %d", age];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *student = [[Student alloc] init];
Ivar age_ivar = class_getInstanceVariable(object_getClass(student), "age");
int *age_pointer = (int *)((__bridge void *)(student) + ivar_getOffset(age_ivar));
NSLog(@"age ivar offset = %td", ivar_getOffset(age_ivar));
*age_pointer = 10;
NSLog(@"%@", student);
}
return 0;
}
上述代碼的輸出結(jié)果為:
2014-11-08 18:24:38.892 Test[4143:466864] age ivar offset = 8
2014-11-08 18:24:38.893 Test[4143:466864] current pointer = 0x1001002d0
2014-11-08 18:24:38.893 Test[4143:466864] age pointer = 0x1001002d8
2014-11-08 18:24:38.894 Test[4143:466864] age = 10
我們可以清晰的看到指針地址的變化和偏移量泄伪,和我們上述描述一致殴蓬。
**說(shuō)完了Ivar, 那Property又是怎么樣的呢蟋滴?**
使用clang -rewrite-objc main.m重寫(xiě)題目中的代碼染厅,我們發(fā)現(xiàn)Sark類(lèi)中的name屬性被轉(zhuǎn)換成了如下代碼:
struct Sark_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// @property (nonatomic, copy) NSString name;
/ @end /
// @implementation Sark
static NSString * _I_Sark_name(Sark * self, SEL _cmd) { return ((NSString **)((char *)self + OBJC_IVAR__Sark_name)); }
static void I_Sark_setName(Sark * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, OFFSETOFIVAR(struct Sark, _name), (id)name, 0, 1); }
類(lèi)中的Property屬性被編譯器轉(zhuǎn)換成了Ivar,并且自動(dòng)添加了我們熟悉的Set和Get方法津函。
我們這個(gè)時(shí)候回頭看一下objc_class結(jié)構(gòu)體中的內(nèi)容肖粮,并沒(méi)有發(fā)現(xiàn)用來(lái)專(zhuān)門(mén)記錄Property的list。我們翻開(kāi)objc源代碼尔苦,在objc-runtime-new.h中涩馆,發(fā)現(xiàn)最終還是會(huì)通過(guò)在class_ro_t結(jié)構(gòu)體中使用property_list_t存儲(chǔ)對(duì)應(yīng)的propertyies行施。
而在剛剛重寫(xiě)的代碼中,我們可以找到這個(gè)property_list_t:
static struct /_prop_list_t/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC__PROP_LIST_Sark attribute ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
name
};
static struct _class_ro_t _OBJC_CLASS_RO__Sark attribute ((used, section ("__DATA,__objc_const"))) = {
0, OFFSETOFIVAR(struct Sark, _name), sizeof(struct Sark_IMPL),
(unsigned int)0,
0,
"Sark",
(const struct _method_list_t *)&_OBJC__INSTANCE_METHODS_Sark,
0,
(const struct _ivar_list_t *)&_OBJC__INSTANCE_VARIABLES_Sark,
0,
(const struct _prop_list_t *)&_OBJC__PROP_LIST_Sark,
};
**解惑**
**1)為什么能夠正常運(yùn)行凌净,并調(diào)用到speak方法?**
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
obj被轉(zhuǎn)換成了一個(gè)指向Sark Class的指針屋讶,然后使用id轉(zhuǎn)換成了objc_object類(lèi)型冰寻。這個(gè)時(shí)候的obj已經(jīng)相當(dāng)于一個(gè)Sark的實(shí)例對(duì)象(但是和使用[Sark new]生成的對(duì)象還是不一樣的),我們回想下Runtime的第二篇博文中objc_object結(jié)構(gòu)體的構(gòu)成就是一個(gè)指向Class的isa指針皿渗。
這個(gè)時(shí)候我們?cè)倩叵胂律弦黄┪闹衞bjc_msgSend的工作流程斩芭,在代碼中的obj指向的Sark Class中能夠找到speak方法,所以代碼能夠正常運(yùn)行乐疆。
**2) 為什么self.name的輸出為?**
我們?cè)跍y(cè)試代碼中加入一些調(diào)試代碼和Log如下:
- (void)speak
{
unsigned int numberOfIvars = 0;
Ivar *ivars = class_copyIvarList([self class], &numberOfIvars);
for(const Ivar *p = ivars; p < ivars+numberOfIvars; p++) {
Ivar const ivar = *p;
ptrdiff_t offset = ivar_getOffset(ivar);
const char *name = ivar_getName(ivar);
NSLog(@"Sark ivar name = %s, offset = %td", name, offset);
}
NSLog(@"my name is %p", &_name);
NSLog(@"my name is %@", *(&_name));
}
@implementation Test - (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"Test instance = %@", self);
void *self2 = (__bridge void *)self;
NSLog(@"Test instance pointer = %p", &self2);
id cls = [Sark class];
NSLog(@"Class instance address = %p", cls);
void *obj = &cls;
NSLog(@"Void *obj = %@", obj);
[(__bridge id)obj speak];
}
return self;
}
@end
輸出結(jié)果如下:
2014-11-11 00:56:02.464 Test[10475:1071029] Test instance = 2014-11-11 00:56:02.464 Test[10475:1071029] Test instance pointer = 0x7fff5fbff7c8
2014-11-11 00:56:02.465 Test[10475:1071029] Class instance address = 0x1000023c8
2014-11-11 00:56:02.465 Test[10475:1071029] Void *obj = 2014-11-11 00:56:02.465 Test[10475:1071029] Sark ivar name = _name, offset = 8
2014-11-11 00:56:02.465 Test[10475:1071029] my name is 0x7fff5fbff7c8
2014-11-11 00:56:02.465 Test[10475:1071029] my name is
Sark中Propertyname最終被轉(zhuǎn)換成了Ivar加入到了類(lèi)的結(jié)構(gòu)中划乖,Runtime通過(guò)計(jì)算成員變量的地址偏移來(lái)尋找最終Ivar的地址,我們通過(guò)上述輸出結(jié)果挤土,可以看到 Sark的對(duì)象指針地址加上Ivar的偏移量之后剛好指向的是Test對(duì)象指針地址琴庵。
這里的原因主要是因?yàn)樵贑中,局部變量是存儲(chǔ)到內(nèi)存的棧區(qū)仰美,程序運(yùn)行時(shí)棧的生長(zhǎng)規(guī)律是從地址高到地址低迷殿。C語(yǔ)言到頭來(lái)講是一個(gè)順序運(yùn)行的語(yǔ)言,隨著程序運(yùn)行咖杂,棧中的地址依次往下走庆寺。
看下圖,可以清楚的展示整個(gè)計(jì)算的過(guò)程:
![1419386619962473.png](http://upload-images.jianshu.io/upload_images/1864395-3bbfa5fac4340b2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我們可以做一個(gè)另外的實(shí)驗(yàn)诉字,把Test Class 的init方法改為如下代碼:
@interface Father : NSObject
@end
@implementation Father
@end
@implementation Test
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"Test instance = %@", self);
id fatherCls = [Father class];
void *father;
father = (void *)&fatherCls;
id cls = [Sark class];
void *obj;
obj = (void *)&cls;
[(__bridge id)obj speak];
}
return self;
}
@end
你會(huì)發(fā)現(xiàn)這個(gè)時(shí)候的輸出變成了:
2014-11-08 21:40:36.724 Test[4845:543231] Test instance = 2014-11-08 21:40:36.725 Test[4845:543231] ivar name = _name, offset = 8
2014-11-08 21:40:36.726 Test[4845:543231] Sark instance = 0x7fff5fbff7b8
2014-11-08 21:40:36.726 Test[4845:543231] my name is 0x7fff5fbff7c0
2014-11-08 21:40:36.726 Test[4845:543231] my name is