runtime 就是OC 中經常說的 運行時
這裏 簡單介紹一下 OC 中用到一些場景
1、字典轉模型
2、給分類添加關聯對象
3、交換方法
runtime 使用的時候一般建立一個 NSObject 的分類Cotegory 。 當然也可以根據 實際情況創建其他類的Cotegory。在創建的文件裏面需要導入
#import <objc/runtime.h>
一、字典轉模型。
首先 動態的獲取 類 的屬性名稱 -> 然後使用 KVC 進行賦值.
1、首先獲取類的屬性名稱
.h
/**
獲取 屬性的名稱數組
@return 返回對象的屬性名稱數組
*/
+ (NSArray *)cz_objPropertiesAry;
.m
const void *kPropertiesKey = "kPropertiesKey";
+ (NSArray *)cz_objPropertiesAry
{
//獲取屬性數組的指針數組
unsigned int count = 0;
objc_property_t *property = class_copyPropertyList([self class], &count);
//創建存放數據的數組
NSMutableArray *array = [NSMutableArray array];
//遍歷屬性數組
for (unsigned int i = 0; i < count; i++) {
// 指針 C語言中數組的名字 爲第一個元素的指針
objc_property_t pty = property[i];
//獲取屬性的名字 類型爲C的字符串
const char *cName = property_getName(pty);
//把C字符串轉化爲OC字符串
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
//[NSString stringWithUTF8String:cName]; 也可以
//把信息添加到數組裏面
[array addObject:name];
}
//釋放屬性數組
free(property);
return array.copy;
}
unsigned int count =0; //下面爲數組
objc_property_t *proList = class_copyPropertyList([selfclass], &count);
注意,這個方法獲取的數組最後要用 free(proList); 釋放掉
class_copyPropertyList 獲取 類的屬性的列表
class_copyIvarList 獲取類的 成員變量的列表
class_copyMethodList 獲取類 的方法的列表
class_copyProtocolList 獲取類的代理的列表
參數
1、 要獲取的這個類 因爲是Cotegory 分類 ,使用 self
2、列表裏面元素的個數 unsigned int 無符號的整形
返回值:
所有屬性的數組 類型爲 objc_property_t 的數組
再遍歷類的屬性數組 ,因爲類型爲 objc_property_t 是一個指針,使用property_getName獲取這個指針對應屬性的名字。
使用property_getName 獲取的是一個const char 類型C 語言的字符串 再轉化爲OC的字符串 。最後添加到存儲數據的數組裏面。
獲取到類的屬性名稱後,就可以使用KVC 就行賦值了。
//所有字典轉模型框架 核心算法
+ (instancetype)cz_objcWithDictionary:(NSDictionary *)dict
{
//實例化對象
id object = [[self alloc]init];
//獲取對象的屬性名稱數組
//1> 獲得 self 的屬性列表
NSArray *array = [self cz_objPropertiesAry];
//遍歷字典 使用KVC 爲數組中的屬性賦值
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
//需要先判斷數組中是否包含 字典的Key
if ([array containsObject:key]) {
//賦值
[object setValue:obj forKey:key];
}
}];
return object;
}
使用:
創建一個Person 類 不實現.m
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double height;
@property (nonatomic, copy) NSString *title;
@end
在ViewController 裏面
導入
#import "Person.h"
#import "NSObject+Runtime.h"
//獲取Persion的屬性列表數組
NSArray *properties = [Person cz_objPropertiesAry];
NSLog(@"%@",properties);
NSDictionary *dic = @{@"name":@"張三",@"age":@(22),@"height":@(168),@"title":@"運行時",@"place":@"boss"};
Person *persion = [Person cz_objcWithDictionary:dic];
NSLog(@"%@",persion);
二、添加關聯 動態的添加屬性值
修改獲取類屬性的方法
const void *kPropertiesKey = "kPropertiesKey";
+ (NSArray *)cz_objPropertiesAry
{
#pragma mark 關聯對象 獲取 屬性值 沒有 則添加屬性值 (動態的添加屬性值)
/*
此方法雖然能夠獲取類的屬性數組 但是如果每次調用都要執行一次的話 耗費的時間長,
使用 關聯對象 動態的添加屬性值 分別在方法的開頭和結尾
*/
/*
參數:
1、對象 self
2、const void 的key
返回值 : Id類型
添加的屬性值
*/
NSArray *proList = objc_getAssociatedObject(self, kPropertiesKey);
if (proList) { //如果獲取的關聯對象裏面有元素 就直接返回
return proList;
}
//獲取屬性數組的指針數組
unsigned int count = 0;
objc_property_t *property = class_copyPropertyList([self class], &count);
//創建存放數據的數組
NSMutableArray *array = [NSMutableArray array];
//遍歷屬性數組
for (unsigned int i = 0; i < count; i++) {
// 指針 C語言中數組的名字 爲第一個元素的指針
objc_property_t pty = property[i];
//獲取屬性的名字 類型爲C的字符串
const char *cName = property_getName(pty);
//把C字符串轉化爲OC字符串
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
//[NSString stringWithUTF8String:cName]; 也可以
//把信息添加到數組裏面
[array addObject:name];
}
//釋放屬性數組
free(property);
#pragma mark 關聯對象 2 添加屬性值 動態的添加屬性值
/*
參數:
1、對象 self
2、const void 的key 同第一步
3、添加的屬性值
4、關聯的協議
*/
objc_setAssociatedObject(self, kPropertiesKey, array.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return array.copy;
}
修改的方法 裏面 多了幾行代碼
NSArray *proList = objc_getAssociatedObject(self, kPropertiesKey);
if (proList) {
return proList;
}
objc_setAssociatedObject(self, kPropertiesKey, array.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
分別在方法的開始和結尾
objc_getAssociatedObject 獲取關聯對象動態添加的屬性值
參數:
1、對象 self
2、const void 的key
返回值:
Id類型, 添加的屬性值
objc_setAssociatedObject 設置動態添加關聯對象的屬性值
參數:
1、對象 self
2、const void 的key 跟上面獲取的key一致
3、要添加的屬性值,(如果設置了,上面的objc_getAssociatedObject 就能直接獲取到,下次調用就能直接獲取屬性值)
4、關聯的協議 OBJC_ASSOCIATION_RETAIN_NONATOMIC添加了 了關聯對象 的方法,下次調用該方法的時候,運行到objc_getAssociatedObject 就能直接獲取到 數據了,不用再往下執行了。
三、交換方法
特點:
在無法修改系統方法 ,和第三方框架的時候,
1、利用交換方法,先執行自定義的方法
2、再執行系統方法或第三方框架方法。
被稱之爲 黑魔法 ,對系統和框架有很強的依賴性。
舉個例子:
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
imageView.center = self.view.center;
[self.view addSubview:imageView];
imageView.image = [UIImage imageNamed:@"image5.png"];
在UIImageView 的時候,系統的設置圖片的方法, imageView.image = [UIImage imageNamed:@""];
imageView.image 相當於 imageView setImage: UIImage
我們可以自定義一個方法,替換掉系統的setImage: 方法
創建一個UIImageView 的Cotegory
//在類 被加載到運行時的時候,就會執行
+ (void)load{
//交叉方法 就下面的3句話
//獲取 類 實例化的原始方法 setImage:
Method originalMethod = class_getInstanceMethod([self class], @selector(setImage:));
//獲取 自定義的類的實例化的方法 cz_setImage:
Method swizzleMethod = class_getInstanceMethod([self class], @selector(cz_setImage:));
//交換兩個方法 setImage: 和 cz_setImage: 完成之後
//1> 調用setImage: 相當於調用 cz_setImage:
//2> 調用 cz_setImage: 相當於調用 setImage:
method_exchangeImplementations(originalMethod, swizzleMethod);
}
//自定義 的 類 的實例化方法 Cotegory
- (void)cz_setImage:(UIImage *)image
{
//這裏是我們想要做的事情
NSLog(@"調用的自定義的方法: %s",__FUNCTION__);
<span style="white-space:pre"> </span>
//再調用系統 的默認方法
/*重點 :
爲什麼 方法名是自定義方法 而不是系統默認的方法名字
是因爲load 裏面 系統默認方法和自定義的方法進行了交換
系統默認的方法 setImage: 變成了 cz_setImage:
所以 在交換方法完成之後 再次調用系統默認的方法就變成了 我們自定義的方法
*/
[self cz_setImage:result];
}
首先先自定義一個設置圖片的方法- (void) cz_setImage:(UIImage *)image
裏面 輸出 當前的方法。
在方法 + (void) load{} 裏面添加 我們的交換方法
就3句話,
1、獲取系統默認的 設置圖片的方法
2、獲取自定義的設置圖片的方法
3、交換這兩個方法
class_getInstanceMethod 獲取類的實例的方法
參數:
1、類的實例 [self class] (這是一種特殊的實例化對象)
2、獲取的實例方法 SEL返回值:
Method 方法 類型
method_exchangeImplementations
參數:
1、要交換的第一個方法
2、要交換的第二個方法
重點注意:
在方法交換之後,兩個方法已經被交換了,調用系統方法 變成調用自定義的方法名
調用自定義的方法 變成調用系統的方法名
所以下面:
//自定義 的 類 的實例化方法 Cotegory
- (void)cz_setImage:(UIImage *)image
{
//這裏是我們想要做的事情 輸出當前調用的方法
NSLog(@"調用的自定義的方法: %s",__FUNCTION__);
<span> </span>
//再調用系統 的默認方法
/*重點 :
爲什麼 方法名是自定義方法 而不是系統默認的方法名字
是因爲load 裏面 系統默認方法和自定義的方法進行了交換
系統默認的方法 setImage: 變成了 cz_setImage:
所以 在交換方法完成之後 再次調用系統默認的方法就變成了 我們自定義的方法
*/ 這個時候系統默認的方法名 在這裏變成了 <span style="font-family: Arial, Helvetica, sans-serif;">cz_setImage:</span>
[self cz_setImage:result];
}
ViewController 的運行結果爲
#import "UIImageView+RuntimeCrossoverMethod.h"
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
imageView.center = self.view.center;
[self.view addSubview:imageView];
imageView.image = [UIImage imageNamed:@"image5.png"];
2016-10-14 16:38:01.248 Runtime[4947:837294]調用的自定義的方法: -[UIImageView(RuntimeCrossoverMethod) cz_setImage:]