?什么是runtime?
runtime就是運(yùn)行時(shí),因?yàn)镺bjective-C是一門動(dòng)態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時(shí)期做的事放到了運(yùn)行時(shí)來處理。也就是說只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來執(zhí)行編譯后的代碼。對(duì)于Objective-C來說,這個(gè)運(yùn)行時(shí)系統(tǒng)就像一個(gè)操作系統(tǒng)一樣:它讓所有的工作可以正常的運(yùn)行。這個(gè)運(yùn)行時(shí)系統(tǒng)即Objc Runtime。Objc Runtime其實(shí)是一個(gè)Runtime庫(kù),它基本上是用C和匯編寫的,這個(gè)庫(kù)使得C語言有了面向?qū)ο蟮哪芰Α?/p>
Runtime庫(kù)主要做下面兩件事:
封裝:在這個(gè)庫(kù)中,對(duì)象可以用C語言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來實(shí)現(xiàn),另外再加上了一些額外的特性。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,我們就可以在程序運(yùn)行時(shí)創(chuàng)建,檢查,修改類、對(duì)象和它們的方法了。
找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object doSomething]時(shí),會(huì)向消息接收者(object)發(fā)送一條消息(doSomething),runtime會(huì)根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)。OC中的方法調(diào)用:[receiver doSomething]; 在運(yùn)行時(shí)都會(huì)轉(zhuǎn)換成消息發(fā)送代碼:objc_msgSend(receiver, @selector(doSomething));,其中,receiver是消息的接收者,@selector(doSomething)是方法的實(shí)現(xiàn)。
runtime的使用場(chǎng)景
1.在程序運(yùn)行過程中, 動(dòng)態(tài)創(chuàng)建一個(gè)類(比如KVO的底層實(shí)現(xiàn))
2.在程序運(yùn)行過程中, 動(dòng)態(tài)地為某個(gè)類添加屬性\方法, 修改屬性值\方法(Objective-C中的Category無法向既有的類添加屬性, 因此可以使用runtime的關(guān)聯(lián)對(duì)象(associated objects)來實(shí)現(xiàn).)
3.遍歷一個(gè)類的所有成員變量(屬性)\所有方法
4. 給一個(gè)類添加方法
5.Method Swizzling(方法調(diào)換:通過修改一個(gè)已存在類的方法, 來實(shí)現(xiàn)方法替換是比較常用的runtime技巧)
簡(jiǎn)單介紹一下KVO的實(shí)現(xiàn)原理:
當(dāng)設(shè)置一個(gè)類為觀察對(duì)象時(shí),系統(tǒng)會(huì)動(dòng)態(tài)地創(chuàng)建一個(gè)新的類,這個(gè)新的類繼承自被觀察對(duì)象的類,還重寫了基類被觀察屬性的setter方法。派生類在被重寫的setter方法中實(shí)現(xiàn)真正的通知機(jī)制。最后,系統(tǒng)將這個(gè)對(duì)象的isa指針指向這個(gè)新創(chuàng)建的派生類,這樣,被觀察對(duì)象就變成了新創(chuàng)建的派生類的實(shí)例。(注:runtime中,對(duì)象的isa指針指向該對(duì)象所屬的類,類的isa指針指向該類的metaclass。有關(guān)OC的對(duì)象、類對(duì)象、元類對(duì)象metaclass object和isa指針,請(qǐng)戳這里詳細(xì)了解)。同時(shí),新的派生類還重寫了dealloc方法(removeObserver)。
動(dòng)態(tài)添加屬性
#import ?<UIKit/UIKit.h>
@interface UIButton (FinshClick)
@property(nonatomic,copy)void(^FinshClickBlock)(UIButton*); // 給button點(diǎn)擊回調(diào)屬性
@property(nonatomic,assign)floatclickCount;//給button添加點(diǎn)擊次數(shù)屬性
@end
#import"UIButton+FinshClick.h"
#import <objc/runtime.h> ?// 導(dǎo)入runtime框架
@implementation UIButton (FinshClick)
static char FinshClickBlockKey;
static char clickCountKey;
@dynamic FinshClickBlock;
@dynamic clickCount;
//@synthesize是默認(rèn)的聲明,意思是編譯器在編譯階段自動(dòng)為你的屬性生成setter與getter;而@dynamic則告訴編譯器,別慌,小子,編譯階段不用為我生成setter與getter,在runtime我已經(jīng)自己實(shí)現(xiàn)了setter與getter。此處我們選擇@dynamic。
// FinshClickBlock的setter方法
- (void)setFinshClickBlock:(void(^)(UIButton*))FinshClickBlock
{
objc_setAssociatedObject(self, &FinshClickBlockKey, FinshClickBlock,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//使用runtime實(shí)現(xiàn)setter方法。參數(shù)含義:1.所關(guān)聯(lián)的對(duì)象、2.分配地址、3.屬性值、4.這里的policy跟屬性聲明中的retain、assign、copy是一樣的
[selfaddTarget:selfaction:@selector(click)forControlEvents:UIControlEventTouchUpInside];//給button添加點(diǎn)擊方法
}
//FinshClickBlock的getter方法
- (void(^)(UIButton*))FinshClickBlock
{
returnobjc_getAssociatedObject(self, &FinshClickBlockKey);//
//使用runtime實(shí)現(xiàn)getter方法。參數(shù)含義:1.關(guān)聯(lián)對(duì)象2.分配地址
}
//點(diǎn)擊實(shí)現(xiàn)
- (void)click {
if(self.FinshClickBlock) {
self.clickCount++;
self.FinshClickBlock(self);
}
}
//點(diǎn)擊次數(shù)setter方法
- (void)setClickCount:(float)clickCount
{
objc_setAssociatedObject(self,&clickCountKey,@(clickCount),OBJC_ASSOCIATION_ASSIGN);
}
// getter方法
- (float)clickCount
{
return[objc_getAssociatedObject(self,&clickCountKey)floatValue];
}
@end
#import"ViewController.h"
#import"UIButton+FinshClick.h"
@interface ViewController()
@property(weak,nonatomic)IBOutlet UIButton*btn;
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
self.btn.FinshClickBlock= ^(UIButton*button) {
NSLog(@"%f",self.btn.clickCount);
};
動(dòng)態(tài)添加方法
#import"Student.h"
#import <objc/message.h>
void eat(id self,SEL sel) {
NSLog(@"----------eat");
}
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if([NSStringFromSelector(sel)isEqualToString:@"eat"]) {
class_addMethod(self, sel, (IMP)eat,"v@:");
returnYES;
}
return[superresolveInstanceMethod:sel];
}
@end
- (void)viewDidLoad {
[superviewDidLoad];
Student*stu = [[Studentalloc]init];
[stu performSelector:@selector(eat)];
}