iOS Runtime學(xué)習(xí)筆記(對象、類、分類、消息機(jī)制、KVO、KVC)

對象模型

新建一個類:

#import <Foundation/Foundation.h>

@interface CustomClass : NSObject

@end

@implementation CustomClass

@end

int main(int argc, char * argv[]) {
    CustomClass *customObject = [[CustomClass alloc] init];
}

在終端運行以下命令:

xcrun -sdk iphonesimulator12.0 clang -rewrite-objc CustomObject.m

.m文件被編譯成C++文件,截取部分代碼如下:

...
typedef struct objc_object CustomClass;
...
int main(int argc, char * argv[]) {
    CustomClass *customObject = ((CustomClass *(*)(id, SEL))(void *)objc_msgSend)((id)((CustomClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CustomClass"), sel_registerName("alloc")), sel_registerName("init"));
}
...

可以看到我們定義的CustomClass被定義成objc_object結(jié)構(gòu)體,在main函數(shù)里面的CustomClass *customObject其實就是struct objc_object *customObject,所以customObject的本質(zhì)就是一個指向objc_object結(jié)構(gòu)體的指針。

objc_object的源碼:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

可以看到objc_object有一個Class類型的isa變量,這個isa是指向該實例所屬的類的。而Class其實本質(zhì)就是objc_class

typedef struct objc_class *Class;

objc_class的源碼:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    ...
};

類繼承自對象, 所有類其實也是對象,它的isa指向的類就是這個類的元類(meta-class)。元類只有一個對象,就是類對象。類方法就在類對象里面。

給一個對象發(fā)送消息,會到這個對象所屬的類里面找對應(yīng)的方法;給一個類發(fā)送消息,會到這個類的元類(也就是類對象所屬的類)里面去找對應(yīng)的方法。

而元類也是類,它也有isa指針,這個isa指針統(tǒng)一都直接指向NSObject的元類。而NSObject的元類的isa則指向自己。另外NSObject的元類的父類則指向NSObject。而其他子類的元類指向其父類的元類。

調(diào)用實例的方法,會到類里面找。調(diào)用類的方法,也就是元類的實例方法,因為類就是元類的實例,回到元類里面找,在元類里面找不到就會去元類的父類,最后來到NSObject元類,NSObject元類的父類就是NSObject。

objc_method的定義:

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
} 

SEL的定義:

typedef objc_selector * SEL;

可以理解為區(qū)分方法的 ID。

在Object-C中可以通過以下方法獲得SEL:

SEL @selector(<selector)

SEL sel_registerName(const char * _Nonnull str)

IMP的定義:

typedef id (*IMP)(id, SEL, ...); 

指向最終實現(xiàn)程序的內(nèi)存地址的指針。

消息機(jī)制

在Object-C里的函數(shù)調(diào)用其實都是發(fā)送消息,[receiver massage]會被編譯成

objc_msgSend(receiver, selector)

如果有參數(shù),則為:

objc_msgSend(receiver, selector, arg1, arg2, ...)

objc_class 里面還有一個變量:struct objc_cache *cache,它主要是為了調(diào)用的性能進(jìn)行優(yōu)化的。每當(dāng)實例對象接收到一個消息時,它會先到cache中去找能夠響應(yīng)的方法,找不到再去方法列表里面找。找到之后就會保存到cache里面。已備下次再被調(diào)用。

如果對象接收到無法解讀的消息,那將會調(diào)用其所屬類的以下類方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel

sel就是那個未知的選擇子,返回的BOOL類型來表示這個類能否新增一個實例方法來處理這個選擇子。所以本類有機(jī)會新增一個處理此選擇子的方法。一般是在resolveInstanceMethod:方法里面調(diào)用class_addMethod方法來動態(tài)添加方法,但前提是實現(xiàn)代碼已經(jīng)提前寫好了。

如果上述方法返回的是NO,那么當(dāng)前接受者還有第二次機(jī)會,runtime還會調(diào)用該對象的以下方法,看能否將該消息轉(zhuǎn)發(fā)給其他接受者處理:

- (id)forwardingTargetForSelector:(SEL)aSelector

如果此時還是返回nil,那么會來到完整的消息轉(zhuǎn)發(fā)。runtime會調(diào)用以下方法:

 - (void)forwardInvocation:(NSInvocation *)anInvocation

anInvocation包含了那條尚未處理的消息的全部細(xì)節(jié),包括選擇子、目標(biāo)以及參數(shù)。這個步驟可以修改消息的內(nèi)容,比如追加參數(shù)或者改變選擇子等。如果本類不處理此消息,會調(diào)用超類的同名方法。最后傳到NSObject時會調(diào)用“doesNotRecognizeSelector:”拋出異常。

Category

我們先給前面自定義的類實現(xiàn)一個分類:

@interface CustomClass : NSObject

@end

@implementation CustomClass

- (void)originMethod {
    NSLog(@"originMethod");
}

@end

@interface CustomClass(MyAddition)<NSCopying>

@property (nonatomic, copy) NSString *customProperty;

@end

@implementation CustomClass(MyAddition)

+ (void)addClassMethod {
    NSLog(@"addClassMethod");
}

- (void)addInstanceMethod {
    NSLog(@"addInstanceMethod");
}

- (id)copyWithZone:(NSZone *)zone {
    return nil;
}

@end

運行以下命令,得到一個C++文件

clang -rewrite-objc CustomClass.m

這個文件很長,先看其中的一部分:

static struct _category_t _OBJC_$_CATEGORY_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "CustomClass",
    0, // &OBJC_CLASS_$_CustomClass,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CustomClass_$_MyAddition,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CustomClass_$_MyAddition,
};

可以看到這個就是分類的定義,其本質(zhì)就是category_t,可以在runtime的源碼中找到category_t的定義:

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;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta) {
        if (isMeta) return nil; // classProperties;
        else return instanceProperties;
    }
};

其中定義了分類的名字、類、實例方法、類方法、實現(xiàn)的協(xié)議、屬性。

對應(yīng)起來OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition則保存了所添加的實例方法:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"addInstanceMethod", "v16@0:8", (void *)_I_CustomClass_MyAddition_addInstanceMethod},
    {(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_CustomClass_MyAddition_copyWithZone_}}
};

method_list_t 里面包含了方法的大小、數(shù)目、方法數(shù)組。

OBJC_$_CATEGORY_CLASS_METHODS_CustomClass_$_MyAddition保存了類方法;

OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition是協(xié)議列表:

static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSCopying
};

OBJC_$_PROP_LIST_CustomClass_$_MyAddition屬性列表:

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_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"customProperty","T@\"NSString\",C,N"}}
};

在DATA段我們還可以看到一個保存分類列表的數(shù)組:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_CustomClass_$_MyAddition,
};

Category被附加到類上面是在objc-os.mm的_objc_init方法里發(fā)生的,_objc_init里面的調(diào)用的map_images最終會調(diào)用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的結(jié)尾,有以下的代碼片段:

    // 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、通過_getObjc2CategoryList拿到所有的分類;
2、對每個分類,拿到所屬的類;
3、Register the category with its target class;addUnattachedCategoryForClass(cat, cls, hi);
4、Rebuild the class's method lists (etc) if the class is realized;remethodizeClass(cls);

來看addUnattachedCategoryForClass的源碼:

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertWriting();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}

注釋里說是把分類注冊到類中,其實就是把類和category做一個關(guān)聯(lián)映射。remethodizeClass則真正在把方法添加到類中:

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

一個關(guān)鍵的代碼:auto rw = cls->data();

根據(jù)objc_class的定義我們可以知道rw就是class_rw_t,而class_rw_t里面還包含著class_ro_t。事實上,類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中,而class_ro_t則存儲了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議。所以分類是把方法添加到class_rw_t當(dāng)中的

分類本質(zhì)上就是一個保存了一系列方法的結(jié)構(gòu)體,在運行時分類的方法會被附加到類的方法前面。

KVO原理

KVO即Key-Value-Observer鍵值觀察,是通過isa-swizzling技術(shù)實現(xiàn)的:

  • 1、當(dāng)類A的實例第一次被觀察的時候,系統(tǒng)會在運行期動態(tài)地創(chuàng)建類A的派生類NSKVONotifying_A,其實就是A的子類
  • 2、類NSKVONotifying_A會重寫被觀察的屬性的setter方法,在修改值之前會調(diào)用willChangeValueForKey:方法,修改值之后會調(diào)用didChangeValueForKey:方法,這兩個方法最終都會被調(diào)用到observeValueForKeyPath:ofObject:change:context:方法中;
  • 3、類NSKVONotifying_A重寫class方法,返回類A,類NSKVONotifying_A還會重寫dealloc方法釋放資源;
  • 4、被觀察對象的isa指針從指向原來的 A 類,被 KVO 機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_A類,來實現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽。

KVO的使用其實比較容易崩潰,addObserver和removeObserver需要是成對的,如果重復(fù)remove則會導(dǎo)致NSRangeException類型的Crash,如果忘記remove則會在觀察者釋放后再次接收到KVO回調(diào)時Crash。蘋果官方推薦的方式是,在init的時候進(jìn)行addObserver,在dealloc時removeObserver,這樣可以保證add和remove是成對出現(xiàn)的,是一種比較理想的使用方式。

KVOController是Facebook的一個KVO開源第三方框架,它具有原生KVO所有的功能,但規(guī)避了原生KVO的很多問題,而且支持block回調(diào)。

KVC

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;

先查找setKey或_setKey方法存不存在,如果存在則直接調(diào)用設(shè)值,否則詢問是否可以直接訪問變量+ (BOOL)accessInstanceVariablesDirectly,如果可以則直接設(shè)置,否則報錯

- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;

先查找getKey、key、isKey、_isKey方法存不存在,如果存在則直接調(diào)用,否則詢問是否可以直接訪問變量+ (BOOL)accessInstanceVariablesDirectly,如果可以則直接讀取,否則報錯。

被監(jiān)聽的實例一定有set方法,所以KVC會觸發(fā)KVO。

strong、weak、assign、copy

有一個值為B,它的內(nèi)存地址為A,現(xiàn)在把A賦值給C會發(fā)生什么呢?如下例子:

NSMutableString *C = [[NSMutableString alloc] initWithString:@"B"];

這里的A就是創(chuàng)建字符串@"B"時的值地址。引用計數(shù)的概念就是針對A的,但A賦值給其他變量或者指針設(shè)置為nil,如A=nil,都會導(dǎo)致引用計數(shù)有所增減。當(dāng)內(nèi)容區(qū)域引用計數(shù)為0的時候就會將數(shù)據(jù)B抹除。

使用copy、strong、retain、weak、assign區(qū)別就在:

  • 是否對A有引用計數(shù)增加;
  • 是否開辟新的內(nèi)存。
@property (nonatomic, strong) NSMutableString *strongC;
@property (nonatomic, weak) NSMutableString *weakC;
@property (nonatomic, assign) NSMutableString *assignC;
@property (nonatomic, copy) NSMutableString *copyC;
...

NSMutableString *C = [[NSMutableString alloc] initWithString:@"B"];
self.strongC = C;
self.weakC = C;
self.copyC = C;
self.assignC = C;

strongC會增加A的引用計數(shù);weakC不會增加A的引用計數(shù);copyC也不會增加A的引用計數(shù),它會開辟一塊新的內(nèi)存。

assign和weak類似,都不會增加A的引用計數(shù),但是區(qū)別在于但weak指向的對象被釋放后weak指針會被置為nil,而assign不會,就會導(dǎo)致野指針的問題。但assign修飾基本數(shù)據(jù)類型是安全的,而weak只能修飾對象類型。

retain和strong一樣,會增加A的引用計數(shù),但在MRC時會有差別。

copy NSString NSArray不會開辟新的內(nèi)存,因為它們是不可變變量。

對不可變對象進(jìn)行copy,是淺拷貝;其余的對不可變對象進(jìn)行mutableCopy,對可變對象進(jìn)行copy,對可變對象進(jìn)行mutableCopy都是深拷貝。

copy之后得到的都是不可變對象,mutableCopy之后得到的都是可變對象。

@property (nonatomic, assign) float a;是保存在它所在的對象所存在的堆上面的。

iOS中copy,strong,retain,weak和assign的區(qū)別

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,776評論 0 9
  • 前言 Runtime是什么 Runtime的實現(xiàn)原理消息傳遞機(jī)制Runtime基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)NSObject & i...
    Supremodeamor閱讀 546評論 0 1
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,158評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,232評論 0 7
  • Objective-C語言是一門動態(tài)語言,他將很多靜態(tài)語言在編譯和鏈接時期做的事情放到了運行時來處理。這種動態(tài)語言...
    tigger丨閱讀 1,439評論 0 8