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