引言
相信很多同學(xué)都聽過運(yùn)行時(shí),但是我相信還是有很多同學(xué)不了解什么是運(yùn)行時(shí),到底在項(xiàng)目開發(fā)中怎么用裳瘪?什么時(shí)候適合使用?想想我們的項(xiàng)目中罪针,到底在哪里使用過運(yùn)行時(shí)呢彭羹?還能想起來嗎?另外泪酱,在面試的時(shí)候派殷,是否經(jīng)常有筆試中要求運(yùn)用運(yùn)行時(shí)或者在面試時(shí)面試官會(huì)問是否使用過運(yùn)行時(shí),又是如何使用的墓阀?
回想自己毡惜,曾經(jīng)在面試中被面試官拿運(yùn)行時(shí)刁難過,也在筆試中遇到過斯撮。因此经伙,后來就深入地學(xué)習(xí)了Runtime機(jī)制,學(xué)習(xí)里面的API勿锅。所以才有了后來的組件封裝中使用運(yùn)行時(shí)帕膜。
相信我們都遇到過這樣一個(gè)問題:我想在擴(kuò)展(category)中添加一個(gè)屬性枣氧,如果iOS是不允許給擴(kuò)展類擴(kuò)展屬性的,那怎么辦呢垮刹?答案就是使用運(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ì)象中的所有屬性瞒大、方法等螃征。
還記得我們在網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)處理時(shí),調(diào)用了-setValuesForKeysWithDictionary:方法來設(shè)置模型的值透敌。這里什么原理呢盯滚?為什么能這么做?其實(shí)就是通過Runtime機(jī)制來完成的酗电,內(nèi)部會(huì)遍歷模型類的所有屬性名魄藕,然后設(shè)置與key對(duì)應(yīng)的屬性名的值。
我們在使用運(yùn)行時(shí)的地方撵术,都需要包含頭文件:#import 背率。如果是Swift就不需要包含頭文件,就可以直接使用了嫩与。
利用運(yùn)行時(shí)獲取對(duì)象的所有屬性名是可以的寝姿,但是變量名獲取就得用另外的方法了。我們可以通過class_copyPropertyList方法獲取所有的屬性名稱划滋。
下面我們通過一個(gè)Person類來學(xué)習(xí)饵筑,這里的方法沒有寫成擴(kuò)展,只是為了簡化处坪,將獲取屬性名的方法直接作為類的實(shí)例方法:
@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服傍。
/// An opaque type that represents an Objective-C declared property.
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;
}
現(xiàn)在套蒂,我們來測試一下,我們的方法是否正確獲取到了呢茫蛹?看下面的打印結(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é)果:
// 2015-10-23 17:28:38.098 PropertiesDemo[1120:361130] size=48
// 2015-10-23 17:28:38.098 PropertiesDemo[1120:361130] copiedString
// 2015-10-23 17:28:38.098 PropertiesDemo[1120:361130] name
// 2015-10-23 17:28:38.098 PropertiesDemo[1120:361130] unsafeName
// 2015-10-23 17:28:38.099 PropertiesDemo[1120:361130] array
對(duì)于Swift版,使用C語言的指針就不容易了婴洼,因?yàn)镾wift希望盡可能減少C語言的指針的直接使用骨坑,因此在Swift中已經(jīng)提供了相應(yīng)的結(jié)構(gòu)體封裝了C語言的指針。但是看起來好復(fù)雜柬采,使用起來好麻煩欢唾。看看Swift版的獲取類的屬性名稱如何做:
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)
? ? var propertyNames: [String] = []
? ? // Swift中類型是嚴(yán)格檢查的粉捻,必須轉(zhuǎn)換成同一類型
? ? for var i = 0; i < Int(count); ++i {
? ? ? // UnsafeMutablePointer是
? ? ? // 可變指針礁遣,因此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)文章沸呐。
測試一下是否獲取正確:
let p = Person()
p.name = "Lili"
// 打印結(jié)果:["name", "hasBMW"],說明成功
p.allProperties()
對(duì)于獲取對(duì)象的所有屬性名续语,在上面的-allProperties方法已經(jīng)可以拿到了垂谢,但是并沒有處理獲取屬性值,下面的方法就是可以獲取屬性名和屬性值疮茄,將屬性名作為key滥朱,屬性值作為value。
- (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) {
? ? ? [resultDict setObject:propertyValue forKey:propertyName];
? ? }
? }
? // 記得釋放
? free(properties);
? return resultDict;
}
測試一下:
// 此方法返回的只有屬性值不為空的屬性
NSDictionary *dict = p.allPropertyNamesAndValues;
for (NSString *propertyName in dict.allKeys) {
? NSLog(@"propertyName: %@ propertyValue: %@",
? ? ? ? propertyName,
? ? ? ? dict[propertyName]);
}
看下打印結(jié)果力试,屬性值為空的屬性并沒有打印出來徙邻,因此字典的key對(duì)應(yīng)的value不能為nil:
propertyName: name propertyValue: Lili
func allPropertyNamesAndValues() ->[String: AnyObject] {
? ? var count: UInt32 = 0
? ? let properties = class_copyPropertyList(Person.self, &count)
? ? var resultDict: [String: AnyObject] = [:]
? ? for var i = 0; i < Int(count); ++i {
? ? ? 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
}
測試一下:
let dict = p.allPropertyNamesAndValues()
for (propertyName, propertyValue) in dict.enumerate() {
? print("propertyName: (propertyName), propertyValue: (propertyValue)")
}
打印結(jié)果與上面的一樣,由于array屬性的值為nil畸裳,因此不會(huì)處理缰犁。
propertyName: 0, propertyValue: ("name", Lili)
通過class_copyMethodList方法就可以獲取所有的方法。
- (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);
}
測試一下:
[p allMethods];
調(diào)用打印結(jié)果如下帅容,為什么參數(shù)個(gè)數(shù)看起來不匹配呢颇象?比如-allProperties方法,其參數(shù)個(gè)數(shù)為0才對(duì)并徘,但是打印結(jié)果為2遣钳。根據(jù)打印結(jié)果可知,無參數(shù)時(shí)麦乞,值就已經(jīng)是2了蕴茴。:
方法名:allProperties, 參數(shù)個(gè)數(shù):2
方法名:allPropertyNamesAndValues, 參數(shù)個(gè)數(shù):2
方法名:allMethods, 參數(shù)個(gè)數(shù):2
方法名:setArray:, 參數(shù)個(gè)數(shù):3
方法名:.cxx_destruct, 參數(shù)個(gè)數(shù):2
方法名:name, 參數(shù)個(gè)數(shù):2
方法名:array, 參數(shù)個(gè)數(shù):2
方法名:setName:, 參數(shù)個(gè)數(shù):3
func allMethods() {
? var count: UInt32 = 0
? let methods = class_copyMethodList(Person.self, &count)
? for var i = 0; i < Int(count); ++i {
? ? let method = methods[i]
? ? let sel = method_getName(method)
? ? let methodName = sel_getName(sel)
? ? let argument = method_getNumberOfArguments(method)
? ? print("name: (methodName), arguemtns: (argument)")
? }
}
測試一下調(diào)用:
p.allMethods()
打印結(jié)果與上面的Objective-C版的一樣。
要獲取對(duì)象的成員變量姐直,可以通過class_copyIvarList方法來獲取倦淀,通過ivar_getName來獲取成員變量的名稱。對(duì)于屬性声畏,會(huì)自動(dòng)生成一個(gè)成員變量撞叽。
- (NSArray *)allMemberVariables {
? unsigned int count = 0;
? Ivar *ivars = class_copyIvarList([self class], &count);
? NSMutableArray *results = [[NSMutableArray alloc] init];
? for (NSUInteger i = 0; i < count; ++i) {
? ? Ivar variable = ivars[i];
? ? const char *name = ivar_getName(variable);
? ? NSString *varName = [NSString stringWithUTF8String:name];
? ? [results addObject:varName];
? }
? return results;
}
測試一下:
for (NSString *varName in p.allMemberVariables) {
? NSLog(@"%@", varName);
}
打印結(jié)果說明屬性也會(huì)自動(dòng)生成一個(gè)成員變量:
2015-10-23 23:54:00.896 PropertiesDemo[46966:3856655] _variableString
2015-10-23 23:54:00.897 PropertiesDemo[46966:3856655] _name
2015-10-23 23:54:00.897 PropertiesDemo[46966:3856655] _array
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
}
測試一下:
let array = p.allMemberVariables()
for varName in array {
? print(varName)
}
打印結(jié)果佣渴,說明Swift的屬性不會(huì)自動(dòng)加下劃線辫狼,屬性名就是變量名:
name
array
iOS中,可以在運(yùn)行時(shí)發(fā)送消息辛润,讓接收消息者執(zhí)行對(duì)應(yīng)的動(dòng)作膨处。可以使用objc_msgSend方法砂竖,發(fā)送消息真椿。
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)格檢查:
很抱歉解恰,似乎在Swift中已經(jīng)沒有這種寫法了。如果有浙于,請(qǐng)告訴我护盈。
iOS的category是不能擴(kuò)展存儲(chǔ)屬性的,但是我們可以通過運(yùn)行時(shí)關(guān)聯(lián)來擴(kuò)展“屬性”羞酗。
假設(shè)擴(kuò)展下面的“屬性”:
// 由于擴(kuò)展不能擴(kuò)展屬性腐宋,因此我們這里在實(shí)現(xiàn)文件中需要利用運(yùn)行時(shí)實(shí)現(xiàn)。
typedef void(^HYBCallBack)();
@property (nonatomic, copy) HYBCallBack callback;
在實(shí)現(xiàn)文件中,我們用一個(gè)靜態(tài)變量作為key:
const void *s_HYBCallbackKey = "s_HYBCallbackKey";
- (void)setCallback:(HYBCallBack)callback {
? objc_setAssociatedObject(self, s_HYBCallbackKey, callback, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (HYBCallBack)callback {
? return objc_getAssociatedObject(self, s_HYBCallbackKey);
}
其實(shí)就是通過objc_getAssociatedObject取得關(guān)聯(lián)的值胸竞,通過objc_setAssociatedObject設(shè)置關(guān)聯(lián)欺嗤。
Swift版的要想擴(kuò)展閉包,就比OC版的要復(fù)雜得多了卫枝。這里只是例子剂府,寫了一個(gè)簡單的存儲(chǔ)屬性擴(kuò)展。
let s_HYBFullnameKey = "s_HYBFullnameKey"
extension Person {
? var fullName: String? {
? ? get { return objc_getAssociatedObject(self, s_HYBFullnameKey) as? String }
? ? set {
? ? ? objc_setAssociatedObject(self, s_HYBFullnameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
? ? }
? }
}
在開發(fā)中剃盾,我們比較常用的是使用關(guān)聯(lián)屬性的方式來擴(kuò)展我們的“屬性”腺占,以便在開發(fā)中簡單代碼。我們在開發(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幅狮。
轉(zhuǎn)自:?http://blog.csdn.net/ssztechnology/article/details/51718586