一、什么是RunTime
iOS開發(fā)過程中,我們一直在與Runtime打交道,可什么是Runtime呢?
對比C語言來說:
- 使用C編寫的程序在編譯過程中已經(jīng)決定了應(yīng)用要調(diào)用哪個(gè)函數(shù)。
- 使用OC代碼編寫程序時(shí),編譯階段,我們可以定義調(diào)用任意的函數(shù)(即使它不存在),只有在程序運(yùn)行的時(shí)候才會去尋找這個(gè)函數(shù)存不存在(不存在則報(bào)錯(cuò))。
RunTime算是OC代碼運(yùn)行的幕后工作者,我們可以利用這種特性做一些有趣的事!
二、RunTime的常用用法
-
為分類添加屬性
-
方法交換swizzle
-
實(shí)現(xiàn)歸檔和反歸檔
-
實(shí)現(xiàn)多播委托
-
實(shí)現(xiàn)KVO
2.1 為分類添加屬性
我們知道在分類中是不允許額外添加屬性的,但我們可以通過運(yùn)行時(shí)機(jī)制,給這個(gè)類添加一個(gè)關(guān)聯(lián)。
在分類的.h文件中添加屬性,.m文件重寫setter和getter方法,然后我們就能正常使用對象的這個(gè)屬性了:
#import <Foundation/Foundation.h>
@interface NSObject (KVO)
@property (nonatomic , copy)NSString *specPro;
#import "NSObject+KVO.h"
#import <objc/objc-runtime.h>
@implementation NSObject (KVO)
- (void)setSpecPro:(NSString *)specPro{
objc_setAssociatedObject(self, @"specPro", specPro, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)specPro{
return objc_getAssociatedObject(self, @"specPro");
}
2.2實(shí)現(xiàn)方法交換
以ViewController為例,當(dāng)我們需要避開ViewController中的某個(gè)周期方法時(shí),Runtime就可以幫我們辦到。
首先創(chuàng)建一個(gè)UIViewController的分類,封裝一個(gè)方法交換的方法:
/**
* 交換函數(shù)
*
* @param cls 類
* @param originSEL 原函數(shù)
* @param newSEL 目標(biāo)函數(shù)
*/
void swizzle(Class c,SEL originSEL,SEL newSEL){
Method origMethod = class_getInstanceMethod(c, originSEL);
Method newMethod = nil;
if (!origMethod) {
origMethod = class_getClassMethod(c, originSEL);
if (!origMethod) {
return;
}
newMethod = class_getClassMethod(c, newSEL);
if (!newMethod) {
return;
}
}else{
newMethod = class_getInstanceMethod(c, newSEL);
if (!newMethod) {
return;
}
}
//自身已經(jīng)有了就添加不成功,直接交換即可
if(class_addMethod(c, originSEL, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))){
class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}else{
method_exchangeImplementations(origMethod, newMethod);
}
}
當(dāng)我們需要使用自己的customviewWillAppear:替換系統(tǒng)的viewWillAppear:時(shí),在分類中:
//對所有的ViewController
void swizzleAllViewController(){
swizzle([UIViewController class], @selector(viewWillAppear:), @selector(customviewWillAppear:));
}
- (void)customviewWillAppear:(BOOL)animated{
NSLog(@"customviewWillAppear");
[self customviewWillAppear:animated];
}
然后在main.m中調(diào)用swizzleAllViewController(),就可以在整個(gè)工程中生效。
2.3實(shí)現(xiàn)歸檔和反歸檔
給NSbject添加一個(gè)分類,并遵守NSCoding協(xié)議,然后實(shí)現(xiàn)歸檔和反歸檔的兩個(gè)方法。
//遍歷類中的所有實(shí)例變量,逐個(gè)進(jìn)行歸檔和反歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int ivarCount = 0;
/*
class_copyIvarList函數(shù),它返回一個(gè)指向成員變量信息的數(shù)組,數(shù)組中每個(gè)元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針。這個(gè)數(shù)組不包含在父類中聲明的變量。outCount指針返回?cái)?shù)組的大小。需要注意的是,我們必須使用free()來釋放這個(gè)數(shù)組。
*/
Ivar *vars = class_copyIvarList(object_getClass(self), &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar var = vars[i];
NSString *varName = [NSString stringWithUTF8String:ivar_getName(var)];
//KVC
id value = [self valueForKey:varName];
//歸檔
[aCoder encodeObject:value forKey:varName];
}
free(vars);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [self init];
if (self) {
//遍歷實(shí)例變量鏈表,逐個(gè)進(jìn)行反歸檔
unsigned int ivarCount = 0;
Ivar *vars = class_copyIvarList(object_getClass(self), &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar var = vars[i];
NSString *varName = [NSString stringWithUTF8String:ivar_getName(var)];
//反歸檔
id value = [aDecoder decodeObjectForKey:varName];
//KVC
[self setValue:value forKey:varName];
}
free(vars);
}
return self;
}
使用
//RunTime實(shí)現(xiàn)歸檔和反歸檔
//RunTime實(shí)現(xiàn)歸檔和反歸檔
Person *person1 = [[Person alloc]init];
person1.name = @"jack";
person1.age = 18;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
Person *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"%@",person2.name);//打印結(jié)果 jack
2.4實(shí)現(xiàn)多播委托
RunTime的這種應(yīng)用我其實(shí)并沒用過,但最近看到了,在這分享給大家。
什么是多播委托?
簡單的說是指允許創(chuàng)建方法的調(diào)用列表或者鏈表的能力.當(dāng)多播委托調(diào)用的時(shí)候,列表中的方法均自動執(zhí)行.
普通的delegate只能是一對一的回調(diào),無法做到一對多的回調(diào)。而多播委托正式對delegate的一種擴(kuò)展和延伸,多了一個(gè)注冊和取消注冊的過程,但是任何需要回調(diào)的對象都必須先注冊。
最主要的應(yīng)用是作為XMPPframework架構(gòu)的核心之一,且支持多線程!
多播委托的本質(zhì)?
多播委托的本質(zhì)還是消息轉(zhuǎn)發(fā),如果一個(gè)對象收到一條無法處理的消息,運(yùn)行時(shí)系統(tǒng)會在拋出錯(cuò)誤前,給該對象發(fā)送一條forwardInvocation:消息,該消息的唯一參數(shù)是個(gè)NSInvocation類型的對象——該對象封裝了原始的消息和消息的參數(shù)。
如何實(shí)現(xiàn)?
為NSObject添加一個(gè)分類,并添加兩個(gè)方法:添加代理和移除代理
.m文件重寫消息轉(zhuǎn)發(fā)方法
// 消息轉(zhuǎn)發(fā)
/*
消息轉(zhuǎn)發(fā)機(jī)制使用從下面這個(gè)方法中獲取的信息來創(chuàng)建NSInvocation對象。因此我們必須重寫這個(gè)方法,為給定的selector提供一個(gè)合適的方法簽名。
*/
// 獲取方法標(biāo)識
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegatekey));
for (id aDelegate in delegateArray) {
NSMethodSignature *sig = [aDelegate methodSignatureForSelector:aSelector];
if (sig) {
return sig;
}
}
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}
/*
運(yùn)行時(shí)系統(tǒng)會在這一步給消息接收者最后一次機(jī)會將消息轉(zhuǎn)發(fā)給其它對象。對象會創(chuàng)建一個(gè)表示消息的NSInvocation對象,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中,包括selector,目標(biāo)(target)和參數(shù)。我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對象。
*/
// 消息轉(zhuǎn)發(fā)給其他對象
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegatekey));
for (id aDelegate in delegateArray) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 異步轉(zhuǎn)發(fā)消息
[anInvocation invokeWithTarget:aDelegate];
});
}
}
@end
2.5實(shí)現(xiàn)KVO
蘋果是怎樣實(shí)現(xiàn)KVO的呢?
當(dāng)我們設(shè)置觀察一個(gè)對象的時(shí)候,會動態(tài)創(chuàng)建出一個(gè)繼承自該對象的衍生類,并重寫了觀察屬性的setter方法,該方法會在觀察屬性更改前后發(fā)出通知,因?yàn)橄到y(tǒng)會在消息發(fā)送之前更改對象的isa指針,指向衍生類,而被觀察對象也就成為了衍生類的實(shí)例(多態(tài))。
實(shí)現(xiàn)步驟?
根據(jù)蘋果內(nèi)部實(shí)現(xiàn)原理,我們可以分以下幾步實(shí)施:
1、檢查對象的類有沒有相應(yīng)的 setter方法。如果沒有拋出異常;
2、檢查對象 isa 指向的類是不是一個(gè) KVO 類。如果不是,新建一個(gè)繼承原來類的子類,并把 isa 指向這個(gè)新建的子類;
3、檢查對象的 KVO 類是否重寫過這個(gè) setter方法。如果沒有,添加重寫的 setter 方法;
4、添加這個(gè)觀察者。
- (void)addObserver:(id)observer forKey:(NSString *)key withBlock:(void (^)(id, NSString *, id, id))block{
//獲取setterName
NSString *setName = setterName(key);
SEL setSelector = NSSelectorFromString(setName);
//通過SEL獲取方法
Method setMethod = class_getInstanceMethod(object_getClass(self), setSelector);
if (!setMethod) {
@throw [NSException exceptionWithName:@"KVO Error" reason:@"沒有setter方法,無法KVO" userInfo:nil];
}
//創(chuàng)建當(dāng)前的類
//判斷是否已經(jīng)創(chuàng)建了衍生類
Class thisClass = object_getClass(self);
NSString *thisClassName = NSStringFromClass(thisClass);
if (![thisClassName hasPrefix:KVOClassPrefix]) {
thisClass = [self makeKVOClassWithOriginalClassName:thisClassName];
//改變類的標(biāo)示
object_setClass(self, thisClass);
}
//判斷衍生類是否實(shí)現(xiàn)了setter方法
if (![self hasSelector:setSelector]) {
const char *setType = method_getTypeEncoding(setMethod);
//自己添加set方法
class_addMethod(object_getClass(self), setSelector, (IMP)setter, setType);
}
NSMutableArray *observers = objc_getAssociatedObject(self, &KVOServerAssociatedKey);
if (!observers) {
observers = [NSMutableArray new];
objc_setAssociatedObject(self, &KVOServerAssociatedKey, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//創(chuàng)建觀察者info類
KVOObserverInfo *info = [[KVOObserverInfo alloc]initWithObserver:observer forKey:key withBlock:block];
[observers addObject:info];
}
//重寫setter方法,新的setter在調(diào)用原來的setter方法后,通知每個(gè)觀察者(調(diào)用之前傳入的block)
void setter(id objc_self,SEL cmd_p,id newValue){
//setterName 轉(zhuǎn)為 name
NSString *setName = NSStringFromSelector(cmd_p);
NSString *key = nameWithSetterName(setName);
//通過kvc獲取key對應(yīng)的value
id oldValue = [objc_self valueForKey:key];
//將setter消息轉(zhuǎn)發(fā)給父類
struct objc_super selfSuper = {
.receiver = objc_self,
.super_class = class_getSuperclass(object_getClass(objc_self))
};
//新版方法不帶參數(shù),這里只要在Buid Settings中搜索msg,將其修改成NO就可以了
objc_msgSendSuper(&selfSuper,cmd_p,newValue);
//調(diào)用block
NSMutableArray *observers = objc_getAssociatedObject(objc_self, &KVOServerAssociatedKey);
for (KVOObserverInfo *info in observers) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([info.key isEqualToString:key]) {
info.block(objc_self, key, oldValue, newValue);
}
});
}
}
把觀察的數(shù)據(jù)放在一個(gè)關(guān)聯(lián)的類中,如下封裝在KVOObserverInfo中:
#import <Foundation/Foundation.h>
typedef void(^ObserverBlock)(id,NSString *,id,id);
@interface KVOObserverInfo : NSObject
// 觀察者屬性
@property (nonatomic, weak) id observer;
// key屬性
@property (nonatomic, copy) NSString *key;
// 回調(diào)block
@property (nonatomic, copy) ObserverBlock block;
- (instancetype)initWithObserver:(id)observer forKey:(NSString *)key withBlock:(ObserverBlock)block;
@end
#import "KVOObserverInfo.h"
@implementation KVOObserverInfo
- (instancetype)initWithObserver:(id)observer forKey:(NSString *)key withBlock:(ObserverBlock)block {
self = [super init];
if (self) {
_observer = observer;
_key = key;
_block = block;
}
return self;
}
@end
調(diào)用:
//kvo
Person *person = [[Person alloc]init];
//給對象添加觀察者
[person addObserver:self forKey:@"name" withBlock:^(id observerObject, NSString *key, id oldValue, id newValue) {
NSLog(@"%@",oldValue);
NSLog(@"%@",newValue);
}];
person.name = @"張三";
person.name = @"李四";
打印數(shù)據(jù):

具體代碼請參考github地址:https://github.com/cusinkgetntly/RunTimeDemo.git
如果有什么問題請多多指正,謝謝!