ios中Runtime的介紹以及使用

hither發表於2017-12-13

###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];
複製程式碼

ios中Runtime的介紹以及使用

相關文章