Swift/Objc的Runtime(運(yùn)行時(shí))機(jī)制

引言

相信很多同學(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ī)制

運(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就不需要包含頭文件,就可以直接使用了嫩与。

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

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

下面我們通過一個(gè)Person類來學(xué)習(xí)饵筑,這里的方法沒有寫成擴(kuò)展,只是為了簡化处坪,將獲取屬性名的方法直接作為類的實(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服傍。

/// 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

Swift版

對(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ì)于獲取對(duì)象的所有屬性名续语,在上面的-allProperties方法已經(jīng)可以拿到了垂谢,但是并沒有處理獲取屬性值,下面的方法就是可以獲取屬性名和屬性值疮茄,將屬性名作為key滥朱,屬性值作為value。

Objective-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) {

? ? ? [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

Swift版

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)

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

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

Objective-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);

}

測試一下:

[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

Swift版

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ì)象的成員變量名稱

要獲取對(duì)象的成員變量姐直,可以通過class_copyIvarList方法來獲取倦淀,通過ivar_getName來獲取成員變量的名稱。對(duì)于屬性声畏,會(huì)自動(dòng)生成一個(gè)成員變量撞叽。

Objective-C版

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

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

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

iOS中,可以在運(yùn)行時(shí)發(fā)送消息辛润,讓接收消息者執(zhí)行對(duì)應(yīng)的動(dòng)作膨处。可以使用objc_msgSend方法砂竖,發(fā)送消息真椿。

Objective-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)格檢查:

Swift版

很抱歉解恰,似乎在Swift中已經(jīng)沒有這種寫法了。如果有浙于,請(qǐng)告訴我护盈。

Category擴(kuò)展”屬性”

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

Objective-C版

假設(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版

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)

? ? }

? }

}

總結(jié)

在開發(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末募强,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子崇摄,更是在濱河造成了極大的恐慌擎值,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逐抑,死亡現(xiàn)場離奇詭異鸠儿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)厕氨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門进每,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腐巢,你說我怎么就攤上這事品追。” “怎么了冯丙?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵肉瓦,是天一觀的道長遭京。 經(jīng)常有香客問我,道長泞莉,這世上最難降的妖魔是什么哪雕? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮鲫趁,結(jié)果婚禮上斯嚎,老公的妹妹穿的比我還像新娘。我一直安慰自己挨厚,他們只是感情好堡僻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疫剃,像睡著了一般钉疫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巢价,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天牲阁,我揣著相機(jī)與錄音,去河邊找鬼壤躲。 笑死城菊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碉克。 我是一名探鬼主播凌唬,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼棉胀!你這毒婦竟也來了法瑟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤唁奢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后窝剖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體麻掸,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年赐纱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脊奋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疙描,死狀恐怖诚隙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情起胰,我是刑警寧澤久又,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響地消,放射性物質(zhì)發(fā)生泄漏炉峰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一脉执、第九天 我趴在偏房一處隱蔽的房頂上張望疼阔。 院中可真熱鬧,春花似錦半夷、人聲如沸婆廊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽否彩。三九已至,卻和暖如春嗦随,著一層夾襖步出監(jiān)牢的瞬間列荔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工枚尼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贴浙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓署恍,卻偏偏與公主長得像崎溃,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盯质,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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