運(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

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