###ios黑魔法--runtime介紹:
在Xcode5以後 ,蘋果不建議開發者使用底層。為了能夠使用runtime,我們需要做下面兩個配置:
1.在需要使用runtime的地方匯入#import<objc/message.h>。 2.在工程的Build Setting中搜尋msg ,將其修改為NO。
###引入: 在開發中,用物件去呼叫方法:
如:我們定義了一個Person類 其中有兩個方法:
-(void)eat;//物件方法
+(void)eat;//類方法
複製程式碼
Person *p = [[Person alloc]init];
[p eat];//呼叫物件方法
//p這個物件呼叫eat方法實際上是傳送了一個訊息去找到eat這個方法,並執行了這個方法:
objc_msgSend(p,@Selector(eat));
//類呼叫類方法,實際上也是用類物件去呼叫方法
Class personName = [Person class];
[personName PerformSelector:@Selector(eat)];
//實際上也執行了這個方法:
objc_msgSend(personName,@Selector(eat));
//PerformSelector這個方法就是一種動態新增方法,動態新增方法是一種懶載入的機制。
複製程式碼
利用runtime擴充套件系統功能
如:現在有這樣一個需求,imageName載入圖片,但是我們並不能知道圖片載入成功與否,我們想新增一個方法,知道image是否新增成功,所以我們就需要使用runtime來改造。
1.新建一個UIImage的category 2.匯入#import<objc/message.h> 3.提供一個自己的方法:
+(_kindof UIImage*)hjt_imageNamed:(NSString *)imageName
{
//載入圖片
UIImage *image = [UIImage imageNamed:imageName];
//進行圖片是否為空的判斷
if(image==nil){
NSLog(@"載入的圖片為空,請注意");
}
return image;
}
複製程式碼
4.重寫load方法
+(void)load{
//Class_getInstanceMethod:獲取物件方法
//Class_getMethodImplementation:獲取類方法的實現
//Class_getClassMethod:獲取類方法
Method imageNameMethod = Class_getClassMethod([UIImage class],@Selector(imageNamed:));
Method hjt_imageNameMethod = Class_getClassMethod([UIImage class],@Selector(hjt_imageNamed:));
//交換方法實現對系統方法的擴充套件
method_exchangeImplementations(imageNameMethod ,hjt_imageNameMethod);
//外部呼叫的時候還是呼叫imageNamed:
}
複製程式碼
使用runtime動態新增方法:
- 對於動態新增方法實際上就是需要藉助PerformSelector:這個方法來做事
如:我們為Person這個類動態新增一個eat的方法;
Person.m
//1.動態新增方法的第一步,先實現resolveInstanceMethod
//當我們在外面呼叫一個沒有實現的方法時,就會呼叫resolveInstanceMethod
//備註:SEl其實只是一個方法的編號,系統會根據這個編號去找這個方法;
+(BOOL)resolveInstanceMethod:(SEL)sel{
if(sel==@Selector(eat)){
//"V@:"這個需要查官方runtime的文件,v表示eatMethod的返回值void
//@表示objc,:表示Selector
Class_addMethod(self,sel,(IMP)eatMethod,"v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
複製程式碼
// 定義eatMethod函式 注意是函式!
void eatMethod(id self,SEL _cmd){
}
複製程式碼
//來到外部 我們初始化Person 然後呼叫eat這個方法
Person *p = [[Person alloc]init];
//這個是呼叫的不帶引數的eat方法
[p performSelector:@Selector(eat)];
//如果要呼叫帶引數的eat方法,我們需要進行下面幾個地方的修改
//帶引數:[p performSelector:@Selector(eat) withObject:@"apple"];
//1.修改Person.m中resolveInstanceMethod方法裡的sel==@Selector(eat:);
//2.定義的函式eatMethod的時候,新增加一個引數 id param
//3.修改Class_addMethod方法中最後一個引數為"v@:@";
複製程式碼
使用runtime動態新增屬性
- 如:我們為NSObject新增一個userName這個屬性 //動態新增屬性就是一種動態的關聯,讓物件的某個屬性去關聯某塊記憶體
1.新建一個NSObject的category
2.給某個物件產生關聯,新增屬性
//object:給那個物件新增屬性 key:屬性的名稱(通過這個key拿到關聯的物件) value:關聯的值
-(void)setName:(NSString*)name{
objc_setAssociatedObject(self,@"userName",userName,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
複製程式碼
-(NSString*)name{
return objc_getAssociatedObject(self,@"userName");
}
//這樣我們就可以拿到NSObject物件中的userName這個屬性了
複製程式碼
使用runtime 進行字典轉模型
1.寫一個NSObject的category
在NSObject+model.m中:
+(instancetype)modelWithDict:(NSDictionary*)dict{
id obic = [[self alloc] init];
//class_copyIvarList:將成員屬性列表複製一份傳出去
//Ivar*:指向一個ivar陣列的指標 count:成員屬性的個數
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self,&count);
for (int i = 0; i < count; i++){
//獲取成員屬性
Ivar ivar = ivarList [i];
//獲取成員名
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//獲取資料型別
NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
//獲取key substringFromIndex:1 去除第0個_ 即將_count這種轉換成count
NSString* key = [propertyName substringFromIndex:1];
id value = dict[key];
//對於解析出來還是一個字典,如 其中有一個字典 但是我們已經新建了一個User去解析這個字典,只有遇到這個User我們才進行這種二級轉換
//二級轉換
//值是字典,成員屬性的型別不是字典,才需要轉換成模型
//下面這個判斷的意思就是遇到我們自定義的User*user進行轉換 而NSDiction*dic不轉換
if([value iskindofClass:[NSDictionary class]]&&![propertyType containString:@"NS"]){
NSRange range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringFromIndex:range.location+range.length];
range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringToIndex:range.location];
class modelClass = NSClassFromString(property);
if(modelClass){
value = [modelClass modelWithDict:value];
}
}
if(value){
[objc setValue:value forkey:key];
}
}
return objc;
}
複製程式碼
//為UIbutton新增block 方便使用
//UIButton+HJTBlock.h
-(void)hjt_addEventHandler:(void (^) (UIButton *sender)) block forUIControlEvents:(UIControlEvents)controlEvents;
複製程式碼
//UIButton+HJTBlock.m
#import "UIButton+HJTBlock.h"
#import <objc/runtime.h>
typedef void(^HJT_ButtonEventsBlock)(UIButton *sender);
@interface UIButton ()
//event callback
@property (nonatomic,copy) HJT_ButtonEventsBlock hjt_buttonEventBlock;
@end
@implementation UIButton (HJTBlock)
static void *hjt_buttonEventsBlockKey = &hjt_buttonEventsBlockKey;
-(HJT_ButtonEventsBlock)hjt_buttonEventBlock{
return objc_getAssociatedObject(self,&hjt_buttonEventsBlockKey);
}
-(void)setHjt_buttonEventBlock:(HJT_ButtonEventsBlock)hjt_buttonEventBlock{
objc_setAssociatedObject(self, &hjt_buttonEventsBlockKey,hjt_buttonEventBlock,OBJC_ASSOCIATION_COPY);
}
-(void)hjt_addEventHandler:(void (^)(UIButton *))block forUIControlEvents:(UIControlEvents)controlEvents{
self.hjt_buttonEventBlock = block;
[self addTarget:self action:@selector(hjt_buttonClick) forControlEvents:controlEvents];
}
-(void)hjt_buttonClick{
if (self.hjt_buttonEventBlock) {
self.hjt_buttonEventBlock(self);
}
}
@end
複製程式碼
//使用:
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(20, 20, 40, 40)];
btn.backgroundColor = [UIColor redColor];
[btn setTitle:@"hello" forState:UIControlStateNormal];
[self.view addSubview:btn];
[btn hjt_addEventHandler:^(UIButton *sender) {
NSLog(@"%@",sender.titleLabel.text);
sender.backgroundColor = [UIColor blueColor];
} forUIControlEvents:UIControlEventTouchUpInside];
複製程式碼