OC和Swift中的Runtime

轉(zhuǎn)載

運(yùn)行時(shí)機(jī)制

runtime是一套比較底層的純C語言的API, 屬于C語言庫, 包含了很多底層的C語言API。
在我們平時(shí)編寫的iOS代碼中, 最終都是轉(zhuǎn)成了runtime的C語言代碼。

所謂運(yùn)行時(shí),也就是在編譯時(shí)是不存在的,只是在運(yùn)行過程中才去確定對(duì)象的類型、方法等。利用Runtime機(jī)制可以在程序運(yùn)行時(shí)動(dòng)態(tài)修改類、對(duì)象中的所有屬性、方法等。

還記得我們?cè)诰W(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)處理時(shí),調(diào)用了-setValuesForKeysWithDictionary:方法來設(shè)置模型的值。這里什么原理呢?為什么能這么做?其實(shí)就是通過Runtime機(jī)制來完成的,內(nèi)部會(huì)遍歷模型類的所有屬性名,然后設(shè)置與key對(duì)應(yīng)的屬性名的值。

我們?cè)谑褂眠\(yùn)行時(shí)的地方,都需要包含頭文件:#import <objc/runtime.h>。如果是Swift就不需要包含頭文件,就可以直接使用了。

runtime基礎(chǔ)

1. 獲取對(duì)象所有屬性名

利用運(yùn)行時(shí)獲取對(duì)象的所有屬性名是可以的,但是變量名獲取就得用另外的方法了。我們可以通過class_copyPropertyList方法獲取所有的屬性名稱。

  • 第一個(gè)參數(shù):類
  • 第二個(gè)參數(shù):存放屬性個(gè)數(shù)的地址

下面我們通過一個(gè)Person類來學(xué)習(xí),這里的方法沒有寫成擴(kuò)展,只是為了簡(jiǎn)化,將獲取屬性名的方法直接作為類的實(shí)例方法:

Objective-C版
@interface Person : NSObject{
    NSString *_variableString;
}

// 默認(rèn)會(huì)是什么呢?
@property (nonatomic, copy) NSString *name;

// 默認(rèn)是strong類型
@property (nonatomic, strong) NSMutableArray *array;

// 獲取所有的屬性名
- (NSArray *)allProperties;
@end

下面主要是寫如何獲取類的所有屬性名的方法。注意,這里的objc_property_t是一個(gè)結(jié)構(gòu)體指針objc_property *,因此我們聲明的properties就是二維指針。在使用完成后,我們一定要記得釋放內(nèi)存,否則會(huì)造成內(nèi)存泄露。這里是使用的是C語言的API,因此我們也需要使用C語言的釋放內(nèi)存的方法free。

@implementation Person

typedef struct objc_property *objc_property_t;

- (NSArray *)allProperties {
    unsigned int count;
    
    // 獲取類的所有屬性
    // 如果沒有屬性,則count為0,properties為nil
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    NSMutableArray *propertiesArray = [NSMutableArray arrayWithCapacity:count];
    
    for (NSUInteger i = 0; i < count; i++) {
        // 獲取屬性名稱
        const char *propertyName = property_getName(properties[i]);
        NSString *name = [NSString stringWithUTF8String:propertyName];
        
        [propertiesArray addObject:name];
    }
    
    // 注意,這里properties是一個(gè)數(shù)組指針,是C的語法,
    // 我們需要使用free函數(shù)來釋放內(nèi)存,否則會(huì)造成內(nèi)存泄露
    free(properties);
    
    return propertiesArray;
}

來測(cè)試一下,我們的方法是否正確獲取到了呢?看下面的打印結(jié)果就明白了吧

Person *p = [[Person alloc] init];
    p.name = @"Lili";
    
    size_t size = class_getInstanceSize(p.class);
    NSLog(@"size=%ld", size);
    
    for (NSString *propertyName in p.allProperties) {
        NSLog(@"%@", propertyName);
    }
    // 打印結(jié)果:
    // 2016-04-19 11:37:24.589 LSRuntimeOCDemo[1554:108843] size=32
    // 2016-04-19 11:37:24.590 LSRuntimeOCDemo[1554:108843] name
    // 2016-04-19 11:37:24.590 LSRuntimeOCDemo[1554:108843] array

Swift版

對(duì)于Swift版,使用C語言的指針就不容易了,因?yàn)镾wift希望盡可能減少C語言的指針的直接使用,因此在Swift中已經(jīng)提供了相應(yīng)的結(jié)構(gòu)體封裝了C語言的指針。但是看起來好復(fù)雜,使用起來好麻煩??纯碨wift版的獲取類的屬性名稱如何做:

class Person: NSObject {
    var name: String = ""
    var hasBMW = false
    
    override init() {
        super.init()
    }
    
    func allProperties() ->[String] {
        // 這個(gè)類型可以使用CUnsignedInt,對(duì)應(yīng)Swift中的UInt32
        var count : UInt32 = 0
        
        let properties = class_copyPropertyList(Person.self, &count)
        
        ///定義一個(gè)元素為字符串的數(shù)組
        var propertyNames: [String] = []
        
        // Swift中類型是嚴(yán)格檢查的,必須轉(zhuǎn)換成同一類型
        for var i  in 0..<Int(count) {
            // UnsafeMutablePointer<objc_property_t>是可變指針,因此properties就是類似數(shù)組一樣,可以通過下標(biāo)獲取
            let property = properties[i]
            let name = property_getName(property)
            
            
            // 這里還得轉(zhuǎn)換成字符串
            let strName = String.fromCString(name)
            propertyNames.append(strName!)
        }
        // 不要忘記釋放內(nèi)存,否則C語言的指針很容易成野指針的
        free(properties)
        
        
        return propertyNames
    }
}

關(guān)于Swift中如何C語言的指針問題,這里不細(xì)說,如果需要了解,請(qǐng)查閱相關(guān)文章。
測(cè)試一下是否獲取正確:

        let p = Person()
        p.name = "Lili"
        
        // 打印結(jié)果:["name", "hasBMW"],說明成功
        print( p.allProperties() )

2.獲取對(duì)象的所有屬性名和屬性值

對(duì)于獲取對(duì)象的所有屬性名,在上面的-allProperties方法已經(jīng)可以拿到了,但是并沒有處理獲取屬性值,下面的方法就是可以獲取屬性名和屬性值,將屬性名作為key,屬性值作為value。

Object-C版
- (NSDictionary *)allPropertyNamesAndValues {
    NSMutableDictionary *resultDict = [NSMutableDictionary dictionary];
    
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *name = property_getName(property);
        
        // 得到屬性名
        NSString *propertyName = [NSString stringWithUTF8String:name];
        
        // 獲取屬性值
        id propertyValue = [self valueForKey:propertyName];
        
        if (propertyValue && propertyValue != nil) {
            ///如果值為空則不添加進(jìn)字典
            [resultDict setObject:propertyValue forKey:propertyName];
        }
    }
    
    // 記得釋放
    free(properties);
    
    return resultDict;
}

測(cè)試一下

// 此方法返回的只有屬性值不為空的屬性
    NSDictionary *dict = p.allPropertyNamesAndValues;
    for (NSString *propertyName in dict.allKeys) {
        NSLog(@"propertyName: %@ propertyValue: %@",
              propertyName,
              dict[propertyName]);
    }
輸出結(jié)果:
2016-04-19 12:30:49.367 LSRuntimeOCDemo[1983:145600] propertyName: name propertyValue: Lili
Siwft版
func allPropertyNamesAndValues() ->[String: AnyObject] {
        var count: UInt32 = 0
        let properties = class_copyPropertyList(Person.self, &count)
        
        var resultDict: [String: AnyObject] = [:]
        for var i in 0..<Int(count) {
            let property = properties[i]
            
            // 取得屬性名
            let name = property_getName(property)
            if let propertyName = String.fromCString(name) {
                // 取得屬性值
                if let propertyValue = self.valueForKey(propertyName) {
                    ///屬性不為空的添加在字典中
                    resultDict[propertyName] = propertyValue
                }
            }
        }
        
        return resultDict
    }

測(cè)試一下:

let dict = p.allPropertyNamesAndValues()
        for (propertyName, propertyValue) in dict {
            print("propertyName: (\(propertyName)), propertyValue: (\(propertyValue))")
        }

打印結(jié)果:

propertyName: (hasBMW), propertyValue: (0)
propertyName: (name), propertyValue: (Lili)

3. 獲取對(duì)象的所有方法名

通過class_copyMethodList方法就可以獲取所有的方法。

  • 第一個(gè)參數(shù):類
  • 第二個(gè)參數(shù):存放方法個(gè)數(shù)的地址
Object-C版
- (void)allMethods {
    unsigned int outCount = 0;
    Method *methods = class_copyMethodList([self class], &outCount);
    
    for (int i = 0; i < outCount; ++i) {
        Method method = methods[i];
        
        // 獲取方法名稱,但是類型是一個(gè)SEL選擇器類型
        SEL methodSEL = method_getName(method);
        // 需要獲取C字符串
        const char *name = sel_getName(methodSEL);
        // 將方法名轉(zhuǎn)換成OC字符串
        NSString *methodName = [NSString stringWithUTF8String:name];
        
        // 獲取方法的參數(shù)列表
        int arguments = method_getNumberOfArguments(method);
        NSLog(@"方法名:%@, 參數(shù)個(gè)數(shù):%d", methodName, arguments);
    }
    
    // 記得釋放
    free(methods);
}

測(cè)試

///獲取所有的方法名
    [p allMethods];

打印結(jié)果:

2016-04-19 13:33:49.999 LSRuntimeOCDemo[2174:175322] 方法名:allProperties, 參數(shù)個(gè)數(shù):2
2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:allPropertyNamesAndValues, 參數(shù)個(gè)數(shù):2
2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:allMethods, 參數(shù)個(gè)數(shù):2
2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:setArray:, 參數(shù)個(gè)數(shù):3
2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:.cxx_destruct, 參數(shù)個(gè)數(shù):2
2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:name, 參數(shù)個(gè)數(shù):2
2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:array, 參數(shù)個(gè)數(shù):2
2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:setName:, 參數(shù)個(gè)數(shù):3
Swift版
func allMethods() {
        var count : UInt32 = 0
        let methods = class_copyMethodList(Person.self,&count)
        
        for var i in 0..<Int(count) {
            let method = methods[i]
            let sel = method_getName(method)
            // 獲取方法的參數(shù)列表
            let argument = method_getNumberOfArguments(method)
             print("name: \(sel), arguemtns: \(argument)")
        }
    }

測(cè)試一下:

///獲取所有方法名
        p.allMethods()

打印結(jié)果:

name: hasBMW, arguemtns: 2
name: setHasBMW:, arguemtns: 3
name: allProperties, arguemtns: 2
name: allPropertyNamesAndValues, arguemtns: 2
name: allMethods, arguemtns: 2
name: name, arguemtns: 2
name: .cxx_destruct, arguemtns: 0
name: init, arguemtns: 2
name: setName:, arguemtns: 3

4. 獲取對(duì)象的成員變量名稱

要獲取對(duì)象的成員變量,可以通過class_copyIvarList方法來獲取,通過ivar_getName來獲取成員變量的名稱。對(duì)于屬性,會(huì)自動(dòng)生成一個(gè)成員變量。使用方法與前面類似。

  • 第一個(gè)參數(shù):類
  • 第二個(gè)參數(shù):存放變量個(gè)數(shù)的地址
Object-C版
- (NSArray *)allMemberVariables {
    unsigned int count = 0;
    ///Ivar 變量
    ///獲取成員變量的數(shù)組 (指針)
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    NSMutableArray *results = [[NSMutableArray alloc] init];
    for (NSUInteger i = 0; i < count; ++i) {
        ///獲取每個(gè)成員變量
        Ivar variable = ivars[i];
        ///獲取成員變量的字符名稱
        const char *name = ivar_getName(variable);
        ///將名稱轉(zhuǎn)為NSString
        NSString *varName = [NSString stringWithUTF8String:name];
        
        [results addObject:varName];
    }
    free(ivars);
    return results;
}

測(cè)試:

NSLog(@"%@",[p allMemberVariables]);

打印結(jié)果:

2016-04-19 14:09:50.781 LSRuntimeOCDemo[2555:199104] (
    "_variableString",
    "_name",
    "_array"
)
Swift版

Swift的成員變量名與屬性名是一樣的,不會(huì)生成下劃線的成員變量名,這一點(diǎn)與Oc是有區(qū)別的。

func allMemberVariables() ->[String] {
        var count:UInt32 = 0
        let ivars = class_copyIvarList(Person.self, &count)
        
        var result: [String] = []
        for var i = 0; i < Int(count); ++i {
            let ivar = ivars[i]
            
            let name = ivar_getName(ivar)
            
            if let varName = String.fromCString(name) {
                result.append(varName)
            }
        }
        
        return result
    }

測(cè)試:

///獲取成員變量
  print(p.allMemberVariables())

打印結(jié)果:說明Swift的屬性不會(huì)自動(dòng)加下劃線,屬性名就是變量名:

["name", "hasBMW"]

5. 運(yùn)行時(shí)發(fā)消息

Object-C版
Person *p = [[Person alloc] init];
p.name = @"Lili";
objc_msgSend(p, @selector(allMethods));

這樣就相當(dāng)于手動(dòng)調(diào)用[p allMethods];。但是編譯器會(huì)抱錯(cuò),問題提示期望的參數(shù)為0,但是實(shí)際上有兩個(gè)參數(shù)。解決辦法是,關(guān)閉嚴(yán)格檢查:

如果,此時(shí)報(bào)錯(cuò)
objc_msgSend()報(bào)錯(cuò)Too many arguments to function call ,expected 0,have2
那么,請(qǐng)Build Setting-->搜索 Enable Strict Checking of objc_msgSend Calls 改為 NO

屏幕快照 2016-04-19 下午2.44.35.png
Swift版

抱歉,Swift中沒有此類方法。

6. Category擴(kuò)展”屬性”

iOS的category是不能擴(kuò)展存儲(chǔ)屬性的,但是我們可以通過運(yùn)行時(shí)關(guān)聯(lián)來擴(kuò)展“屬性”。

Object-C 版

假設(shè)擴(kuò)展下面的“屬性”:

// 由于擴(kuò)展不能擴(kuò)展屬性,因此我們這里在實(shí)現(xiàn)文件中需要利用運(yùn)行時(shí)實(shí)現(xiàn)。
#import <objc/runtime.h>

typedef void(^LSCallBack)();
@interface NSObject (Property)

@property (nonatomic, copy) LSCallBack callback;
@end

在實(shí)現(xiàn)文件中,我們用一個(gè)靜態(tài)變量作為key:

const void *s_LSCallbackKey = "s_LSCallbackKey";

- (void)setCallback:(LSCallBack)callback {
// 第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)
// 第二個(gè)參數(shù):關(guān)聯(lián)的key,通過這個(gè)key獲取
// 第三個(gè)參數(shù):關(guān)聯(lián)的value
// 第四個(gè)參數(shù):關(guān)聯(lián)的策略
  objc_setAssociatedObject(self, s_LSCallbackKey, callback, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (HYBCallBack)callback {
  return objc_getAssociatedObject(self, s_LSCallbackKey);
}

測(cè)試:

p.callback=^(){
        NSLog(@"aaaa");
    };
    p.callback();

其實(shí)就是通過objc_getAssociatedObject取得關(guān)聯(lián)的值,通過objc_setAssociatedObject設(shè)置關(guān)聯(lián)。

Swift版

Swift版的要想擴(kuò)展閉包,就比OC版的要復(fù)雜得多了。這里只是例子,寫了一個(gè)簡(jiǎn)單的存儲(chǔ)屬性擴(kuò)展。

let s_LSFullnameKey = "s_LSFullnameKey"

extension Person {
    var fullName: String? {
        get { return objc_getAssociatedObject(self, s_LSFullnameKey) as? String }
        set {
            // 第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)
            // 第二個(gè)參數(shù):關(guān)聯(lián)的key,通過這個(gè)key獲取
            // 第三個(gè)參數(shù):關(guān)聯(lián)的value
            // 第四個(gè)參數(shù):關(guān)聯(lián)的策略
            objc_setAssociatedObject(self, s_LSFullnameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
    }
}

總結(jié)

在開發(fā)中,我們比較常用的是使用關(guān)聯(lián)屬性的方式來擴(kuò)展我們的“屬性”,以便在開發(fā)中簡(jiǎn)單代碼。我們?cè)陂_發(fā)中使用關(guān)聯(lián)屬性擴(kuò)展所有響應(yīng)事件、將代理轉(zhuǎn)換成block版等。比如,我們可以將所有繼承于UIControl的控件,都擁有block版的點(diǎn)擊響應(yīng),那么我們就可以給UIControl擴(kuò)展一個(gè)TouchUp、TouchDown、TouchOut的block等。
對(duì)于動(dòng)態(tài)獲取屬性的名稱、屬性值使用較多的地方一般是在使用第三方庫中,比如MJExtension等。這些三方庫都是通過這種方式將Model轉(zhuǎn)換成字典,或者將字典轉(zhuǎn)換成Model

另外

1.交換方法

開發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能。

// 需求:給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功。
// 步驟一:先搞個(gè)分類,定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步驟二:交換imageNamed和imageWithName的實(shí)現(xiàn),就能調(diào)用imageWithName,間接調(diào)用imageWithName的實(shí)現(xiàn)。

@implementation UIImage (image)
// 加載分類到內(nèi)存的時(shí)候調(diào)用

+(void)load
{
    // 交換方法
    
    // 獲取imageWithName方法地址
    Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
    
    // 獲取imageWithName方法地址
    Method imageName = class_getClassMethod(self, @selector(imageNamed:));
    
    // 交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式
    method_exchangeImplementations(imageWithName, imageName);
    
}

// 不能在分類中重寫系統(tǒng)方法imageNamed,因?yàn)闀?huì)把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.

// 既能加載圖片又能打印

+(instancetype)imageWithName:(NSString *)name
{
    
    // 這里調(diào)用imageWithName,相當(dāng)于調(diào)用imageName
    UIImage *image = [self imageWithName:name];
    
    if (image == nil) {
        
        NSLog(@"加載空的圖片");
    }
    
    return image;
}

測(cè)試:

UIImage *image = [UIImage imageNamed:@"123"];

打印結(jié)果:

2016-04-19 16:26:45.317 LSRuntimeOCDemo[3394:299037] 加載空的圖片

2.動(dòng)態(tài)添加方法

開發(fā)使用場(chǎng)景:如果一個(gè)類方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類,添加方法解決。
經(jīng)典面試題:有沒有使用performSelector,其實(shí)主要想問你有沒有動(dòng)態(tài)添加過方法。
簡(jiǎn)單使用

例如:
Person *p = [[Person alloc] init];
// 默認(rèn)person,沒有實(shí)現(xiàn)eat方法,可以通過performSelector調(diào)用,但是會(huì)報(bào)錯(cuò)。

// 動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
[p performSelector:@selector(eat)];

實(shí)現(xiàn):

#import "Person.h"

@implementation Person

// void(*)()
// 默認(rèn)方法都有兩個(gè)隱式參數(shù),
void eat(id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過來.
// 剛好可以用來判斷,未實(shí)現(xiàn)的方法是不是我們想要?jiǎng)討B(tài)添加的方法

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    
    if (sel == @selector(eat)) {
        
        // 動(dòng)態(tài)添加eat方法
        
        // 第一個(gè)參數(shù):給哪個(gè)類添加方法
        // 第二個(gè)參數(shù):添加方法的方法編號(hào)
        // 第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
        // 第四個(gè)參數(shù):函數(shù)的類型,(返回值+參數(shù)類型) v:void @:對(duì)象->self :表示SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "v@:");
    }
    
    return [super resolveInstanceMethod:sel];
}

測(cè)試:

Person *p = [[Person alloc] init];
// 默認(rèn)person,沒有實(shí)現(xiàn)eat方法,可以通過performSelector調(diào)用,但是會(huì)報(bào)錯(cuò)。
// 動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
[p performSelector:@selector(eat)];

打?。?/p>

2016-04-19 16:36:50.275 LSRuntimeOCDemo[3429:304239] <Person: 0x7f8620d9dd00> eat

3.字典轉(zhuǎn)模型

設(shè)計(jì)模型:字典轉(zhuǎn)模型的第一步
模型屬性,通常需要跟字典中的key一一對(duì)應(yīng)
問題:一個(gè)一個(gè)的生成模型屬性,很慢?
需求:能不能自動(dòng)根據(jù)一個(gè)字典,生成對(duì)應(yīng)的屬性。
解決:提供一個(gè)分類,專門根據(jù)字典生成對(duì)應(yīng)的屬性字符串。

@implementation NSObject (Log)
+(void)resolveDict:(NSDictionary *)dict{
    // 拼接屬性字符串代碼
    NSMutableString *strM = [NSMutableString string];
    // 1.遍歷字典,把字典中的所有key取出來,生成對(duì)應(yīng)的屬性代碼
    [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
       // 類型經(jīng)常變,抽出來
        NSString *type;
        if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
            type = @"NSString";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
            type = @"NSArray";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
            type = @"int";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){ type = @"NSDictionary";
        }
    
       // 屬性字符串
        NSString *str;
        if ([type containsString:@"NS"]) {
            str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
        }else{
            str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
        }
        // 每生成屬性字符串,就自動(dòng)換行。
        [strM appendFormat:@"\n%@\n",str];
    }];
    // 把拼接好的字符串打印出來,就好了。
    NSLog(@"%@",strM);
    
}
@end
字典轉(zhuǎn)模型的方式一:KVC
@implementation Status
+(instancetype)statusWithDict:(NSDictionary *)dict
{
    Status *status = [[self alloc] init];
    
    [status setValuesForKeysWithDictionary:dict];
    
    return status;
    
}
@end

KVC字典轉(zhuǎn)模型弊端:必須保證,模型中的屬性和字典中的key一一對(duì)應(yīng)。
如果不一致,就會(huì)調(diào)用[<Status 0x7fa74b545d60> setValue:forUndefinedKey:] 報(bào)key找不到的錯(cuò)。
分析:模型中的屬性和字典的key不一一對(duì)應(yīng),系統(tǒng)就會(huì)調(diào)用setValue:forUndefinedKey:報(bào)錯(cuò)。
解決:重寫對(duì)象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋, 就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了。

-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
字典轉(zhuǎn)模型的方式二:Runtime

思路:利用運(yùn)行時(shí),遍歷模型中所有屬性,根據(jù)模型的屬性名,去字典中查找key,取出對(duì)應(yīng)的值,給模型的屬性賦值。
步驟:提供一個(gè)NSObject分類,專門字典轉(zhuǎn)模型,以后所有模型都可以通過這個(gè)分類轉(zhuǎn)。

#import "NSObject+Model.h"

@implementation NSObject (Model)
+(instancetype)modelWithDict:(NSDictionary *)dict
{
    // 思路:遍歷模型中所有屬性-》使用運(yùn)行時(shí)
    
    // 0.創(chuàng)建對(duì)應(yīng)的對(duì)象
    id objc = [[self alloc] init];
    
    // 1.利用runtime給對(duì)象中的成員屬性賦值
    
    // class_copyIvarList:獲取類中的所有成員屬性
    // Ivar:成員屬性的意思
    // 第一個(gè)參數(shù):表示獲取哪個(gè)類中的成員屬性
    // 第二個(gè)參數(shù):表示這個(gè)類有多少成員屬性,傳入一個(gè)Int變量地址,會(huì)自動(dòng)給這個(gè)變量賦值
    // 返回值Ivar :指的是一個(gè)ivar數(shù)組,會(huì)把所有成員屬性放在一個(gè)數(shù)組中,通過返回的數(shù)組就能全部獲取到。
    // 類似下面這種寫法
    
//    Ivar ivar;
//    Ivar ivar1;
//    Ivar ivar2;
//    // 定義一個(gè)ivar的數(shù)組a
//    Ivar a[] = {ivar,ivar1,ivar2};
//    
//    // 用一個(gè)Ivar 指針指向數(shù)組第一個(gè)元素
//    Ivar ivarList = a;
//    
//    // 根據(jù)指針訪問數(shù)組第一個(gè)元素
//    ivarList[0];
    
    
    unsigned int count;
    
    // 獲取類中的所有成員屬性
    Ivar *ivarList = class_copyIvarList(self, &count);
    
    for (int i = 0; i < count; i++) {
        
        // 根據(jù)角標(biāo),從數(shù)組取出對(duì)應(yīng)的成員屬性
        Ivar ivar = ivarList[i];
        
        // 獲取成員屬性名
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 處理成員屬性名->字典中的key
        // 從第一個(gè)角標(biāo)開始截取
        NSString *key = [name substringFromIndex:1];
        
        // 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的value
        id value = dict[key];
        
        // 二級(jí)轉(zhuǎn)換:如果字典中還有字典,也需要把對(duì)應(yīng)的字典轉(zhuǎn)換成模型
        // 判斷下value是否是字典
        if ([value isKindOfClass:[NSDictionary class]]) {
            // 字典轉(zhuǎn)模型
            // 獲取模型的類對(duì)象,調(diào)用modelWithDict
            // 模型的類名已知,就是成員屬性的類型
            
            // 獲取成員屬性類型
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            // 生成的是這種@"@\"User\"" 類型 -》 @"User"  在OC字符串中 \" -> ",\是轉(zhuǎn)義的意思,不占用字符
            // 裁剪類型字符串
            NSRange range = [type rangeOfString:@"\""];
            
            type = [type substringFromIndex:range.location + range.length];
            
            range = [type rangeOfString:@"\""];
            
            // 裁剪到哪個(gè)角標(biāo),不包括當(dāng)前角標(biāo)
            type = [type substringToIndex:range.location];
            // 根據(jù)字符串類名生成類對(duì)象
            Class modelClass = NSClassFromString(type);
            
            
            if (modelClass) { // 有對(duì)應(yīng)的模型才需要轉(zhuǎn)
                
                // 把字典轉(zhuǎn)模型
                value  =  [modelClass modelWithDict:value];
            }
            
            
        }
        
        // 三級(jí)轉(zhuǎn)換:NSArray中也是字典,把數(shù)組中的字典轉(zhuǎn)換成模型.
        // 判斷值是否是數(shù)組
        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對(duì)應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                
                // 轉(zhuǎn)換成id類型,就能調(diào)用任何對(duì)象的方法
                id idSelf = self;
                
                // 獲取數(shù)組中字典對(duì)應(yīng)的模型
                NSString *type =  [idSelf arrayContainModelClass][key];
                
                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍歷字典數(shù)組,生成模型數(shù)組
                for (NSDictionary *dict in value) {
                    // 字典轉(zhuǎn)模型
                    id model =  [classModel modelWithDict:dict];
                    [arrM addObject:model];
                }
                
                // 把模型數(shù)組賦值給value
                value = arrM;
                
            }
        }
        
        
        if (value) { // 有值,才需要給模型的屬性賦值
            // 利用KVC給模型中的屬性賦值
            [objc setValue:value forKey:key];
        }
        
    }
    
    return objc;
}

-(NSDictionary *)arrayContainModelClass{
NSDictionary *dic=@{@"aa":@"sta"};
return dic;
}
這個(gè)方法主要是確定三級(jí)轉(zhuǎn)換時(shí),用哪個(gè)類接收

- 二級(jí)轉(zhuǎn)換時(shí),如果用類去接收字典,那么需要重寫setter方法


測(cè)試:

// 解析Plist文件
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
// 獲取字典數(shù)組NSArray *dictArr = statusDict[@"statuses"];
// 自動(dòng)生成模型的屬性字符串//
[NSObject resolveDict:dictArr[0][@"user"]];

_statuses = [NSMutableArray array];
// 遍歷字典數(shù)組
for (NSDictionary *dict in dictArr) {
Status *status = [Status modelWithDict:dict];
[_statuses addObject:status];
}
// 測(cè)試數(shù)據(jù)
NSLog(@"%@ %@",_statuses,[_statuses[0] user]);

}

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

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,828評(píng)論 4 61
  • 2016年的假期,我們?nèi)チ顺啥?。?dāng)時(shí)并沒有寫些什么,時(shí)隔將近一年,才記下《成都印象》。 春熙路。 春...
    藏南藏北閱讀 828評(píng)論 3 9
  • 根據(jù)市場(chǎng)調(diào)研機(jī)構(gòu)Counterpoint數(shù)據(jù),高端智能手機(jī)市場(chǎng)的錢全讓蘋果給賺走了。在售價(jià)超過400美金(2745...
    敗叔閱讀 230評(píng)論 0 0
  • 坐地鐵時(shí),看見邊上男生書包里帶了一直小奶貓。小奶貓一路不停的貓貓的叫著,吸引著男生盡量勸哄著,生怕引起別人注意,影...
    萌萌噠哈士奇閱讀 246評(píng)論 1 1

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