Runtime是什么药有?
就像我們開(kāi)發(fā)或者運(yùn)行java程序需要安裝jdk一樣毅戈,使用Objective-C編寫(xiě)的程序也需要在一個(gè)特定的環(huán)境下才能運(yùn)行,這個(gè)環(huán)境就是Objective-C runtime system, 程序運(yùn)行的時(shí)候可以動(dòng)態(tài)載入類(lèi)以及向其他的對(duì)象發(fā)送消息愤惰。
由于現(xiàn)在ios開(kāi)發(fā)已經(jīng)全部需要支持64位了苇经,這里只介紹Objective-C 2.0的runtime, 也被蘋(píng)果稱(chēng)之為 “現(xiàn)代的運(yùn)行時(shí)(the modern runtime)”宦言, 其新的特性為:
當(dāng)在runtime中動(dòng)態(tài)改變一個(gè)類(lèi)中的參數(shù)后扇单,不需要重新編譯這個(gè)類(lèi)的子類(lèi)。
Runtime能做什么奠旺?
鑒于很多文章已經(jīng)對(duì)官方文檔進(jìn)行了翻譯蜘澜,我這里著重總結(jié)一下runtime在實(shí)際場(chǎng)景下的應(yīng)用:
1、Introspection, 獲得對(duì)象中的信息响疚,如Class, Selector(SEL), Method:
現(xiàn)在我們有一個(gè)類(lèi)RuntimeObject.h:
#import <Foundation/Foundation.h>
@interface RuntimeObject : NSObject
@property (nonatomic, strong) NSString *name;
@property (assign) NSInteger age;
- (BOOL)canDrink:(NSInteger)age;
@end
RuntimeObject.m:
#import "RuntimeObject.h"
@implementation RuntimeObject
- (BOOL)canDrink:(NSInteger)age {
return age >= 18;
}
@end
下面先創(chuàng)建一個(gè)RuntimeObject的實(shí)例:
RuntimeObject *ro = [[RuntimeObject alloc] init];
ro.name = @"Leakey";
ro.age = !28;
獲得類(lèi)的信息:
// Class class = [ro class];
Class class = object_getClass(ro); //等效[ro class];
// Class superClass = [ro superclass];
Class superClass = class_getSuperclass(class);//等效[ro superclass];
NSLog(@"super class of %@ is:%@", NSStringFromClass(class), NSStringFromClass(superClass));
// 輸出:super class of RuntimeObject is:NSObject
獲得方法信息:
SEL selector = @selector(canDrink:);
NSLog(@"Selector; %@", NSStringFromSelector(selector));
// 輸出: Selector; shouldHeDrink:
Method method = class_getInstanceMethod(class, @selector(name));
NSLog(@"%d arguments", method_getNumberOfArguments(method));
// 輸出: 2 arguments
// 方法不管有參數(shù)結(jié)果始終是2鄙信,因?yàn)?objc_msgSend有兩個(gè)參數(shù),一個(gè)是指向?qū)ο蟮闹羔樂拊危粋€(gè)是對(duì)象的selector
判斷是否屬于是否個(gè)類(lèi)的實(shí)現(xiàn)装诡,或者是屬于某個(gè)類(lèi):
NSLog(@"Member of NSObject: %d", [ro isMemberOfClass:[NSObject class]]);
// 輸出: Member of NSObject: 0 因?yàn)閞o不是NSObject的實(shí)例
NSLog(@"Kind of NSObject: %d", [ro isKindOfClass:[NSObject class]]);
// 輸出: Kind of NSObject: 1 RuntimeObject是NSObject的子類(lèi),所以是NSObject
執(zhí)行這個(gè)對(duì)象中的方法:
Objective-C發(fā)送消息的步驟為:
1践盼、向?qū)ο蟀l(fā)送了一個(gè)消息鸦采,每一個(gè)類(lèi)會(huì)有一個(gè)isa指針指向這個(gè)類(lèi)的結(jié)構(gòu)體(structure),在結(jié)構(gòu)體中會(huì)有一個(gè) dispatch table宏侍, 包含這個(gè)類(lèi)中的selector赖淤,如果消息在這個(gè)類(lèi)的dispatch table 中找不到這個(gè)selector,就會(huì)通過(guò)isa指針指向這個(gè)類(lèi)的父類(lèi)谅河,在這個(gè)類(lèi)的super class中找是否含有selector咱旱,直到找到這個(gè)方法再動(dòng)態(tài)綁定這條消息。但是當(dāng)消息發(fā)送到root class還沒(méi)有找到這個(gè)selector的時(shí)候绷耍,就會(huì)報(bào):unrecognized selector sent to instance
// LLVM 6.0后增加了OBJC_OLD_DISPATCH_PROTOTYPES吐限,需要在build setting中將Enable Strict Checking of objc_msgSend Calls設(shè)置為NO才可以使用objc_msgSend(id self, SEL op, ...);
NSString *name = objc_msgSend(ro, @selector(name));
//NSString *name = [ro name];
BOOL canDrink = ((const BOOL (*)(id, SEL, ...))objc_msgSend)(ro, @selector(canDrink:), 24);
canDrink = [ro canDrink:24];
NSLog(@"name is:%@", name);
if (canDrink) {
NSLog(@"%@可以喝酒", name);
} else {
NSLog(@"%@不能喝酒", name);
}
// 輸出: Leakey可以喝酒
使用隱藏的參數(shù):
objc_msgSend方法中有兩個(gè)參數(shù):
1、接收消息的對(duì)象 (id self)
2褂始、方法的selector(SEL op)
_cmd是一個(gè)特別的SEL, 可以在方法里用_cmd獲得這個(gè)方法的信息诸典,通常用來(lái)記錄日志
#define debug 1
- (void)aMethod {
if (debug==1) {
NSLog(@"Running %@ '%@'", self.class,NSStringFromSelector(_cmd));
}
}
// 在AppDelegate中調(diào)用輸出: Running AppDelegate 'aMethod'
獲得方法的地址:
當(dāng)需要很多次調(diào)用方法的時(shí)候,可以通過(guò)這個(gè)方法的地址直接調(diào)用這個(gè)方法崎苗,避免每次去尋找這個(gè)方法的地址狐粱。
NSObject類(lèi)中有一個(gè)methodForSelector:方法,可以返回方法的指針(IMP)舀寓,再通過(guò)這個(gè)指針來(lái)調(diào)用方法可以減少方法調(diào)用所花的時(shí)間。
我們?cè)赗untimeObject中添加一個(gè)方法:
- (void)setNumber: (NSInteger)num {
NSLog(@"%ld", num);
}
調(diào)用1000次此方法可以用以下寫(xiě)法:
void (*setter)(id, SEL, NSInteger);
int i;
// ro是之前創(chuàng)建的RuntimeObject對(duì)象
setter = (void (*)(id, SEL, NSInteger))[ro
methodForSelector:@selector(setNumber:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(ro, @selector(setNumber:), i);
這里是一個(gè)對(duì)象調(diào)用多次方法肌蜻,當(dāng)然多個(gè)實(shí)體調(diào)用同一個(gè)方法只需將最后一行setter中的ro改成對(duì)應(yīng)的 targetList[i]即可互墓,要注意的是前面定義的void (*setter)(id, SEL, NSInteger);里的參數(shù)必須與需要調(diào)用的方法的參數(shù)一致,其中id,SEL是objc_msgSend的前兩個(gè)參數(shù)蒋搜, 后面的是方法的參數(shù)篡撵。
此文列舉了用runtime獲取class、selector以及method的信息豆挽,以及一些使用場(chǎng)景育谬,我也會(huì)不斷完善runtime在實(shí)際項(xiàng)目中的用法。