Objective-C中的Runtime

Runtime是一套比較底層的純C語言API,包含了很多底層的C語言API。在我們平時(shí)編寫的OC代碼中,程序運(yùn)行時(shí),其實(shí)最終都是轉(zhuǎn)成了Runtime的C語言代碼。Runtime是開源的,你可以去這里下載Runtime的源碼。

本文主要分為兩個(gè)章節(jié),第一部分主要是理論和原理,第二部分主要是使用實(shí)例。簡書文章地址,文章的最后會附上本文的demo下載鏈接。

一、Objective-C中的數(shù)據(jù)結(jié)構(gòu)

描述Objective-C對象所用的數(shù)據(jù)結(jié)構(gòu)定義都在Runtime的頭文件里,下面我們逐一分析。

運(yùn)行期系統(tǒng)如何知道某個(gè)對象的類型呢?對象類型并不是在編譯期就知道了,而是要在運(yùn)行期查找。Objective-C有個(gè)特殊的類型id,它可以表示Objective-C的任意對象類型,id類型定義在Runtime的頭文件中:

struct objc_object {

Class isa;

} *;

由此可見,每個(gè)對象結(jié)構(gòu)體的首個(gè)成員是Class類的變量。該變量定義了對象所屬的類,通常稱為isa指針。

2.Class

Class對象也定義在Runtime的頭文件中:

typedef struct objc_class *Class;

struct objc_class {

Class isa? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC_ISA_AVAILABILITY;

# !__OBJC2__

Class super_class? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

const? *name? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

version? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

info? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

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

}

下面說下Class的結(jié)構(gòu)體中的幾個(gè)主要變量:

* 1.isa:

結(jié)構(gòu)體的首個(gè)變量也是isa指針,這說明Class本身也是Objective-C中的對象。

* 2.super_class:

結(jié)構(gòu)體里還有個(gè)變量是super_class,它定義了本類的超類。類對象所屬類型(isa指針?biāo)赶虻念愋停┦橇硗庖粋€(gè)類,叫做“元類”。

* 3.ivars:

成員變量列表,類的成員變量都在ivars里面。

* 4.methodLists:

方法列表,類的實(shí)例方法都在methodLists里,類方法在元類的methodLists里面。methodLists是一個(gè)指針的指針,通過修改該指針指向指針的值,就可以動態(tài)的為某一個(gè)類添加成員方法。這也就是Category實(shí)現(xiàn)的原理,同時(shí)也說明了Category只可以為對象添加成員方法,不能添加成員變量。

* 5.cache:

方法緩存列表,objc_msgSend(下文詳解)每調(diào)用一次方法后,就會把該方法緩存到cache列表中,下次調(diào)用的時(shí)候,會優(yōu)先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。提高效率??磮D說話:


上圖中:superclass指針代表繼承關(guān)系,isa指針代表實(shí)例所屬的類。

類也是一個(gè)對象,它是另外一個(gè)類的實(shí)例,這個(gè)就是“元類”,元類里面保存了類方法的列表,類里面保存了實(shí)例方法的列表。實(shí)例對象的isa指向類,類對象的isa指向元類,元類對象的isa指針指向一個(gè)“根元類”(root metaclass)。所有子類的元類都繼承父類的元類,換而言之,類對象和元類對象有著同樣的繼承關(guān)系。

1.Class是一個(gè)指向objc_class結(jié)構(gòu)體的指針,而id是一個(gè)指向objc_object結(jié)構(gòu)體的指針,其中的isa是一個(gè)指向objc_class結(jié)構(gòu)體的指針。其中的id就是我們所說的對象,Class就是我們所說的類。

2.isa指針不總是指向?qū)嵗龑ο笏鶎俚念?,不能依靠它來確定類型,而是應(yīng)該用isKindOfClass:方法來確定實(shí)例對象的類。因?yàn)镵VO的實(shí)現(xiàn)機(jī)制就是將被觀察對象的isa指針指向一個(gè)中間類而不是真實(shí)的類。

3.SEL

SEL是選擇子的類型,選擇子指的就是方法的名字。在Runtime的頭文件中的定義如下:

typedef struct objc_selector *SEL;

它就是個(gè)映射到方法的C字符串,SEL類型代表著方法的簽名,在類對象的方法列表中存儲著該簽名與方法代碼的對應(yīng)關(guān)系,每個(gè)方法都有一個(gè)與之對應(yīng)的SEL類型的對象,根據(jù)一個(gè)SEL對象就可以找到方法的地址,進(jìn)而調(diào)用方法。

4.Method

Method代表類中的某個(gè)方法的類型,在Runtime的頭文件中的定義如下:

typedef struct objc_method *Method;

objc_method的結(jié)構(gòu)體定義如下:

struct objc_method {

SEL method_name? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

*method_types? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

IMP method_imp? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

}

1.method_name:方法名。

2.method_types:方法類型,主要存儲著方法的參數(shù)類型和返回值類型。

3.IMP:方法的實(shí)現(xiàn),函數(shù)指針。(下文詳解)

class_copyMethodList(Class cls, unsigned int *outCount)可以使用這個(gè)方法獲取某個(gè)類的成員方法列表。

5.Ivar

Ivar代表類中實(shí)例變量的類型,在Runtime的頭文件中的定義如下:

typedef struct objc_ivar *Ivar;

objc_ivar的定義如下:

struct objc_ivar {

*ivar_name? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

*ivar_type? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

ivar_offset? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

#ifdef __LP64__

space? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

#endif

}

class_copyIvarList(Class cls, unsigned int *outCount)可以使用這個(gè)方法獲取某個(gè)類的成員變量列表。

6.objc_property_t

objc_property_t是屬性,在Runtime的頭文件中的的定義如下:

typedef struct objc_property *objc_property_t;

class_copyPropertyList(Class cls, unsigned int *outCount)可以使用這個(gè)方法獲取某個(gè)類的屬性列表。

7.IMP

IMP在Runtime的頭文件中的的定義如下:

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

IMP是一個(gè)函數(shù)指針,它是由編譯器生成的。當(dāng)你發(fā)起一個(gè)消息后,這個(gè)函數(shù)指針決定了最終執(zhí)行哪段代碼。

8.Cache

Cache在Runtime的頭文件中的的定義如下:

typedef struct objc_cache *Cache

objc_cache的定義如下:

struct objc_cache {

unsigned? mask? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

unsigned? occupied? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

Method buckets[]? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

};

每調(diào)用一次方法后,不會直接在isa指向的類的方法列表(methodLists)中遍歷查找能夠響應(yīng)消息的方法,因?yàn)檫@樣效率太低。它會把該方法緩存到cache列表中,下次的時(shí)候,就直接優(yōu)先從cache列表中尋找,如果cache沒有,才從isa指向的類的方法列表(methodLists)中查找方法。提高效率。

二、發(fā)送消息(objc_msgSend)

在Objective-C中,調(diào)用方法是經(jīng)常使用的。用Objective-C的術(shù)語來說,這叫做“傳遞消息”(pass a message)。消息有“名稱”(name)或者“選擇子”(selector),也可以接受參數(shù),而且可能還有返回值。

如果向某個(gè)對象傳遞消息,在底層,所有的方法都是普通的C語言函數(shù),然而對象收到消息之后,究竟該調(diào)用哪個(gè)方法則完全取決于運(yùn)行期決定,甚至可能在運(yùn)行期改變,這些特性使得Objective-C變成一門真正的動態(tài)語言。

給對象發(fā)送消息可以這樣來寫:

id returnValue = [someObject message:parm];

someObject叫做“接收者”(receiver),message是“選擇子”(selector),選擇子和參數(shù)結(jié)合起來就叫做“消息”(message)。編譯器看到此消息后,將其轉(zhuǎn)換成C語言函數(shù)調(diào)用,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù),叫做objc_msgSend,其原型如下:

objc_msgSend ( , SEL _cmd, ...);

后面的…表示這是個(gè)“參數(shù)個(gè)數(shù)可變的函數(shù)”,能接受兩個(gè)或兩個(gè)以上的參數(shù)。第一個(gè)參數(shù)是接收者(receiver),第二個(gè)參數(shù)是選擇子(selector),后續(xù)參數(shù)就是消息中傳遞的那些參數(shù)(parm),其順序不變。

編譯器會把上面的那個(gè)消息轉(zhuǎn)換成:

returnValue objc_mgSend(someObject, @selector(message:), parm);

傳遞消息的幾種函數(shù):

objc_msgSend:普通的消息都會通過該函數(shù)發(fā)送。

objc_msgSend_stret:消息中有結(jié)構(gòu)體作為返回值時(shí),通過此函數(shù)發(fā)送和接收返回值。

objc_msgSend_fpret:消息中返回的是浮點(diǎn)數(shù),可交由此函數(shù)處理。

objc_msgSendSuper:和objc_msgSend類似,這里把消息發(fā)送給超類。

objc_msgSendSuper_stret:和objc_msgSend_stret類似,這里把消息發(fā)送給超類。

objc_msgSendSuper_fpret:和objc_msgSend_fpret類似,這里把消息發(fā)送給超類。

編譯器會根據(jù)情況選擇一個(gè)函數(shù)來執(zhí)行。

objc_msgSend發(fā)送消息的原理:

* 第一步:檢測這個(gè)selector是不是要被忽略的。

* 第二步:檢測這個(gè)target對象是不是nil對象。(nil對象執(zhí)行任何一個(gè)方法都不會Crash,因?yàn)闀缓雎缘簦?/p>

* 第三步:首先會根據(jù)target對象的isa指針獲取它所對應(yīng)的類(class)。

* 第四步:優(yōu)先在類(class)的cache里面查找與選擇子(selector)名稱相符,如果找不到,再到methodLists查找。

* 第五步:如果沒有在類(class)找到,再到父類(super_class)查找,再到元類(metaclass),直至根metaclass。

* 第六步:一旦找到與選擇子(selector)名稱相符的方法,就跳至其實(shí)現(xiàn)代碼。如果沒有找到,就會執(zhí)行消息轉(zhuǎn)發(fā)(message forwarding)。(下節(jié)會詳解)

三、消息轉(zhuǎn)發(fā)(message forwarding)

上面說了消息的傳遞機(jī)制,下面就來說一下,如果對象在收到無法解讀的消息之后會發(fā)生上面情況。

當(dāng)一個(gè)對象在收到無法解讀的消息之后,它會將消息實(shí)施轉(zhuǎn)發(fā)。轉(zhuǎn)發(fā)的主要步驟如下:

消息轉(zhuǎn)發(fā)步驟

第一步:對象在收到無法解讀的消息后,首先調(diào)用resolveInstanceMethod:方法決定是否動態(tài)添加方法。如果返回YES,則調(diào)用class_addMethod動態(tài)添加方法,消息得到處理,結(jié)束;如果返回NO,則進(jìn)入下一步;

第二步:當(dāng)前接收者還有第二次機(jī)會處理未知的選擇子,在這一步中,運(yùn)行期系統(tǒng)會問:能不能把這條消息轉(zhuǎn)給其他接收者來處理。會進(jìn)入forwardingTargetForSelector:方法,用于指定備選對象響應(yīng)這個(gè)selector,不能指定為self。如果返回某個(gè)對象則會調(diào)用對象的方法,結(jié)束。如果返回nil,則進(jìn)入下一步;

第三步:這步我們要通過methodSignatureForSelector:方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進(jìn)入下一步;

第四步:這步調(diào)用forwardInvocation:方法,我們可以通過anInvocation對象做很多處理,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對象等,如果方法調(diào)用成功,則結(jié)束。如果失敗,則進(jìn)入doesNotRecognizeSelector方法,拋出異常,此異常表示選擇子最終未能得到處理。

/**

消息轉(zhuǎn)發(fā)第一步:對象在收到無法解讀的消息后,首先調(diào)用此方法,可用于動態(tài)添加方法,方法決定是否動態(tài)添加方法。如果返回YES,則調(diào)用class_addMethod動態(tài)添加方法,消息得到處理,結(jié)束;如果返回NO,則進(jìn)入下一步;

*/

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

return NO;

}

/**

當(dāng)前接收者還有第二次機(jī)會處理未知的選擇子,在這一步中,運(yùn)行期系統(tǒng)會問:能不能把這條消息轉(zhuǎn)給其他接收者來處理。會進(jìn)入此方法,用于指定備選對象響應(yīng)這個(gè)selector,不能指定為self。如果返回某個(gè)對象則會調(diào)用對象的方法,結(jié)束。如果返回nil,則進(jìn)入下一步;

*/

- (id)forwardingTargetForSelector:(SEL)aSelector

{

return nil;

}

/**

這步我們要通過該方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進(jìn)入下一步。

*/

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

([NSStringFromSelector(aSelector) isEqualToString:@"study"])

{

return [NSMethodSignature signatureWithObjCTypes:"v@:"];

}

return [super methodSignatureForSelector:aSelector];

}

/**

這步調(diào)用該方法,我們可以通過anInvocation對象做很多處理,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對象等,如果方法調(diào)用成功,則結(jié)束。如果失敗,則進(jìn)入doesNotRecognizeSelector方法。

*/

- ()forwardInvocation:(NSInvocation *)anInvocation

{

[anInvocation setSelector:@selector(play)];

[anInvocation invokeWithTarget:self];

}

/**

拋出異常,此異常表示選擇子最終未能得到處理。

*/

- ()doesNotRecognizeSelector:(SEL)aSelector

{

NSLog(@"無法處理消息:%@", NSStringFromSelector(aSelector));

}



接收者在每一步中均有機(jī)會處理消息,步驟越靠后,處理消息的代價(jià)越大。最好在第一步就能處理完,這樣系統(tǒng)就可以把此方法緩存起來了。

四、關(guān)聯(lián)對象 (AssociatedObject)

有時(shí)我們需要在對象中存放相關(guān)信息,Objective-C中有一種強(qiáng)大的特性可以解決此類問題,就是“關(guān)聯(lián)對象”。

可以給某個(gè)對象關(guān)聯(lián)許多其他對象,這些對象通過“鍵”來區(qū)分。存儲對象值時(shí),可以指明“存儲策略”,用以維護(hù)相應(yīng)地“內(nèi)存管理語義”。存儲策略由名為“objc_AssociationPolicy” 的枚舉所定義。下表中列出了該枚舉值得取值,同時(shí)還列出了與之等下的@property屬性:假如關(guān)聯(lián)對象成為了屬性,那么他就會具備對應(yīng)的語義。

關(guān)聯(lián)類型等效的@property屬性

OBJC_ASSOCIATION_ASSIGN@property (assign) or @ property (unsafe_unretained)

OBJC_ASSOCIATION_RETAIN_NONATOMIC@property (nonatomic, strong)

OBJC_ASSOCIATION_COPY_NONATOMIC@property (nonatomic, copy)

OBJC_ASSOCIATION_RETAIN@property (atomic, strong)

OBJC_ASSOCIATION_COPY@property (atomic, copy)

下列方法可以管理關(guān)聯(lián)對象:

// 以給定的鍵和策略為某對象設(shè)置關(guān)聯(lián)對象值。

objc_setAssociatedObject( object, const? *key,? value, objc_AssociationPolicy policy)

// 根據(jù)給定的鍵從某對象中獲取對應(yīng)的對象值。

objc_getAssociatedObject( object,? *key)

// 移除指定對象的全部關(guān)聯(lián)對象。

objc_removeAssociatedObjects( object)

五、方法交換(method swizzing)

在Objective-C中,對象收到消息之后,究竟會調(diào)用哪種方法需要在運(yùn)行期才能解析出來。查找消息的唯一依據(jù)是選擇子(selector),選擇子(selector)與相應(yīng)的方法(IMP)對應(yīng),利用Objective-C的動態(tài)特性,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換選擇子(selector)對應(yīng)的方法實(shí)現(xiàn),這就是方法交換(method swizzling)。


類的方法列表會把每個(gè)選擇子都映射到相關(guān)的IMP之上

我們可以新增選擇子,也可以改變某個(gè)選擇子所對應(yīng)的方法實(shí)現(xiàn),還可以交換兩個(gè)選擇子所映射到的指針。

Objective-C中提供了三種API來動態(tài)替換類方法或?qū)嵗椒ǖ膶?shí)現(xiàn):

1.class_replaceMethod替換類方法的定義。

class_replaceMethod(Class cls, SEL name, IMP imp, const? *types)

2.method_exchangeImplementations交換兩個(gè)方法的實(shí)現(xiàn)。

method_exchangeImplementations(Method m1, Method m2)

3.method_setImplementation設(shè)置一個(gè)方法的實(shí)現(xiàn)

method_Implementation(Method m, IMP imp)

先說下這三個(gè)方法的區(qū)別:

*class_replaceMethod:當(dāng)類中沒有想替換的原方法時(shí),該方法調(diào)用class_addMethod來為該類增加一個(gè)新方法,也正因如此,class_replaceMethod在調(diào)用時(shí)需要傳入types參數(shù),而其余兩個(gè)卻不需要。

*method_exchangeImplementations:內(nèi)部實(shí)現(xiàn)就是調(diào)用了兩次method_setImplementation方法。

再來看看他們的使用場景:

+ ()load

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

SEL originalSelector = @selector(willMoveToSuperview:);

SEL swizzledSelector = @selector(myWillMoveToSuperview:);

Method originalMethod = class_getInstanceMethod(, originalSelector);

Method swizzledMethod = class_getInstanceMethod(, swizzledSelector);

didAddMethod = class_addMethod(,

originalSelector,

method_getImplementation(swizzledMethod),

method_getTypeEncoding(swizzledMethod));

(didAddMethod) {

class_replaceMethod(,

swizzledSelector,

method_getImplementation(originalMethod),

method_getTypeEncoding(originalMethod));

}? {

method_exchangeImplementations(originalMethod, swizzledMethod);

}

});

}

- ()myWillMoveToSuperview:(UIView *)newSuperview

{

NSLog(@"WillMoveToSuperview: %@", );

[ myWillMoveToSuperview:newSuperview];

}

1.class_replaceMethod,當(dāng)需要替換的方法有可能不存在時(shí),可以考慮使用該方法。

2.method_exchangeImplementations,當(dāng)需要交換兩個(gè)方法的時(shí)使用。

3.method_setImplementation是最簡單的用法,當(dāng)僅僅需要為一個(gè)方法設(shè)置其實(shí)現(xiàn)方式時(shí)實(shí)現(xiàn)。

前面講的全部是理論知識,比較枯燥,下面說一些實(shí)際的栗子。

一、動態(tài)的創(chuàng)建一個(gè)類

// 創(chuàng)建一個(gè)名為People的類,它是NSObject的子類

Class People = objc_allocateClassPair([NSObject class], "People", );

// 為該類添加一個(gè)eat的方法

class_addMethod(People, NSSelectorFromString(@"eat"), (IMP) eatFun, "v@:");

// 注冊該類

objc_registerClassPair(People);

// 創(chuàng)建一個(gè)People的實(shí)例對象p

p = [[People alloc] init];

// 調(diào)用eat方法

[p performSelector:@selector(eat)];

二、動態(tài)的給某個(gè)類添加方法

+ ()resolveInstanceMethod:(SEL)sel

{

([NSStringFromSelector(sel) isEqualToString:@"doSomething"])

{

class_addMethod(, sel, (IMP) doSomething, "v@:@");

}

return ;

}

動態(tài)的給某個(gè)類添加方法,class_addMethod的參數(shù):

self:給哪個(gè)類添加方法

sel:添加方法的方法編號(選擇子)

IMP:添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)

types 函數(shù)的類型,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->_cmd

三、關(guān)聯(lián)對象

類別不可以添加屬性,我們可以在類別中設(shè)置關(guān)聯(lián),舉個(gè)栗子:

Person+Category.h 文件

#import "Person.h"

@interface Person (Category)

@property (nonatomic, copy) NSString *name;

Person+Category.m 文件

#import "Person+Category.h"

#import

@implementation Person (Category)

static? *key;

- ()setName:(NSString *)name

{

objc_setAssociatedObject(,

key,

name,

OBJC_ASSOCIATION_COPY_NONATOMIC);

}

- (NSString *)name

{

return objc_getAssociatedObject(, key);

}

當(dāng)然你也可以這么寫

Person+Category.m 文件

#import "Person+Category.h"

#import

@implementation Person (Category)

- ()setName:(NSString *)name

{

objc_setAssociatedObject(,

@selector(name),

name,

OBJC_ASSOCIATION_COPY_NONATOMIC);

}

- (NSString *)name

{

return objc_getAssociatedObject(, _cmd);

}

objc_setAssociatedObject和objc_getAssociatedObject傳入的參數(shù)key:要求是唯一并且是常量,可以使用static char,然而一個(gè)更簡單方便的方法就是:使用選擇子。由于選擇子是唯一并且是常量,你可以使用選擇子作為關(guān)聯(lián)的key。(PS:_cmd表示當(dāng)前調(diào)用的方法,它就是一個(gè)方法選擇器SEL,類似self表示當(dāng)前對象)

四、方法交換

1.如果我現(xiàn)在想檢查一下項(xiàng)目中有沒有內(nèi)存循環(huán),怎么辦?是不是要重寫dealloc函數(shù),看下dealloc有沒有執(zhí)行,項(xiàng)目小的時(shí)候,一個(gè)一個(gè)controller的寫,還不麻煩,如果項(xiàng)目大,要是一個(gè)一個(gè)的寫,估計(jì)你會瘋掉的。這時(shí)候方法交換就派上用場了,你就可以嘗試用自己的方法交換系統(tǒng)的dealloc方法,幾句代碼就搞定了。

#import "UIViewController+Dealloc.h"

#import

@implementation UIViewController (Dealloc)

+ ()load

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Method method1 = class_getInstanceMethod(, NSSelectorFromString(@"dealloc"));

Method method2 = class_getInstanceMethod(, @selector(my_dealloc));

method_exchangeImplementations(method1, method2);

});

}

- ()my_dealloc

{

NSLog(@"%@銷毀了", );

[ my_dealloc];

}

2.數(shù)組越界,向數(shù)組中添加一個(gè)nil對象等等,都會造成閃退,我們可以用自己的方法交換數(shù)組相對應(yīng)的方法。下面是一個(gè)交換數(shù)組addObject:方法的栗子:

#import "NSMutableArray+Category.h"

#import

@implementation NSMutableArray (Category)

+ ()load

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

SEL originalSelector = @selector(addObject:);

SEL swizzledSelector = @selector(lj_AddObject:);

// NSMutableArray是類簇,真正的類名是__NSArrayM

Method originalMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), originalSelector);

Method swizzledMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), swizzledSelector);

didAddMethod = class_addMethod(,

originalSelector,

method_getImplementation(swizzledMethod),

method_getTypeEncoding(swizzledMethod));

(didAddMethod)

{

class_replaceMethod(,

swizzledSelector,

method_getImplementation(originalMethod),

method_getTypeEncoding(originalMethod));

}

{

method_exchangeImplementations(originalMethod, swizzledMethod);

}

});

}

- ()lj_AddObject:()object

{

(object != )

{

[ lj_AddObject:object];

}

}

PS:我不太建議大家平時(shí)開發(fā)的時(shí)候使用這類數(shù)組安全操作的做法,不利于代碼的調(diào)試,如果真的加入了nil對象,你可能就不會那么容易找出問題在哪,還是在項(xiàng)目發(fā)布的時(shí)候使用比較合適。

大家都知道在歸檔的時(shí)候,需要先將屬性一個(gè)一個(gè)的歸檔,然后再將屬性一個(gè)一個(gè)的解檔,3-5個(gè)屬性還好,假如100個(gè)怎么辦,那不得寫累死。有了Runtime,就不用擔(dān)心這個(gè)了,下面就是如何利用Runtime實(shí)現(xiàn)自動歸檔和解檔。

NSObject+Archive.h文件:

#import

@interface NSObject (Archive)

/**

*? 歸檔

*/

- ()encode:(NSCoder *)aCoder;

/**

*? 解檔

*/

- ()decode:(NSCoder *)aDecoder;

/**

*? 這個(gè)數(shù)組中的成員變量名將會被忽略:不進(jìn)行歸檔

*/

@property (nonatomic, strong) NSArray *ignoredIvarNames;

NSObject+Archive.m文件:

#import "NSObject+Archive.h"

#import

@implementation NSObject (Archive)

- ()encode:(NSCoder *)aCoder

{

unsigned? outCount = ;

Ivar *ivars = class_copyIvarList([ class], &outCount);

(unsigned? i = ; i < outCount; i++)

{

Ivar ivar = ivars[i];

NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

([.ignoredIvarNames containsObject:key])

{

continue;

}

value = [ valueForKey:key];

[aCoder encodeObject:value forKey:key];

}

free(ivars);

}

- ()decode:(NSCoder *)aDecoder

{

unsigned? outCount = ;

Ivar *ivars = class_copyIvarList([ class], &outCount);

(unsigned? i = ; i < outCount; i++)

{

Ivar ivar = ivars[i];

NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

([.ignoredIvarNames containsObject:key])

{

continue;

}

value = [aDecoder decodeObjectForKey:key];

[ setValue:value forKey:key];

}

free(ivars);

}

- ()setIgnoredIvarNames:(NSArray *)ignoredIvarNames

{

objc_setAssociatedObject(,

@selector(ignoredIvarNames),

ignoredIvarNames,

OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

- (NSArray *)ignoredIvarNames

{

return objc_getAssociatedObject(, _cmd);

}

然后再去需要?dú)w檔的類實(shí)現(xiàn)文件里面寫上這幾行代碼:

@implementation Person

- ()encodeWithCoder:(NSCoder *)aCoder

{

[ encode:aCoder];

}

- (instancetype)initWithCoder:(NSCoder *)aDecoder

{

( = [super init])

{

[ decode:aDecoder];

}

return ;

}

這幾行代碼都是固定寫法,你也可以把它們定義成宏,這樣就可以實(shí)現(xiàn)一行代碼就歸檔了,思路源自MJExtension!

六、字典轉(zhuǎn)模型

利用Runtime,遍歷模型中所有成員變量,根據(jù)模型的屬性名,去字典中查找key,取出對應(yīng)的value,給模型的屬性賦值,實(shí)現(xiàn)的思路主要借鑒MJExtension。

NSObject+Property.h文件:

#import

@protocol KeyValue

@optional

/**

*? 數(shù)組中需要轉(zhuǎn)換的模型類

*

*? @return 字典中的key是數(shù)組屬性名,value是數(shù)組中存放模型的Class(Class類型或者NSString類型)

*/

+ (NSDictionary *)objectClassInArray;

/**

*? 將屬性名換為其他key去字典中取值

*

*? @return 字典中的key是屬性名,value是從字典中取值用的key

*/

+ (NSDictionary *)replacedKeyFromPropertyName;

@interface NSObject (Property)

+ (instancetype)objectWithDictionary:(NSDictionary *)dictionary;

NSObject+Property.m文件:

#import "NSObject+Property.h"

#import

@implementation NSObject (Property)

+ (instancetype)objectWithDictionary:(NSDictionary *)dictionary

{

obj = [[ alloc] init];

// 獲取所有的成員變量

unsigned? count;

Ivar *ivars = class_copyIvarList(, &count);

(unsigned? i = ; i < count; i++)

{

Ivar ivar = ivars[i];

// 取出的成員變量,去掉下劃線

NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

NSString *key = [ivarName substringFromIndex:];

value = dictionary[key];

// 當(dāng)這個(gè)值為空時(shí),判斷一下是否執(zhí)行了replacedKeyFromPropertyName協(xié)議,如果執(zhí)行了替換原來的key查值

(!value)

{

([ respondsToSelector:@selector(replacedKeyFromPropertyName)])

{

NSString *replaceKey = [ replacedKeyFromPropertyName][key];

value = dictionary[replaceKey];

}

}

// 字典嵌套字典

([value isKindOfClass:[NSDictionary class]])

{

NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

NSRange range = [type rangeOfString:@];

type = [type substringFromIndex:range.location + range.length];

range = [type rangeOfString:@];

type = [type substringToIndex:range.location];

Class modelClass = NSClassFromString(type);

(modelClass)

{

value = [modelClass objectWithDictionary:value];

}

}

// 字典嵌套數(shù)組

([value isKindOfClass:[NSArray class]])

{

([ respondsToSelector:@selector(objectClassInArray)])

{

NSMutableArray *models = [NSMutableArray array];

NSString *type = [ objectClassInArray][key];

Class classModel = NSClassFromString(type);

(NSDictionary *dict in value)

{

model = [classModel objectWithDictionary:dict];

[models addObject:model];

}

value = models;

}

}

(value)

{

[obj setValue:value forKey:key];

}

}

// 釋放ivars

free(ivars);

return obj;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個(gè)「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,351評論 0 7
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 842評論 0 2
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,098評論 0 9
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 869評論 0 4
  • 原文出處:南峰子的技術(shù)博客 Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時(shí)期做的事放到了...
    _燴面_閱讀 1,442評論 1 5

友情鏈接更多精彩內(nèi)容