目錄[-]面試
相信不少同窗都聽過運行時,可是我相信仍是有不少同窗不瞭解什麼是運行時,到底在項目開發中怎麼用?何時適合使用?想一想咱們的項目中,到底在哪裏使用過運行時呢?還能想起來嗎?另外,在面試的時候,是否常常有筆試中要求運用運行時或者在面試時面試官會問是否使用過運行時,又是如何使用的?數組
回想本身,曾經在面試中被面試官拿運行時刁難過,也在筆試中遇到過。所以,後來就深刻地學習了Runtime
機制,學習裏面的API。因此纔有了後來的組件封裝中使用運行時。網絡
相信咱們都遇到過這樣一個問題:我想在擴展(category)中添加一個屬性,若是iOS是不容許給擴展類擴展屬性的,那怎麼辦呢?答案就是使用運行時機制函數
Runtime
是一套比較底層的純C語言的API, 屬於C語言庫, 包含了不少底層的C語言API。 在咱們平時編寫的iOS代碼中, 最終都是轉成了runtime的C語言代碼。學習
所謂運行時,也就是在編譯時是不存在的,只是在運行過程當中纔去肯定對象的類型、方法等。利用Runtime
機制能夠在程序運行時動態修改類、對象中的全部屬性、方法等。測試
還記得咱們在網絡請求數據處理時,調用了-setValuesForKeysWithDictionary:
方法來設置模型的值。這裏什麼原理呢?爲何能這麼作?其實就是經過Runtime
機制來完成的,內部會遍歷模型類的全部屬性名,而後設置與key
對應的屬性名的值。atom
咱們在使用運行時的地方,都須要包含頭文件:#import
。若是是Swift
就不須要包含頭文件,就能夠直接使用了。spa
利用運行時獲取對象的全部屬性名是能夠的,可是變量名獲取就得用另外的方法了。咱們能夠經過class_copyPropertyList
方法獲取全部的屬性名稱。.net
下面咱們經過一個Person
類來學習,這裏的方法沒有寫成擴展,只是爲了簡化,將獲取屬性名的方法直接做爲類的實例方法:代理
@interface Person : NSObject { NSString *_variableString; } // 默認會是什麼呢? @property (nonatomic, copy) NSString *name; // 默認是strong類型 @property (nonatomic, strong) NSMutableArray *array; // 獲取全部的屬性名 - (NSArray *)allProperties; @end
下面主要是寫如何獲取類的全部屬性名的方法。注意,這裏的objc_property_t
是一個結構體指針objc_property *
,所以咱們聲明的properties
就是二維指針。在使用完成後,咱們必定要記得釋放內存,不然會形成內存泄露。這裏是使用的是C語言的API,所以咱們也須要使用C語言的釋放內存的方法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是一個數組指針,是C的語法, // 咱們須要使用free函數來釋放內存,不然會形成內存泄露 free(properties); return propertiesArray; }
對於獲取對象的全部屬性名,在上面的-allProperties
方法已經能夠拿到了,可是並無處理獲取屬性值,下面的方法就是能夠獲取屬性名和屬性值,將屬性名做爲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]); }
看下打印結果,屬性值爲空的屬性並無打印出來,所以字典的key對應的value不能爲nil:
propertyName: name propertyValue: 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]; // 獲取方法名稱,可是類型是一個SEL選擇器類型 SEL methodSEL = method_getName(method); // 須要獲取C字符串 const char *name = sel_getName(methodSEL); // 將方法名轉換成OC字符串 NSString *methodName = [NSString stringWithUTF8String:name]; // 獲取方法的參數列表 int arguments = method_getNumberOfArguments(method); NSLog(@"方法名:%@, 參數個數:%d", methodName, arguments); } // 記得釋放 free(methods); }
測試一下:
[p allMethods];
調用打印結果以下,爲何參數個數看起來不匹配呢?好比-allProperties
方法,其參數個數爲0纔對,可是打印結果爲2。根據打印結果可知,無參數時,值就已是2了。:
方法名:allProperties, 參數個數:2 方法名:allPropertyNamesAndValues, 參數個數:2 方法名:allMethods, 參數個數:2 方法名:setArray:, 參數個數:3 方法名:.cxx_destruct, 參數個數:2 方法名:name, 參數個數:2 方法名:array, 參數個數:2 方法名:setName:, 參數個數:3
要獲取對象的成員變量,能夠經過class_copyIvarList
方法來獲取,經過ivar_getName
來獲取成員變量的名稱。對於屬性,會自動生成一個成員變量。
- (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]; } free(ivars); return results; }
測試一下:
for (NSString *varName in p.allMemberVariables) { NSLog(@"%@", varName); }
打印結果說明屬性也會自動生成一個成員變量:
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
iOS中,能夠在運行時發送消息,讓接收消息者執行對應的動做。可使用objc_msgSend
方法,發送消息。
Person *p = [[Person alloc] init]; p.name = @"Lili"; objc_msgSend(p, @selector(allMethods));
iOS的category是不能擴展存儲屬性的,可是咱們能夠經過運行時關聯來擴展「屬性」。
假設擴展下面的「屬性」:
// 因爲擴展不能擴展屬性,所以咱們這裏在實現文件中須要利用運行時實現。 typedef void(^HYBCallBack)(); @property (nonatomic, copy) HYBCallBack callback;
在實現文件中,咱們用一個靜態變量做爲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); }
其實就是經過objc_getAssociatedObject
取得關聯的值,經過objc_setAssociatedObject
設置關聯。
在開發中,咱們比較經常使用的是使用關聯屬性的方式來擴展咱們的「屬性」,以便在開發中簡單代碼。咱們在開發中使用關聯屬性擴展全部響應事件、將代理轉換成block版等。好比,咱們能夠將全部繼承於UIControl的控件,都擁有block版的點擊響應,那麼咱們就能夠給UIControl擴展一個TouchUp、TouchDown、TouchOut的block等。
對於動態獲取屬性的名稱、屬性值使用較多的地方通常是在使用第三方庫中,好比MJExtension等。這些三方庫都是經過這種方式將Model轉換成字典,或者將字典轉換成Model。