《轉》Objective-C Runtime(4)- 成員變量與屬性

下面代碼會? 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;
}

答案:代碼正常輸出,輸出結果爲:佈局

2014-11-07 14:08:25.698 Test[1097:57255] my name is <Test: 0x1001002d0>

爲何呢?

前幾節博文中屢次講到了objc_class結構體,今天咱們再拿出來看一下:測試

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結構體存儲着objc_ivar數組列表,而objc_ivar結構體存儲了類的單個成員變量的信息。atom

那麼什麼是Ivar呢?

Ivar 在objc中被定義爲:spa

typedef struct objc_ivar *Ivar;

它是一個指向objc_ivar結構體的指針,結構體有以下定義:指針

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;

這裏咱們注意第三個成員 ivar_offset。它表示基地址偏移字節。調試

在編譯咱們的類時,編譯器生成了一個 ivar佈局,顯示了在類中從哪能夠訪問咱們的 ivars 。看下圖:code

上圖中,左側的數據就是地址偏移字節,咱們對 ivar 的訪問就能夠經過 對象地址 + ivar偏移字節的方法。可是這又引起一個問題,看下圖:orm

咱們增長了父類的ivar,這個時候佈局就出錯了,咱們就不得不從新編譯子類來恢復兼容性。對象

而Objective-C Runtime中使用了Non Fragile ivars,看下圖:

使用Non Fragile ivars時,Runtime會進行檢測來調整類中新增的ivar的偏移量。 這樣咱們就能夠經過 對象地址 + 基類大小 + ivar偏移字節的方法來計算出ivar相應的地址,並訪問到相應的ivar。

咱們來看一個例子:

@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有兩個被標記爲private的ivar,這個時候當咱們使用 -> 訪問時,編譯器會報錯。那麼咱們如何設置一個被標記爲private的ivar的值呢?

經過上面的描述,咱們知道ivar是經過計算字節偏量來肯定地址,並訪問的。咱們能夠改爲這樣:

@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;
}

上述代碼的輸出結果爲:

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

咱們能夠清晰的看到指針地址的變化和偏移量,和咱們上述描述一致。

說完了Ivar, 那Property又是怎麼樣的呢?

使用clang -rewrite-objc main.m重寫題目中的代碼,咱們發現Sark類中的name屬性被轉換成了以下代碼:

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); }

類中的Property屬性被編譯器轉換成了Ivar,而且自動添加了咱們熟悉的SetGet方法。

咱們這個時候回頭看一下objc_class結構體中的內容,並無發現用來專門記錄Property的list。咱們翻開objc源代碼,在objc-runtime-new.h中,發現最終仍是會經過在class_ro_t結構體中使用property_list_t存儲對應的propertyies。

而在剛剛重寫的代碼中,咱們能夠找到這個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)爲何可以正常運行,並調用到speak方法?

id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];

obj被轉換成了一個指向Sark Class的指針,而後使用id轉換成了objc_object類型。這個時候的obj已經至關於一個Sark的實例對象(可是和使用[Sark new]生成的對象仍是不同的),咱們回想下Runtime的第二篇博文objc_object結構體的構成就是一個指向Class的isa指針。

這個時候咱們再回想下上一篇博文objc_msgSend的工做流程,在代碼中的obj指向的Sark Class中可以找到speak方法,因此代碼可以正常運行。

2) 爲何self.name的輸出爲 <Test: 0x1001002d0> ?

咱們在測試代碼中加入一些調試代碼和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

輸出結果以下:

2014-11-11 00:56:02.464 Test[10475:1071029] Test instance = <Test: 0x10010fb60>
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 = <Sark: 0x7fff5fbff7c0>
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 <Test: 0x10010fb60>

Sark中Propertyname最終被轉換成了Ivar加入到了類的結構中,Runtime經過計算成員變量的地址偏移來尋找最終Ivar的地址,咱們經過上述輸出結果,能夠看到 Sark的對象指針地址加上Ivar的偏移量以後恰好指向的是Test對象指針地址。

這裏的緣由主要是由於在C中,局部變量是存儲到內存的棧區,程序運行時棧的生長規律是從地址高到地址低。C語言到頭來說是一個順序運行的語言,隨着程序運行,棧中的地址依次往下走。

看下圖,能夠清楚的展現整個計算的過程:

咱們能夠作一個另外的實驗,把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

你會發現這個時候的輸出變成了:

2014-11-08 21:40:36.724 Test[4845:543231] Test instance = <Test: 0x10010fb60>
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 <Father: 0x7fff5fbff7c8>
相關文章
相關標籤/搜索