Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Objective-C中的Runtime

Objective-C中的Runtime

編輯:關於Android編程

前言

Runtime是一套比較底層的純C語言API,包含了很多底層的C語言API。在我們平時編寫的OC代碼中,程序運行時,其實最終都是轉成了Runtime的C語言代碼。Runtime是開源的,你可以去這裡下載Runtime的源碼。
本文主要分為兩個章節,第一部分主要是理論和原理,第二部分主要是使用實例。簡書文章地址,文章的最後會附上本文的demo下載鏈接。

理論知識

一、Objective-C中的數據結構

描述Objective-C對象所用的數據結構定義都在Runtime的頭文件裡,下面我們逐一分析。

1.id

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

struct objc_object {
    Class isa;
} *id;

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

2.Class

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

typedef struct objc_class *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
}

下面說下Class的結構體中的幾個主要變量:
* 1.isa:
結構體的首個變量也是isa指針,這說明Class本身也是Objective-C中的對象。
* 2.super_class:
結構體裡還有個變量是super_class,它定義了本類的超類。類對象所屬類型(isa指針所指向的類型)是另外一個類,叫做“元類”。
* 3.ivars:
成員變量列表,類的成員變量都在ivars裡面。
* 4.methodLists:
方法列表,類的實例方法都在methodLists裡,類方法在元類的methodLists裡面。methodLists是一個指針的指針,通過修改該指針指向指針的值,就可以動態的為某一個類添加成員方法。這也就是Category實現的原理,同時也說明了Category只可以為對象添加成員方法,不能添加成員變量。
* 5.cache:
方法緩存列表,objc_msgSend(下文詳解)每調用一次方法後,就會把該方法緩存到cache列表中,下次調用的時候,會優先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。提高效率。

\

看圖說話:

上圖中:superclass指針代表繼承關系,isa指針代表實例所屬的類。
類也是一個對象,它是另外一個類的實例,這個就是“元類”,元類裡面保存了類方法的列表,類裡面保存了實例方法的列表。實例對象的isa指向類,類對象的isa指向元類,元類對象的isa指針指向一個“根元類”(root metaclass)。所有子類的元類都繼承父類的元類,換而言之,類對象和元類對象有著同樣的繼承關系。

1.Class是一個指向objc_class結構體的指針,而id是一個指向objc_object結構體的指針,其中的isa是一個指向objc_class結構體的指針。其中的id就是我們所說的對象,Class就是我們所說的類。
2.isa指針不總是指向實例對象所屬的類,不能依靠它來確定類型,而是應該用isKindOfClass:方法來確定實例對象的類。因為KVO的實現機制就是將被觀察對象的isa指針指向一個中間類而不是真實的類。

3.SEL

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

typedef struct objc_selector *SEL;

它就是個映射到方法的C字符串,SEL類型代表著方法的簽名,在類對象的方法列表中存儲著該簽名與方法代碼的對應關系,每個方法都有一個與之對應的SEL類型的對象,根據一個SEL對象就可以找到方法的地址,進而調用方法。

4.Method

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

typedef struct objc_method *Method;

objc_method的結構體定義如下:

struct objc_method {
    SEL method_name                    OBJC2_UNAVAILABLE;
    char *method_types                 OBJC2_UNAVAILABLE;
    IMP method_imp                     OBJC2_UNAVAILABLE;
}
1.method_name:方法名。 2.method_types:方法類型,主要存儲著方法的參數類型和返回值類型。 3.IMP:方法的實現,函數指針。(下文詳解)

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

5.Ivar

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

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
}

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

6.objc_property_t

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

typedef struct objc_property *objc_property_t;

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

7.IMP

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

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

IMP是一個函數指針,它是由編譯器生成的。當你發起一個消息後,這個函數指針決定了最終執行哪段代碼。

8.Cache

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

typedef struct objc_cache *Cache

objc_cache的定義如下:

struct objc_cache {
    unsigned int mask                   OBJC2_UNAVAILABLE;
    unsigned int occupied               OBJC2_UNAVAILABLE;
    Method buckets[1]                   OBJC2_UNAVAILABLE;
};

每調用一次方法後,不會直接在isa指向的類的方法列表(methodLists)中遍歷查找能夠響應消息的方法,因為這樣效率太低。它會把該方法緩存到cache列表中,下次的時候,就直接優先從cache列表中尋找,如果cache沒有,才從isa指向的類的方法列表(methodLists)中查找方法。提高效率。

二、發送消息(objc_msgSend)

在Objective-C中,調用方法是經常使用的。用Objective-C的術語來說,這叫做“傳遞消息”(pass a message)。消息有“名稱”(name)或者“選擇子”(selector),也可以接受參數,而且可能還有返回值。
如果向某個對象傳遞消息,在底層,所有的方法都是普通的C語言函數,然而對象收到消息之後,究竟該調用哪個方法則完全取決於運行期決定,甚至可能在運行期改變,這些特性使得Objective-C變成一門真正的動態語言。
給對象發送消息可以這樣來寫:

id returnValue = [someObject message:parm];

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

id objc_msgSend (id self, SEL _cmd, ...);

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

編譯器會把上面的那個消息轉換成:

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

傳遞消息的幾種函數:

objc_msgSend:普通的消息都會通過該函數發送。
objc_msgSend_stret:消息中有結構體作為返回值時,通過此函數發送和接收返回值。
objc_msgSend_fpret:消息中返回的是浮點數,可交由此函數處理。
objc_msgSendSuper:和objc_msgSend類似,這裡把消息發送給超類。
objc_msgSendSuper_stret:和objc_msgSend_stret類似,這裡把消息發送給超類。
objc_msgSendSuper_fpret:和objc_msgSend_fpret類似,這裡把消息發送給超類。

編譯器會根據情況選擇一個函數來執行。

objc_msgSend發送消息的原理:
* 第一步:檢測這個selector是不是要被忽略的。
* 第二步:檢測這個target對象是不是nil對象。(nil對象執行任何一個方法都不會Crash,因為會被忽略掉)
* 第三步:首先會根據target對象的isa指針獲取它所對應的類(class)。
* 第四步:優先在類(class)的cache裡面查找與選擇子(selector)名稱相符,如果找不到,再到methodLists查找。
* 第五步:如果沒有在類(class)找到,再到父類(super_class)查找,再到元類(metaclass),直至根metaclass。
* 第六步:一旦找到與選擇子(selector)名稱相符的方法,就跳至其實現代碼。如果沒有找到,就會執行消息轉發(message forwarding)。(下節會詳解)

三、消息轉發(message forwarding)

上面說了消息的傳遞機制,下面就來說一下,如果對象在收到無法解讀的消息之後會發生上面情況。
當一個對象在收到無法解讀的消息之後,它會將消息實施轉發。轉發的主要步驟如下:

消息轉發步驟

第一步:對象在收到無法解讀的消息後,首先調用resolveInstanceMethod:方法決定是否動態添加方法。如果返回YES,則調用class_addMethod動態添加方法,消息得到處理,結束;如果返回NO,則進入下一步; 第二步:當前接收者還有第二次機會處理未知的選擇子,在這一步中,運行期系統會問:能不能把這條消息轉給其他接收者來處理。會進入forwardingTargetForSelector:方法,用於指定備選對象響應這個selector,不能指定為self。如果返回某個對象則會調用對象的方法,結束。如果返回nil,則進入下一步; 第三步:這步我們要通過methodSignatureForSelector:方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進入下一步; 第四步:這步調用forwardInvocation:方法,我們可以通過anInvocation對象做很多處理,比如修改實現方法,修改響應對象等,如果方法調用成功,則結束。如果失敗,則進入doesNotRecognizeSelector方法,拋出異常,此異常表示選擇子最終未能得到處理。
/**
 消息轉發第一步:對象在收到無法解讀的消息後,首先調用此方法,可用於動態添加方法,方法決定是否動態添加方法。如果返回YES,則調用class_addMethod動態添加方法,消息得到處理,結束;如果返回NO,則進入下一步;
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    return NO;
}

/**
 當前接收者還有第二次機會處理未知的選擇子,在這一步中,運行期系統會問:能不能把這條消息轉給其他接收者來處理。會進入此方法,用於指定備選對象響應這個selector,不能指定為self。如果返回某個對象則會調用對象的方法,結束。如果返回nil,則進入下一步;
 */
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}

/**
 這步我們要通過該方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進入下一步。
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"study"])
    {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

/**
 這步調用該方法,我們可以通過anInvocation對象做很多處理,比如修改實現方法,修改響應對象等,如果方法調用成功,則結束。如果失敗,則進入doesNotRecognizeSelector方法。
 */
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation setSelector:@selector(play)];
    [anInvocation invokeWithTarget:self];
}

/**
 拋出異常,此異常表示選擇子最終未能得到處理。
 */
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"無法處理消息:%@", NSStringFromSelector(aSelector));
}

\

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

四、關聯對象 (AssociatedObject)

有時我們需要在對象中存放相關信息,Objective-C中有一種強大的特性可以解決此類問題,就是“關聯對象”。
可以給某個對象關聯許多其他對象,這些對象通過“鍵”來區分。存儲對象值時,可以指明“存儲策略”,用以維護相應地“內存管理語義”。存儲策略由名為“objc_AssociationPolicy” 的枚舉所定義。下表中列出了該枚舉值得取值,同時還列出了與之等下的@property屬性:假如關聯對象成為了屬性,那麼他就會具備對應的語義。

關聯類型 等效的@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)

下列方法可以管理關聯對象:

// 以給定的鍵和策略為某對象設置關聯對象值。
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 根據給定的鍵從某對象中獲取對應的對象值。
id objc_getAssociatedObject(id object, void *key)
// 移除指定對象的全部關聯對象。
void objc_removeAssociatedObjects(id object)

五、方法交換(method swizzing)

在Objective-C中,對象收到消息之後,究竟會調用哪種方法需要在運行期才能解析出來。查找消息的唯一依據是選擇子(selector),選擇子(selector)與相應的方法(IMP)對應,利用Objective-C的動態特性,可以實現在運行時偷換選擇子(selector)對應的方法實現,這就是方法交換(method swizzling)。
這裡寫圖片描述
類的方法列表會把每個選擇子都映射到相關的IMP之上

我們可以新增選擇子,也可以改變某個選擇子所對應的方法實現,還可以交換兩個選擇子所映射到的指針。

Objective-C中提供了三種API來動態替換類方法或實例方法的實現:

1.class_replaceMethod替換類方法的定義。
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
2.method_exchangeImplementations交換兩個方法的實現。
method_exchangeImplementations(Method m1, Method m2)
3.method_setImplementation設置一個方法的實現
method_setImplementation(Method m, IMP imp)

先說下這三個方法的區別:
* class_replaceMethod:當類中沒有想替換的原方法時,該方法調用class_addMethod來為該類增加一個新方法,也正因如此,class_replaceMethod在調用時需要傳入types參數,而其余兩個卻不需要。
* method_exchangeImplementations:內部實現就是調用了兩次method_setImplementation方法。

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

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalSelector = @selector(willMoveToSuperview:);
        SEL swizzledSelector = @selector(myWillMoveToSuperview:);

        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);

        BOOL didAddMethod = class_addMethod(self, 
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(self, 
                                swizzledSelector, 
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)myWillMoveToSuperview:(UIView *)newSuperview
{
    NSLog(@"WillMoveToSuperview: %@", self); 
    [self myWillMoveToSuperview:newSuperview];
}

總結

1.class_replaceMethod,當需要替換的方法有可能不存在時,可以考慮使用該方法。
2.method_exchangeImplementations,當需要交換兩個方法的時使用。
3.method_setImplementation是最簡單的用法,當僅僅需要為一個方法設置其實現方式時實現。

使用實例

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

一、動態的創建一個類

// 創建一個名為People的類,它是NSObject的子類
Class People = objc_allocateClassPair([NSObject class], "People", 0);

// 為該類添加一個eat的方法
class_addMethod(People, NSSelectorFromString(@"eat"), (IMP) eatFun, "v@:");

// 注冊該類
objc_registerClassPair(People);

// 創建一個People的實例對象p
id p = [[People alloc] init];

// 調用eat方法
[p performSelector:@selector(eat)];

二、動態的給某個類添加方法

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if ([NSStringFromSelector(sel) isEqualToString:@"doSomething"])
    {
        class_addMethod(self, sel, (IMP) doSomething, "v@:@");
    }
    return YES;
}

動態的給某個類添加方法,class_addMethod的參數:
self:給哪個類添加方法
sel:添加方法的方法編號(選擇子)
IMP:添加方法的函數實現(函數地址)
types 函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd

三、關聯對象

類別不可以添加屬性,我們可以在類別中設置關聯,舉個栗子:
Person+Category.h 文件

#import "Person.h"

@interface Person (Category)

@property (nonatomic, copy) NSString *name;

@end

Person+Category.m 文件

#import "Person+Category.h"
#import 

@implementation Person (Category)

static char *key;

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, 
                             key, 
                             name,
                             OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, key);
}

@end

當然你也可以這麼寫

Person+Category.m 文件

#import "Person+Category.h"
#import 

@implementation Person (Category)

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, 
                             @selector(name), 
                             name,
                             OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, _cmd);
}

@end

objc_setAssociatedObject和objc_getAssociatedObject傳入的參數key:要求是唯一並且是常量,可以使用static char,然而一個更簡單方便的方法就是:使用選擇子。由於選擇子是唯一並且是常量,你可以使用選擇子作為關聯的key。(PS:_cmd表示當前調用的方法,它就是一個方法選擇器SEL,類似self表示當前對象)

四、方法交換

1.如果我現在想檢查一下項目中有沒有內存循環,怎麼辦?是不是要重寫dealloc函數,看下dealloc有沒有執行,項目小的時候,一個一個controller的寫,還不麻煩,如果項目大,要是一個一個的寫,估計你會瘋掉的。這時候方法交換就派上用場了,你就可以嘗試用自己的方法交換系統的dealloc方法,幾句代碼就搞定了。
#import "UIViewController+Dealloc.h"
#import 

@implementation UIViewController (Dealloc)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
        Method method2 = class_getInstanceMethod(self, @selector(my_dealloc));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)my_dealloc
{
    NSLog(@"%@銷毀了", self);
    [self my_dealloc];
}

@end
2.數組越界,向數組中添加一個nil對象等等,都會造成閃退,我們可以用自己的方法交換數組相對應的方法。下面是一個交換數組addObject:方法的栗子:
#import "NSMutableArray+Category.h"
#import 

@implementation NSMutableArray (Category)

+ (void)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);

        BOOL didAddMethod = class_addMethod(self,
        originalSelector,
        method_getImplementation(swizzledMethod),
        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod)
        {
            class_replaceMethod(self,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        }
        else
        {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)lj_AddObject:(id)object
{
    if (object != nil)
    {
        [self lj_AddObject:object];
    }
}

@end

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

五、歸檔

大家都知道在歸檔的時候,需要先將屬性一個一個的歸檔,然後再將屬性一個一個的解檔,3-5個屬性還好,假如100個怎麼辦,那不得寫累死。有了Runtime,就不用擔心這個了,下面就是如何利用Runtime實現自動歸檔和解檔。
NSObject+Archive.h文件:

#import 

@interface NSObject (Archive)

/**
 *  歸檔
 */
- (void)encode:(NSCoder *)aCoder;

/**
 *  解檔
 */
- (void)decode:(NSCoder *)aDecoder;

/**
 *  這個數組中的成員變量名將會被忽略:不進行歸檔
 */
@property (nonatomic, strong) NSArray *ignoredIvarNames;

@end

NSObject+Archive.m文件:

#import "NSObject+Archive.h"
#import 

@implementation NSObject (Archive)

- (void)encode:(NSCoder *)aCoder
{
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (unsigned int i = 0; i < outCount; i++)
    {
        Ivar ivar = ivars[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        if ([self.ignoredIvarNames containsObject:key])
        {
            continue;
        }
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (void)decode:(NSCoder *)aDecoder
{
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (unsigned int i = 0; i < outCount; i++)
    {
        Ivar ivar = ivars[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        if ([self.ignoredIvarNames containsObject:key])
        {
            continue;
        }
        id value = [aDecoder decodeObjectForKey:key];
        [self setValue:value forKey:key];
    }
    free(ivars);
}

- (void)setIgnoredIvarNames:(NSArray *)ignoredIvarNames
{
    objc_setAssociatedObject(self,
                             @selector(ignoredIvarNames),
                             ignoredIvarNames,
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSArray *)ignoredIvarNames
{
    return objc_getAssociatedObject(self, _cmd);
}

@end

然後再去需要歸檔的類實現文件裡面寫上這幾行代碼:

@implementation Person

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [self encode:aCoder];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init])
    {
        [self decode:aDecoder];
    }
    return self;
}

@end

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

六、字典轉模型

利用Runtime,遍歷模型中所有成員變量,根據模型的屬性名,去字典中查找key,取出對應的value,給模型的屬性賦值,實現的思路主要借鑒MJExtension。

NSObject+Property.h文件:

#import 

@protocol KeyValue 

@optional
/**
 *  數組中需要轉換的模型類
 *
 *  @return 字典中的key是數組屬性名,value是數組中存放模型的Class(Class類型或者NSString類型)
 */
+ (NSDictionary *)objectClassInArray;

/**
 *  將屬性名換為其他key去字典中取值
 *
 *  @return 字典中的key是屬性名,value是從字典中取值用的key
 */
+ (NSDictionary *)replacedKeyFromPropertyName;

@end

@interface NSObject (Property) 

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

@end

NSObject+Property.m文件:

#import "NSObject+Property.h"
#import 

@implementation NSObject (Property)

+ (instancetype)objectWithDictionary:(NSDictionary *)dictionary
{
    id obj = [[self alloc] init];

    // 獲取所有的成員變量
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);

    for (unsigned int i = 0; i < count; i++)
    {
        Ivar ivar = ivars[i];

        // 取出的成員變量,去掉下劃線
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSString *key = [ivarName substringFromIndex:1];

        id value = dictionary[key];

        // 當這個值為空時,判斷一下是否執行了replacedKeyFromPropertyName協議,如果執行了替換原來的key查值
        if (!value)
        {
            if ([self respondsToSelector:@selector(replacedKeyFromPropertyName)])
            {
                NSString *replaceKey = [self replacedKeyFromPropertyName][key];
                value = dictionary[replaceKey];
            }
        }

        // 字典嵌套字典
        if ([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);

            if (modelClass)
            {
                value = [modelClass objectWithDictionary:value];
            }
        }

        // 字典嵌套數組
        if ([value isKindOfClass:[NSArray class]])
        {
            if ([self respondsToSelector:@selector(objectClassInArray)])
            {
                NSMutableArray *models = [NSMutableArray array];

                NSString *type = [self objectClassInArray][key];
                Class classModel = NSClassFromString(type);
                for (NSDictionary *dict in value)
                {
                    id model = [classModel objectWithDictionary:dict];
                    [models addObject:model];
                }
                value = models;
            }
        }

        if (value)
        {
            [obj setValue:value forKey:key];
        }
    }

    // 釋放ivars
    free(ivars);

    return obj;
}

@end
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved