一阅悍、什么是RunTime
iOS開發(fā)過程中,我們一直在與Runtime打交道渗钉,可什么是Runtime呢郭厌?
對比C語言來說:
- 使用C編寫的程序在編譯過程中已經(jīng)決定了應(yīng)用要調(diào)用哪個函數(shù)。
- 使用OC代碼編寫程序時你踩,編譯階段诅岩,我們可以定義調(diào)用任意的函數(shù)(即使它不存在),只有在程序運(yùn)行的時候才會去尋找這個函數(shù)存不存在(不存在則報錯)带膜。
RunTime算是OC代碼運(yùn)行的幕后工作者吩谦,我們可以利用這種特性做一些有趣的事!
二膝藕、RunTime的常用用法
-
為分類添加屬性
-
方法交換swizzle
-
實(shí)現(xiàn)歸檔和反歸檔
-
實(shí)現(xiàn)多播委托
-
實(shí)現(xiàn)KVO
2.1 為分類添加屬性
我們知道在分類中是不允許額外添加屬性的式廷,但我們可以通過運(yùn)行時機(jī)制,給這個類添加一個關(guān)聯(lián)芭挽。
在分類的.h文件中添加屬性滑废,.m文件重寫setter
和getter
方法掠剑,然后我們就能正常使用對象的這個屬性了:
#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
中的某個周期方法時,Runtime就可以幫我們辦到。
首先創(chuàng)建一個UIViewController
的分類凑保,封裝一個方法交換的方法:
/**
* 交換函數(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:
時妻导,在分類中:
//對所有的ViewController
void swizzleAllViewController(){
swizzle([UIViewController class], @selector(viewWillAppear:), @selector(customviewWillAppear:));
}
- (void)customviewWillAppear:(BOOL)animated{
NSLog(@"customviewWillAppear");
[self customviewWillAppear:animated];
}
然后在main.m中調(diào)用swizzleAllViewController()
,就可以在整個工程中生效。
2.3實(shí)現(xiàn)歸檔和反歸檔
給NSbject添加一個分類倔韭,并遵守NSCoding
協(xié)議,然后實(shí)現(xiàn)歸檔和反歸檔的兩個方法术浪。
//遍歷類中的所有實(shí)例變量,逐個進(jìn)行歸檔和反歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int ivarCount = 0;
/*
class_copyIvarList函數(shù)寿酌,它返回一個指向成員變量信息的數(shù)組胰苏,數(shù)組中每個元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針。這個數(shù)組不包含在父類中聲明的變量醇疼。outCount指針返回數(shù)組的大小硕并。需要注意的是,我們必須使用free()來釋放這個數(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í)例變量鏈表倔毙,逐個進(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)用的時候,列表中的方法均自動執(zhí)行.
普通的delegate
只能是一對一的回調(diào)颁股,無法做到一對多的回調(diào)么库。而多播委托正式對delegate
的一種擴(kuò)展和延伸,多了一個注冊和取消注冊的過程甘有,但是任何需要回調(diào)的對象都必須先注冊诉儒。
最主要的應(yīng)用是作為XMPPframework
架構(gòu)的核心之一,且支持多線程梧疲!
多播委托的本質(zhì)允睹?
多播委托的本質(zhì)還是消息轉(zhuǎn)發(fā),如果一個對象收到一條無法處理的消息幌氮,運(yùn)行時系統(tǒng)會在拋出錯誤前缭受,給該對象發(fā)送一條forwardInvocation:
消息,該消息的唯一參數(shù)是個NSInvocation
類型的對象——該對象封裝了原始的消息和消息的參數(shù)该互。
如何實(shí)現(xiàn)米者?
為NSObjec
t添加一個分類,并添加兩個方法:添加代理和移除代理
.m文件重寫消息轉(zhuǎn)發(fā)方法
// 消息轉(zhuǎn)發(fā)
/*
消息轉(zhuǎn)發(fā)機(jī)制使用從下面這個方法中獲取的信息來創(chuàng)建NSInvocation對象宇智。因此我們必須重寫這個方法蔓搞,為給定的selector提供一個合適的方法簽名。
*/
// 獲取方法標(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)行時系統(tǒng)會在這一步給消息接收者最后一次機(jī)會將消息轉(zhuǎn)發(fā)給其它對象随橘。對象會創(chuàng)建一個表示消息的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è)置觀察一個對象的時候梆掸,會動態(tài)創(chuàng)建出一個繼承自該對象的衍生類扬卷,并重寫了觀察屬性的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 指向的類是不是一個 KVO 類。如果不是风皿,新建一個繼承原來類的子類河爹,并把 isa 指向這個新建的子類;
3桐款、檢查對象的 KVO 類是否重寫過這個 setter
方法咸这。如果沒有,添加重寫的 setter
方法魔眨;
4媳维、添加這個觀察者。
- (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方法后,通知每個觀察者(調(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ù)放在一個關(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
如果有什么問題請多多指正杂彭,謝謝墓毒!