Runtime 應用

參考文章:

1、Objctive-C Runtime
2、梧雨北辰
3概而、jackyshan
4、人仙兒a
本文主要是參考梧雨北辰的文章囱修,并在該作者的文章之上添加自己理解的內(nèi)容赎瑰。侵權(quán)必刪

Runtime應用框架.png

1破镰、方法魔法(Method Swizzling)

實現(xiàn)動態(tài)方法交換(Method Swizzling )是Runtime中最具盛名的應用場景餐曼,其原理是:通過Runtime獲取到方法實現(xiàn)的地址,進而動態(tài)交換兩個方法的功能鲜漩。使用到關鍵方法如下:

///獲取類方法的Mthod
Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
///獲取實例對象方法的Mthod
Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
///交換兩個方法的實現(xiàn)
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)

1.1 動態(tài)方法交換示例

- (void)printA{
    NSLog(@"打印A......");
}

- (void)printB{
    NSLog(@"打印B......");
}

//交換方法的實現(xiàn)源譬,并測試打印
Method methodA = class_getInstanceMethod([self class], @selector(printA));
Method methodB = class_getInstanceMethod([self class], @selector(printB));
method_exchangeImplementations(methodA, methodB);

[self printA];  ///打印B......
[self printB];  ///打印A......

1.2 攔截并替換系統(tǒng)方法

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(jkviewDidLoad);
        
        Method originalMethod = class_getInstanceMethod(class,originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
        
        //judge the method named  swizzledMethod is already existed.
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        // if swizzledMethod is already existed.
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)jkviewDidLoad {
    NSLog(@"替換的方法");
    
    [self jkviewDidLoad];
}

- (void)viewDidLoad {
    NSLog(@"自帶的方法");
    
    [super viewDidLoad];
}

@end

swizzling應該只在+load中完成。 在 Objective-C 的運行時中孕似,每個類有兩個方法都會自動調(diào)用踩娘。+load 是在一個類被初始裝載時調(diào)用,+initialize 是在應用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的喉祭。兩個方法都是可選的养渴,并且只有在方法被實現(xiàn)的情況下才會被調(diào)用。

swizzling應該只在dispatch_once 中完成,由于swizzling 改變了全局的狀態(tài)泛烙,所以我們需要確保每個預防措施在運行時都是可用的理卑。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次胶惰。Grand Central Dispatchdispatch_once滿足了所需要的需求傻工,并且應該被當做使用swizzling 的初始化單例方法的標準。

1.3 KVO實現(xiàn)

全稱是Key-value observing孵滞,翻譯成鍵值觀察中捆。提供了一種當其它對象屬性被修改的時候能通知當前對象的機制。再MVC大行其道的Cocoa中坊饶,KVO機制很適合實現(xiàn)modelcontroller類之間的通訊泄伪。

KVO的實現(xiàn)依賴于 Objective-C 強大的 Runtime,當觀察某對象 A 時匿级,KVO 機制動態(tài)創(chuàng)建一個對象A當前類的子類蟋滴,并為這個新的子類重寫了被觀察屬性 keyPathsetter 方法。setter 方法隨后負責通知觀察對象屬性的改變狀況痘绎。

Apple 使用了 isa-swizzling 來實現(xiàn) KVO 津函。當觀察對象A時,KVO機制動態(tài)創(chuàng)建一個新的名為:NSKVONotifying_A 的新類孤页,該類繼承自對象A的本類尔苦,且 KVO 為 NSKVONotifying_A 重寫觀察屬性的 setter 方法,setter 方法會負責在調(diào)用原 setter方法之前和之后,通知所有觀察對象屬性值的更改情況允坚。

我們通過例子來驗證一下魂那,首先我們檢測一個類的

NSLog(@"kvo之前 self -> isa: %@",object_getClass(runtime));
NSLog(@"kvo之前 self class : %@",[runtime class]);

測試代碼如下:

RuntimeTestManager *runtime = [[RuntimeTestManager alloc] init];
runtime.name = @"zhangsan";
NSLog(@"kvo之前 self -> isa: %@",object_getClass(runtime));
NSLog(@"kvo之前 self class : %@",[runtime class]);
    
[runtime addObserver:self forKeyPath:@"_name" options:NSKeyValueObservingOptionInitial context:nil];
_runtimeManager = runtime;
    
NSLog(@"kvo之后 self -> isa: %@",object_getClass(runtime));
NSLog(@"kvo之后 self class : %@",[runtime class]);

測試結(jié)果為:

2018-11-27 17:19:43.221175+0800 Runtime[7499:2757536] kvo之前 self -> isa: RuntimeTestManager
2018-11-27 17:19:43.221217+0800 Runtime[7499:2757536] kvo之前 self class : RuntimeTestManager
2018-11-27 17:19:43.221441+0800 Runtime[7499:2757536] kvo之后 self -> isa: NSKVONotifying_RuntimeTestManager
2018-11-27 17:19:43.221460+0800 Runtime[7499:2757536] kvo之后 self class : RuntimeTestManager

在這個過程,被觀察對象的 isa 指針從指向原來的 RuntimeTestManager 類稠项,被KVO 機制修改為指向系統(tǒng)新創(chuàng)建的子類NSKVONotifying_ RuntimeTestManager類涯雅,來實現(xiàn)當前類屬性值改變的監(jiān)聽;

所以當我們從應用層面上看來展运,完全沒有意識到有新的類出現(xiàn)活逆,這是系統(tǒng)“隱瞞”了對 KVO 的底層實現(xiàn)過程,讓我們誤以為還是原來的類乐疆。但是此時如果我們創(chuàng)建一個新的名為NSKVONotifying_ RuntimeTestManager的類划乖,就會發(fā)現(xiàn)系統(tǒng)運行到注冊 KVO 的那段代碼時程序就崩潰,因為系統(tǒng)在注冊監(jiān)聽的時候動態(tài)創(chuàng)建了名為 NSKVONotifying_ RuntimeTestManager 的中間類挤土,并指向這個中間類了琴庵。

子類setter方法剖析

KVO 的鍵值觀察通知依賴于 NSObject 的兩個法:willChangeValueForKey:didChangeValueForKey:,在存取數(shù)值的前后分別調(diào)用 2 個方法:被觀察屬性發(fā)生改變之前仰美,willChangeValueForKey:被調(diào)用迷殿,通知系統(tǒng)該 keyPath 的屬性值即將變更;當改變發(fā)生后咖杂, didChangeValueForKey: 被調(diào)用庆寺,通知系統(tǒng)該keyPath 的屬性值已經(jīng)變更;之后诉字,observeValueForKey:ofObject:change:context:也會被調(diào)用懦尝。且重寫觀察屬性的setter 方法這種繼承方式的注入是在運行時而不是編譯時實現(xiàn)的。KVO 為子類的觀察者屬性重寫調(diào)用存取方法的工作原理在代碼中相當于:

- (void)setName:(NSString *)newName { 
      [self willChangeValueForKey:@"name"];    //KVO 在調(diào)用存取方法之前總調(diào)用 
      [super setValue:newName forKey:@"name"]; //調(diào)用父類的存取方法 
      [self didChangeValueForKey:@"name"];     //KVO 在調(diào)用存取方法之后總調(diào)用
}

2壤圃、類目添加新屬性

在我們的日常開發(fā)中陵霉,分類可以為原有類擴展功能,復寫原有類方法伍绳。但是分類不支持添加成員變量踊挠。盡管我們可以在分類中直接聲明屬性,但是由于不能生成成員變量冲杀,所以直接調(diào)用這些屬性還會造成崩潰效床。為了實現(xiàn)分類添加屬性,Runtime為我們添加了關聯(lián)對象方法。它能夠幫助我們在運行階段將任意的屬性關聯(lián)到一個對象上权谁。方法如下:

/**
 1.給對象設置關聯(lián)屬性
 @param object 需要設置關聯(lián)屬性的對象剩檀,即給哪個對象關聯(lián)屬性
 @param key 關聯(lián)屬性對應的key,可通過key獲取這個屬性旺芽,
 @param value 給關聯(lián)屬性設置的值
 @param policy 關聯(lián)屬性的存儲策略(對應Property屬性中的assign,copy谨朝,retain等)
 OBJC_ASSOCIATION_ASSIGN             @property(assign)卤妒。
 OBJC_ASSOCIATION_RETAIN_NONATOMIC   @property(strong, nonatomic)。
 OBJC_ASSOCIATION_COPY_NONATOMIC     @property(copy, nonatomic)字币。
 OBJC_ASSOCIATION_RETAIN             @property(strong,atomic)。
 OBJC_ASSOCIATION_COPY               @property(copy, atomic)共缕。
 */
void objc_setAssociatedObject(id _Nonnull object,
                              const void * _Nonnull key,
                              id _Nullable value,
                              objc_AssociationPolicy policy)
/**
 2.通過key獲取關聯(lián)的屬性
 @param object 從哪個對象中獲取關聯(lián)屬性
 @param key 關聯(lián)屬性對應的key
 @return 返回關聯(lián)屬性的值
 */
id _Nullable objc_getAssociatedObject(id _Nonnull object,
                                      const void * _Nonnull key)
/**
 3.移除對象所關聯(lián)的屬性
 @param object 移除某個對象的所有關聯(lián)屬性
 */
void objc_removeAssociatedObjects(id _Nonnull object)

接下來我用一個例子說明:

#import <UIKit/UIKit.h>

@interface UIViewController (custome)

@property (nonatomic, copy) NSString *age;

@end

#import "UIViewController+custome.h"
#import <objc/runtime.h>

@implementation UIViewController (custome)

- (void)setAge:(NSString *)age {
    ///添加成員變量
    objc_setAssociatedObject(self, @selector(setAge:), age, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)age {
    ///獲取成員變量
    return objc_getAssociatedObject(self, @selector(setAge:));
}

@end

注意:key與關聯(lián)屬性一一對應洗出,我們必須確保其全局唯一性,常用我們使用@selector(methodName)作為key图谷。

3翩活、獲取類的詳細信息

1.1 獲取屬性列表

unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i<count; i++) {
    const char *propertyName = property_getName(propertyList[i]);
    NSLog(@"PropertyName(%d): %@",i,[NSString stringWithUTF8String:propertyName]);
}
free(propertyList);

1.2 獲取所有成員變量

Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i= 0; i<count; i++) {
    Ivar ivar = ivarList[i];
    const char *ivarName = ivar_getName(ivar);
    NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);

1.3 獲取所有方法

Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i = 0; i<count; i++) {
    Method method = methodList[i];
    SEL mthodName = method_getName(method);
    NSLog(@"MethodName(%d): %@",i,NSStringFromSelector(mthodName));
}
free(methodList);

1.4 獲取當前遵循的所有協(xié)議

__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (int i=0; i<count; i++) {
    Protocol *protocal = protocolList[i];
    const char *protocolName = protocol_getName(protocal);
    NSLog(@"protocol(%d): %@",i, [NSString stringWithUTF8String:protocolName]);
}
free(propertyList);

注意:C語言中使用Copy操作的方法,要注意釋放指針便贵,防止內(nèi)存泄漏

4菠镇、解決同一方法高頻率調(diào)用的效率問題

5、方法動態(tài)解析與消息轉(zhuǎn)發(fā)

方法的動態(tài)解析和消息轉(zhuǎn)發(fā),詳細內(nèi)容請參考上篇文章承璃。

5.1 動態(tài)方法解析:動態(tài)添加方法

Runtime足夠強大利耍,能夠讓我們在運行時動態(tài)添加一個未實現(xiàn)的方法,這個功能主要有兩個應用場景:
場景1:動態(tài)添加未實現(xiàn)方法盔粹,解決代碼中因為方法未找到而報錯的問題隘梨;
場景2:利用懶加載思路,若一個類有很多個方法舷嗡,同時加載到內(nèi)存中會耗費資源轴猎,可以使用動態(tài)解析添加方法。方法動態(tài)解析主要用到的方法如下:

//OC方法:
//類方法未找到時調(diào)起进萄,可于此添加類方法實現(xiàn)
+ (BOOL)resolveClassMethod:(SEL)sel

//實例方法未找到時調(diào)起捻脖,可于此添加實例方法實現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel

//Runtime方法:
/**
 運行時方法:向指定類中添加特定方法實現(xiàn)的操作
 @param cls 被添加方法的類
 @param name selector方法名
 @param imp 指向?qū)崿F(xiàn)方法的函數(shù)指針
 @param types imp函數(shù)實現(xiàn)的返回值與參數(shù)類型
 @return 添加方法是否成功
 */
BOOL class_addMethod(Class _Nullable cls,
                     SEL _Nonnull name,
                     IMP _Nonnull imp,
                     const char * _Nullable types)

5.2 解決方法無響應崩潰問題

執(zhí)行OC方法其實就是一個發(fā)送消息的過程,若方法未實現(xiàn)中鼠,我們可以利用方法動態(tài)解析與消息轉(zhuǎn)發(fā)來避免程序崩潰可婶,這主要涉及下面一個處理未實現(xiàn)消息的過程:
消息轉(zhuǎn)發(fā)流程圖

其他相關方法如下:

///重定向類方法的消息接收者,返回一個類
- (id)forwardingTargetForSelector:(SEL)aSelector

///重定向?qū)嵗椒ǖ南⒔邮苷叨等洌祷匾粋€實例對象
- (id)forwardingTargetForSelector:(SEL)aSelector

///消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation扰肌;

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;

6、動態(tài)操作屬性

6.1 動態(tài)修改屬性變量

現(xiàn)在假設這樣一個情況:我們使用第三方框架里的Person類熊杨,在特殊需求下想要更改其私有屬性name曙旭,這樣的操作我們就可以使用Runtime可以動態(tài)修改對象屬性。

基本思路:首先使用Runtime獲取Peson對象的所有屬性晶府,找到name桂躏,然后使用ivar的方法修改其值。具體的代碼示例如下:

    Person *per = [[Person alloc] init];
    per.name = @"zhagnsan";
    NSLog(@"======== %@",per.name);
    
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList([per class], &count);
    
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
        const char *ivarName = ivar_getName(ivar);
        NSString *propertyName = [NSString stringWithUTF8String:ivarName];
        if ([propertyName isEqualToString:@"_name"]) {
            object_setIvar(per, ivar, @"李四");
        }
    }
    free(ivarList); //釋放指針
    NSLog(@"------- %@",per.name);

執(zhí)行結(jié)果為:

2018-11-29 11:17:06.050754+0800 Runtime[34451:175705] ======== zhagnsan
2018-11-29 11:17:06.050844+0800 Runtime[34451:175705] ------- 李四

6.2 實現(xiàn) NSCoding 的自動歸檔和解檔

歸檔是一種常用的輕量型文件存儲方式川陆,但是它有個弊端:在歸檔過程中剂习,若一個Model有多個屬性,我們不得不對每個屬性進行處理,非常繁瑣鳞绕。歸檔操作主要涉及兩個方法:encodeObjectdecodeObjectForKey失仁,現(xiàn)在,我們可以利用Runtime來改進它們们何,關鍵的代碼示例如下:

//原理:使用Runtime動態(tài)獲取所有屬性
//解檔操作
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self) {
        unsigned int count = 0;
        
        Ivar *ivarList = class_copyIvarList([self class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivarList[i];
            const char *ivarName = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:ivarName];
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivarList); //釋放指針
    }
    return self;
}

//歸檔操作
- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int count = 0;
    
    Ivar *ivarList = class_copyIvarList([self class], &count);
    for (NSInteger i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(ivarList); //釋放指針
}

測試如下:

//--測試歸檔
Person *per = [[Person alloc] init];
per.name = @"zhagnsan";
per.age  = 18;
NSString *temp = NSTemporaryDirectory();
NSString *fileTemp = [temp stringByAppendingString:@"person.archive"];
[NSKeyedArchiver archiveRootObject:ps toFile:fileTemp];

//--測試解檔
NSString *temp = NSTemporaryDirectory();
NSString *fileTemp = [temp stringByAppendingString:@"person.henry"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:fileTemp];
NSLog(@"person-name:%@萄焦,person-age:%ld",person.name,person.age); 
//person-name:zhagnsan,person-age:18

6.3 實現(xiàn)字典與模型的轉(zhuǎn)換

字典數(shù)據(jù)轉(zhuǎn)模型的操作在項目開發(fā)中很常見冤竹,通常我們會選擇第三方如YYModel拂封;其實我們也可以自己來實現(xiàn)這一功能,主要的思路有兩種:KVC鹦蠕、Runtime冒签,總結(jié)字典轉(zhuǎn)化模型過程中需要解決的問題如下:


字典轉(zhuǎn)模型

現(xiàn)在,我們使用Runtime來實現(xiàn)字典轉(zhuǎn)模型的操作钟病,大致的思路是這樣:
借助Runtime可以動態(tài)獲取成員列表的特性萧恕,遍歷模型中所有屬性,然后以獲取到的屬性名為key档悠,在JSON字典中尋找對應的值value廊鸥;再將每一個對應Value賦值給模型,就完成了字典轉(zhuǎn)模型的目的辖所。

首先準備下面的JSON數(shù)據(jù)用于測試:

{
    "id":"2462079046",
    "name": "梧雨北辰",
    "age":"18",
    "weight":140,
    "address":{
            "country":"中國",
            "province": "河南"
            },
    "courses":[{
               "name":"Chinese",
               "desc":"語文課"
    },{
               "name":"Math",
               "desc":"數(shù)學課"
    },{
               "name":"English",
               "desc":"英語課"
    }
    ]
}

具體的代碼實現(xiàn)流程如下:

步驟1:創(chuàng)建NSObject的類目NSObject+ZSModel惰说,用于實現(xiàn)字典轉(zhuǎn)模型
@interface NSObject (ZSModel)
+ (instancetype)zs_modelWithDictionary:(NSDictionary *)dictionary;
@end

//ZSModel協(xié)議,協(xié)議方法可以返回一個字典缘回,表明特殊字段的處理規(guī)則
@protocol ZSModel<NSObject>
@optional
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
@end;
#import "NSObject+ZSModel.h"
#import <objc/runtime.h>
@implementation NSObject (ZSModel)
+ (instancetype)zs_modelWithDictionary:(NSDictionary *)dictionary{
    
    //創(chuàng)建當前模型對象
    id object = [[self alloc] init];
    //1.獲取當前對象的成員變量列表
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList([self class], &count);
    
    //2.遍歷ivarList中所有成員變量吆视,以其屬性名為key,在字典中查找Value
    for (int i= 0; i<count; i++) {
        //2.1獲取成員屬性
        Ivar ivar = ivarList[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)] ;
        
        //2.2截取成員變量名:去掉成員變量前面的"_"號
        NSString *propertyName = [ivarName substringFromIndex:1];
        
        //2.3以屬性名為key酥宴,在字典中查找value
        id value = dictionary[propertyName];
        
        //3.獲取成員變量類型, 因為ivar_getTypeEncoding獲取的類型是"@\"NSString\""的形式
        //所以我們要做以下的替換
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];// 替換:
        //3.1去除轉(zhuǎn)義字符:@\"name\" -> @"name"
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        //3.2去除@符號
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
        
        //4.對特殊成員變量進行處理:
        //判斷當前類是否實現(xiàn)了協(xié)議方法啦吧,獲取協(xié)議方法中規(guī)定的特殊變量的處理方式
        NSDictionary *perpertyTypeDic;
        if([self respondsToSelector:@selector(modelContainerPropertyGenericClass)]){
            perpertyTypeDic = [self performSelector:@selector(modelContainerPropertyGenericClass) withObject:nil];
        }
        
        //4.1處理:字典的key與模型屬性不匹配的問題,如id->uid
        id anotherName = perpertyTypeDic[propertyName];
        if(anotherName && [anotherName isKindOfClass:[NSString class]]){
            value =  dictionary[anotherName];
        }
        
        //4.2.處理:模型嵌套模型
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
            Class modelClass = NSClassFromString(ivarType);
            if (modelClass != nil) {
                //將被嵌套字典數(shù)據(jù)也轉(zhuǎn)化成Model
                value = [modelClass zs_modelWithDictionary:value];
            }
        }
        
        //4.3處理:模型嵌套模型數(shù)組
        //判斷當前Vaue是一個數(shù)組拙寡,而且存在協(xié)議方法返回了perpertyTypeDic
        if ([value isKindOfClass:[NSArray class]] && perpertyTypeDic) {
            Class itemModelClass = perpertyTypeDic[propertyName];
            //封裝數(shù)組:將每一個子數(shù)據(jù)轉(zhuǎn)化為Model
            NSMutableArray *itemArray = @[].mutableCopy;
            for (NSDictionary *itemDic  in value) {
                id model = [itemModelClass zs_modelWithDictionary:itemDic];
                [itemArray addObject:model];
            }
            value = itemArray;
        }
        
        //5.使用KVC方法將Vlue更新到object中
        if (value != nil) {
            [object setValue:value forKey:propertyName];
        }
    }
    free(ivarList); //釋放C指針
    return object;
}
@end
步驟2:分別創(chuàng)建各個數(shù)據(jù)模型Student授滓、Address、Course

Student類:

//Student.h文件
#import "NSObject+ZSModel.h"
#import "AddressModel.h"
#import "CourseModel.h"
@interface StudentModel : NSObject<ZSModel> //遵循協(xié)議
//普通屬性
@property (nonatomic, copy) NSString *uid;
@property(nonatomic,copy)NSString *name;
@property (nonatomic, assign) NSInteger age;
//嵌套模型
@property (nonatomic, strong) AddressModel *address;
//嵌套模型數(shù)組
@property (nonatomic, strong) NSArray *courses;
@end
#import "StudentModel.h"
@implementation StudentModel
+ (NSDictionary *)modelContainerPropertyGenericClass {
    //需要特別處理的屬性
    return @{@"courses" : [CourseModel class],@"uid":@"id"};
}
@end

Address類:

//AddressModel.h文件
@interface AddressModel : NSObject
@property (nonatomic, copy) NSString *country;  //國籍
@property (nonatomic, copy) NSString *province; //省份
@property (nonatomic, copy) NSString *city;     //城市
@end

//-----------------優(yōu)美的分割線------------------------
//AddressModel.m文件
#import "AddressModel.h"
@implementation AddressModel
@end

Course類:

//讀取JSON數(shù)據(jù)
NSDictionary *jsonData = [FileTools getDictionaryFromJsonFile:@"Student"];
NSLog(@"%@",jsonData);

//字典轉(zhuǎn)模型
StudentModel *student = [StudentModel zs_modelWithDictionary:jsonData];
CourseModel *courseModel = student.courses[0];
NSLog(@"%@",courseModel.name);

步驟4:測試字典轉(zhuǎn)模型操作

//讀取JSON數(shù)據(jù)
NSDictionary *jsonData = [FileTools getDictionaryFromJsonFile:@"Student"];
NSLog(@"%@",jsonData);

//字典轉(zhuǎn)模型
StudentModel *student = [StudentModel zs_modelWithDictionary:jsonData];
CourseModel *courseModel = student.courses[0];
NSLog(@"%@",courseModel.name);

效果如下:


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肆糕,一起剝皮案震驚了整個濱河市般堆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诚啃,老刑警劉巖淮摔,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異始赎,居然都是意外死亡和橙,警方通過查閱死者的電腦和手機仔燕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魔招,“玉大人晰搀,你說我怎么就攤上這事∑桶伲” “怎么了厕隧?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俄周。 經(jīng)常有香客問我,道長髓迎,這世上最難降的妖魔是什么峦朗? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮排龄,結(jié)果婚禮上波势,老公的妹妹穿的比我還像新娘。我一直安慰自己橄维,他們只是感情好尺铣,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著争舞,像睡著了一般凛忿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竞川,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天店溢,我揣著相機與錄音,去河邊找鬼委乌。 笑死床牧,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的遭贸。 我是一名探鬼主播戈咳,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壕吹!你這毒婦竟也來了著蛙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤算利,失蹤者是張志新(化名)和其女友劉穎册踩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體效拭,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡暂吉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年胖秒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慕的。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡阎肝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肮街,到底是詐尸還是另有隱情风题,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布嫉父,位于F島的核電站沛硅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绕辖。R本人自食惡果不足惜摇肌,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仪际。 院中可真熱鬧围小,春花似錦、人聲如沸树碱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽成榜。三九已至框舔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伦连,已是汗流浹背雨饺。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惑淳,地道東北人额港。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像歧焦,于是被迫代替她去往敵國和親移斩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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