什么是runtime沾乘?
1> runtime是一套底層的C語言API(包含很多強(qiáng)大實(shí)用的C語言數(shù)據(jù)類型、C語言函數(shù))
2> 實(shí)際上积蔚,平時(shí)我們編寫的OC代碼意鲸,底層都是基于runtime實(shí)現(xiàn)的
- 也就是說,平時(shí)我們編寫的OC代碼尽爆,最終都是轉(zhuǎn)成了底層的runtime代碼(C語言代碼)
runtime有啥用怎顾?
1> 能動(dòng)態(tài)產(chǎn)生一個(gè)類、一個(gè)成員變量漱贱、一個(gè)方法
2> 能動(dòng)態(tài)修改一個(gè)類槐雾、一個(gè)成員變量、一個(gè)方法
3> 能動(dòng)態(tài)刪除一個(gè)類幅狮、一個(gè)成員變量募强、一個(gè)方法
常見的函數(shù)株灸、頭文件
成員變量、類擎值、方法
#import <objc/runtime.h>
獲得某個(gè)類內(nèi)部的所有成員變量
Ivar * class_copyIvarList
獲得某個(gè)類內(nèi)部的所有方法
Method * class_copyMethodList
獲得某個(gè)實(shí)例方法(對(duì)象方法慌烧,減號(hào)-開頭)
Method class_getInstanceMethod
獲得某個(gè)類方法(加號(hào)+開頭)
Method class_getClassMethod
交換2個(gè)方法的具體實(shí)現(xiàn)
method_exchangeImplementations
消息機(jī)制
#import <objc/message.h> objc_msgSend(....)
- @param receiver 消息接收者
- @param selector 消息對(duì)應(yīng)的方法名字
- @param arg1.arg2...消息中的任意數(shù)目的參數(shù)
objc_msgSend(receiver, selector, arg1, arg2, ...)
獲取某個(gè)類的所有成員變量(延伸作用是歸檔、解檔)
- (void)testRuntimeIvar
{
// Ivar : 成員變量
unsigned int count = 0;
// 獲得所有的成員變量
//指針可以理解成為數(shù)組(根據(jù)1鸠儿、2屹蚊、3進(jìn)行取值)
Ivar *ivars = class_copyIvarList([HMPerson class], &count);
for (int i = 0; i<count; i++) {
// 取得i位置的成員變量
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"%d %s %s", i, name, type);
}
// HMPerson *p = [[HMPerson alloc] init];
// objc_msgSend(p, @selector(setAge:), 20);
// NSLog(@"%d", p.age);
}
歸檔示例
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([HMPerson class], &count);
for (int i = 0; i<count; i++) {
// 取得i位置的成員變量
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
[encoder encodeObject:[self valueForKeyPath:key] forKey:key];
}
}
交換兩個(gè)方法的實(shí)現(xiàn)
交換方法
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)
獲得方法
Method class_getClassMethod(Class cls, SEL name)
例:
分類加載進(jìn)內(nèi)存的時(shí)候會(huì)調(diào)用
load
方法,只會(huì)調(diào)用一次进每。當(dāng)使用這個(gè)方法的時(shí)候汹粤,不需要聲明也不需要寫.h
方法。因?yàn)樗?code>.m方法都會(huì)加入內(nèi)存中田晚,調(diào)用到load
中嘱兼。實(shí)現(xiàn)了
imageWithName:
和imageNamed:
方法的交換,需要注意的問題是再重寫imageWithName:
時(shí)候不要在內(nèi)部使用imageNamed:
方法
#import <objc/runtime.h>
@implementation UIImage (Extension)
/**
* 只要分類被裝載到內(nèi)存中贤徒,就會(huì)調(diào)用1次
*/
+ (void)load
{
Method otherMehtod = class_getClassMethod(self, @selector(imageWithName:));
Method originMehtod = class_getClassMethod(self, @selector(imageNamed:));
// 交換2個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(otherMehtod, originMehtod);
}
+ (UIImage *)imageWithName:(NSString *)name
{
BOOL iOS7 = [[UIDevice currentDevice].systemVersion floatValue] >= 7.0;
UIImage *image = nil;
if (iOS7) {
NSString *newName = [name stringByAppendingString:@"_os7"];
image = [UIImage imageWithName:newName];
}
if (image == nil) {
image = [UIImage imageWithName:name];
}
return image;
}
獲得方法地址
- 編譯時(shí)沒問題芹壕,運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法,這就是所謂的動(dòng)態(tài)綁定泞莉。
避免動(dòng)態(tài)綁定的唯一辦法就是取得方法的地址,并且直接象函數(shù)調(diào)用一樣調(diào)用它哪雕。當(dāng)一個(gè)方法會(huì)被連續(xù)調(diào) 用很多次,而且您希望節(jié)省每次調(diào)用方法都要發(fā)送消息的開銷時(shí),使用方法地址來調(diào)用方法就顯得很有效。
利用 Cocoa 運(yùn)行時(shí)系統(tǒng)的提供的功能 methodForSelector:
實(shí)現(xiàn)某個(gè)方法
void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled)];
for (NSInteger i = 0; i < 100; i++) {
setter(target,@selector(setFilled),YES);
}
- (void)setFilled
{
NSLog(@"123");
}
使用 methodForSelector:
來避免動(dòng)態(tài)綁定將減少大部分消息的開銷,但是這只有在指定的消息被重 復(fù)發(fā)送很多次時(shí)才有意義,例如上面的 for
循環(huán)鲫趁。
注意,methodForSelector:
是 Cocoa
運(yùn)行時(shí)系統(tǒng)的提供的功能,而不是Objective-C
語言本身的功 能斯嚎。
消息機(jī)制

當(dāng)對(duì)象收到消息時(shí),消息函數(shù)首先根據(jù)該對(duì)象的isa指針找到該對(duì)象所對(duì)應(yīng)的類的方法表,并從表中尋找 該消息對(duì)應(yīng)的方法選標(biāo)。如果找不到,objc_msgSend 將繼續(xù)從父類中尋找,直到 NSObject 類挨厚。一 旦找到了方法選標(biāo), objc_msgSend 則以消息接收者對(duì)象為參數(shù)調(diào)用,調(diào)用該選標(biāo)對(duì)應(yīng)的方法實(shí)現(xiàn)堡僻。
這就是在運(yùn)行時(shí)系統(tǒng)中選擇方法實(shí)現(xiàn)的方式吸重。在面向?qū)ο缶幊讨?一般稱作方法和消息動(dòng)態(tài)綁定的過程又碌。
為了加快消息的處理過程,運(yùn)行時(shí)系統(tǒng)通常會(huì)將使用過的方法選標(biāo)和方法實(shí)現(xiàn)的地址放入緩存中巍扛。每個(gè)類
都有一個(gè)獨(dú)立的緩存,同時(shí)包括繼承的方法和在該類中定義的方法询枚。消息函數(shù)會(huì)首先檢查消息接收者對(duì)象
對(duì)應(yīng)的類的緩存(理論上,如果一個(gè)方法被使用過一次,那么它很可能被再次使用)。如果在緩存中已經(jīng)
有了需要的方法選標(biāo),則消息僅僅比函數(shù)調(diào)用慢一點(diǎn)點(diǎn)商蕴。如果程序運(yùn)行了足夠長(zhǎng)的時(shí)間,幾乎每個(gè)消息都
能在緩存中找到方法實(shí)現(xiàn)泻红。程序運(yùn)行時(shí),緩存也將隨著新的消息的增加而增加负敏。
消息轉(zhuǎn)發(fā)
如果一個(gè)對(duì)象收到一條無法處理的消息,運(yùn)行時(shí)系統(tǒng)會(huì)在拋出錯(cuò)誤前,給該對(duì)象發(fā)送一條
forwardInvocation:
消息,該消息的唯一參數(shù)是個(gè) NSInvocation
類型的對(duì)象——該對(duì)象封裝了 原始的消息和消息的參數(shù)壤躲。
防止crash
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
//Person實(shí)現(xiàn)了`invocation selector`方法
return [Person instanceMethodSignatureForSelector:aSelector];
//傳遞參數(shù)到forwardInvocation: 的invocation的key,采用的是傳遞方式的辦法
// return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
Person *person = [[Person alloc] init];
//
// NSString *key = NSStringFromSelector([invocation selector]);
NSString *sel = NSStringFromSelector([invocation selector]);
NSLog(@"%@",sel);
if ([person respondsToSelector:
[invocation selector]])
[invocation invokeWithTarget:person];
else
NSLog(@"nothing");
//程序會(huì)crash
// [super forwardInvocation:invocation]
}
參考資料:
鏈接: [http://pan.baidu.com/s/1i3JB56d][jekyll - DB] 密碼: cmh6
[jekyll - DB]: http://pan.baidu.com/s/1i3JB56d