(轉(zhuǎn))Objective-C Runtime(3、4)-消息和Category 成員變量與屬性

習(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末懦尝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子壤圃,更是在濱河造成了極大的恐慌陵霉,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伍绳,死亡現(xiàn)場(chǎng)離奇詭異撩匕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)墨叛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)止毕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人漠趁,你說(shuō)我怎么就攤上這事扁凛。” “怎么了闯传?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵谨朝,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)字币,這世上最難降的妖魔是什么则披? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮洗出,結(jié)果婚禮上士复,老公的妹妹穿的比我還像新娘。我一直安慰自己翩活,他們只是感情好阱洪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著菠镇,像睡著了一般冗荸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上利耍,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天蚌本,我揣著相機(jī)與錄音,去河邊找鬼隘梨。 笑死魂毁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的出嘹。 我是一名探鬼主播席楚,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼税稼!你這毒婦竟也來(lái)了烦秩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤郎仆,失蹤者是張志新(化名)和其女友劉穎只祠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扰肌,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抛寝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曙旭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盗舰。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖桂躏,靈堂內(nèi)的尸體忽然破棺而出钻趋,到底是詐尸還是另有隱情,我是刑警寧澤剂习,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布蛮位,位于F島的核電站较沪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏失仁。R本人自食惡果不足惜尸曼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萄焦。 院中可真熱鬧控轿,春花似錦、人聲如沸楷扬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烘苹。三九已至,卻和暖如春片部,著一層夾襖步出監(jiān)牢的瞬間镣衡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工档悠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廊鸥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓辖所,卻偏偏與公主長(zhǎng)得像惰说,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缘回,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉吆视,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評(píng)論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 731評(píng)論 0 2
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢酥宴?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,192評(píng)論 0 7
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中啦吧。 以下內(nèi)容是我通過(guò)整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 923評(píng)論 0 6
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼拙寡,具備了靈活的...
    lylaut閱讀 800評(píng)論 0 4