Runtime
基本是用C和匯編寫的
Runtime 涉及三個(gè)點(diǎn),面向?qū)ο?,消息分發(fā),消息轉(zhuǎn)發(fā)
面向?qū)ο?/h4>
Objective-C 的對象是基于 Runtime 創(chuàng)建的結(jié)構(gòu)體
image
消息分發(fā)
// 創(chuàng)建person對象
Person *p = [[Person alloc] init];
// 調(diào)用對象方法
[p eat];
// 本質(zhì):讓對象發(fā)送消息
objc_msgSend(p, @selector(eat));
// 調(diào)用類方法的方式:兩種
// 第一種通過類名調(diào)用
[Person eat];
// 第二種通過類對象調(diào)用
[[Person class] eat];
// 用類名調(diào)用類方法,底層會自動把類名轉(zhuǎn)換成類對象調(diào)用
// 本質(zhì):讓類對象發(fā)送消息
objc_msgSend([Person class], @selector(eat));
消息轉(zhuǎn)發(fā)
Objective-C 的對象是基于 Runtime 創(chuàng)建的結(jié)構(gòu)體

// 創(chuàng)建person對象
Person *p = [[Person alloc] init];
// 調(diào)用對象方法
[p eat];
// 本質(zhì):讓對象發(fā)送消息
objc_msgSend(p, @selector(eat));
// 調(diào)用類方法的方式:兩種
// 第一種通過類名調(diào)用
[Person eat];
// 第二種通過類對象調(diào)用
[[Person class] eat];
// 用類名調(diào)用類方法,底層會自動把類名轉(zhuǎn)換成類對象調(diào)用
// 本質(zhì):讓類對象發(fā)送消息
objc_msgSend([Person class], @selector(eat));
完整的消息轉(zhuǎn)發(fā)流程圖:

- 1、動態(tài)方法解析
- +(BOOL)resolveInstanceMethod:(SEL)sel;
當(dāng)接受到未能識別的SEL時(shí),運(yùn)行時(shí)系統(tǒng)會調(diào)用該函數(shù)用以給對象一次機(jī)會來添加相應(yīng)的方法實(shí)現(xiàn),如果用戶在該函數(shù)中動態(tài)添加了相應(yīng)方法的實(shí)現(xiàn),則跳轉(zhuǎn)到方法的實(shí)現(xiàn)部分,并將該實(shí)現(xiàn)存入緩存中,以供下次調(diào)用。
- 2、備援接收者
- -(id)forwardingTargetForSelector:(SEL)aSelector;
如果運(yùn)行時(shí)在消息轉(zhuǎn)發(fā)的第一步中未找到所調(diào)用方法的實(shí)現(xiàn),那么當(dāng)前接收者還有第二次機(jī)會進(jìn)行未知SEL的處理。這時(shí)運(yùn)行期系統(tǒng)會調(diào)用上述方法,并將未知SEL作為參數(shù)傳入,該方法可以返回一個(gè)能處理該選擇子的對象,運(yùn)行時(shí)系統(tǒng)會根據(jù)返回的對象進(jìn)行查找,若找到則跳轉(zhuǎn)到相應(yīng)方法的實(shí)現(xiàn),則消息轉(zhuǎn)發(fā)結(jié)束。
- 3、完整的消息轉(zhuǎn)發(fā)
- -(void)forwardInvocation:(NSInvocation *)anInvocation;
當(dāng)運(yùn)行時(shí)系統(tǒng)檢測到第二步中用戶未返回能處理相應(yīng)選擇子的對象時(shí),那么來到這一步就要啟動完整的消息轉(zhuǎn)發(fā)機(jī)制了。該方法可以改變消息調(diào)用目標(biāo),運(yùn)行時(shí)系統(tǒng)根據(jù)所改變的調(diào)用目標(biāo),向調(diào)用目標(biāo)方法列表中查詢對應(yīng)方法的實(shí)現(xiàn)并實(shí)現(xiàn)跳轉(zhuǎn),這種方式和第二步的操作非常相似。當(dāng)然你也可以修改方法的選擇子,亦或者向所調(diào)用方法中追加一個(gè)參數(shù)等來跳轉(zhuǎn)到相關(guān)方法的實(shí)現(xiàn)。
最后,如果消息轉(zhuǎn)發(fā)的第三步還未能處理該未知選擇子的話,那么最終會調(diào)用NSObject類的如下方法用以異常的拋出,表明該選擇子最終未能處理。
- -(void)doesNotRecognizeSelector:(SEL)aSelector;
常用方式
替換系統(tǒng)方法
獲得某個(gè)類的類方法
Method class_getClassMethod(Class cls , SEL name)
獲得某個(gè)類的實(shí)例對象方法
Method class_getInstanceMethod(Class cls , SEL name)
交換兩個(gè)方法的實(shí)現(xiàn)
void method_exchangeImplementations(Method m1 , Method m2)
攔截系統(tǒng)方法
1.建一個(gè)分類(UIImage+Category)
2.分類中實(shí)現(xiàn)一個(gè)自定義方法,方法中寫要在系統(tǒng)方法中加入的語句
+ (UIImage *)xh_imageNamed:(NSString *)name {
double version = [[UIDevice currentDevice].systemVersion doubleValue];
if (version >= 7.0) {
// 如果系統(tǒng)版本是7.0以上,使用另外一套文件名結(jié)尾是‘_os7’的扁平化圖片
name = [name stringByAppendingString:@"_os7"];
}
return [UIImage xh_imageNamed:name];
}
3.分類中重寫UIImage的load方法,實(shí)現(xiàn)方法的交換
+ (void)load {
// 獲取兩個(gè)類的類方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(xh_imageNamed:));
// 開始交換方法實(shí)現(xiàn)
method_exchangeImplementations(m1, m2);
}
實(shí)現(xiàn)分類增加屬性(關(guān)聯(lián)對象)
.h
@property(nonatomic,copy)NSString *name;
.m
static NSString * const nameKey = @"nameKey";
- (void)setName:(NSString *)name {
// 將某個(gè)值跟某個(gè)對象關(guān)聯(lián)起來,將某個(gè)值存儲到某個(gè)對象中
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &nameKey);
}
自動歸檔解檔
自定義對象不能用于writeToFile保存,需要用于歸檔來進(jìn)行保存
NSObject+Extension
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置對應(yīng)的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 歸檔
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置對應(yīng)的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 歸檔
NSString *key = [NSString stringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
// 設(shè)置到成員變量身上
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end
字典、Json、Model轉(zhuǎn)換
//字典數(shù)據(jù)轉(zhuǎn)Model
User *user = [[User alloc] init];
//獲取當(dāng)前model所有屬性
unsigned int propertiesCount;
objc_property_t *properties = class_copyPropertyList([user class], &propertiesCount);
for (NSUInteger i = 0; i < propertiesCount; i++){
//獲取property Name
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
id value = [json valueForKey:@(propertyName)];
//使用KVC形式進(jìn)行對象賦值
[user performSelector:@selector(setValue:forKey:) withObject:value withObject:@(propertyName)];
}
上述代碼僅僅可以做到NSDictionary轉(zhuǎn)Model,并且Model不能包含自定義對象
完整代碼可能需要單獨(dú)來介紹---->Runtime 字典、Json、Model(含內(nèi)嵌對象)轉(zhuǎn)換
動態(tài)增加方法
- 開發(fā)使用場景:如果一個(gè)類方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動態(tài)給某個(gè)類,添加方法解
- 經(jīng)典面試題:怎么調(diào)用類的私有方法?有沒有使用performSelector,其實(shí)主要想問你有沒有動態(tài)添加過方法。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
//還可以調(diào)用私有方法
[p performSelector:@selector(run:) withObject:@10];
}
@end
#import "Person.h"
#import <objc/message.h>
@implementation Person
// 沒有返回值,也沒有參數(shù) --> void,(id,SEL)
void aaa(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@", meter);
}
// 任何方法默認(rèn)都有兩個(gè)隱式參數(shù),self,_cmd(_cmd代表方法編號,打印結(jié)果為當(dāng)前執(zhí)行的方法名)
// 什么時(shí)候調(diào)用:只要一個(gè)對象調(diào)用了一個(gè)未實(shí)現(xiàn)的方法就會調(diào)用這個(gè)方法,進(jìn)行處理
// 作用:動態(tài)添加方法,處理未實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == NSSelectorFromString(@"run:")) {
//aaa不會生成方法列表
class_addMethod(self, sel, (IMP)aaa, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
動態(tài)變量控制
在程序中,Person的私有屬性age是10,使用runtime變成了20。
Person *p = [[Person alloc] init];
Ivar *ivar = class_copyIvarList([p class], &count);
const char *varName = ivar_getName(var);
object_setIvar(p, var, @"20");
實(shí)現(xiàn)萬能調(diào)整(Router)
- (void)push:(NSString *)classFromString{
Class actionClass = NSClassFromString(classFromString);
id target = [[actionClass alloc] init];
if(!target){
NSLog(@"沒有當(dāng)前類");
}
if (![(NSObject *) target isKindOfClass:[UIViewController class]]) {
NSLog(@"當(dāng)前不是UIViewController");
}
UINavigationController *navigationCtr = [self getActiveNavigationController];
if ([navigationCtr isKindOfClass:[UINavigationController class]]) {
[navigationCtr pushViewController:(UIViewController *)target animated:YES];
}
}
- (UINavigationController *)getActiveNavigationController
{
if ([[UIApplication sharedApplication] delegate].window.isKeyWindow)
{
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([rootViewController isKindOfClass:[UITabBarController class]])
{
UINavigationController *navigationController = ((UITabBarController *) rootViewController).selectedViewController;
return navigationController;
} else if ([rootViewController isKindOfClass:[UINavigationController class]])
return (UINavigationController *) rootViewController;
else
return nil;
} else
return nil;
}
by 有涯sui無涯