前言
上篇主要講Runtime的一些術語描述和定義去枷,Runtime的主要應用是用于消息的傳遞,今天會結合一些實戰(zhàn)例子來講下OC的消息傳遞機制。
普通消息傳遞
在OC里删顶,對象調用方法叫作發(fā)送消息疗隶,對象調用方法在Runtime里被轉化為objc_msgSend函數(shù)來實現(xiàn)
[receiver oneMethod];
//transfer to :
objc_msgSend(receiver, @selector(oneMethod));
Runtime會根據(jù)類型自動轉換為下列某個函數(shù):
objc_msgSend:普通的消息都會通過該函數(shù)發(fā)送;
objc_msgSend_stret:消息中有數(shù)據(jù)結構作為返回值(不是簡單值)時翼闹,通過此函數(shù)發(fā)送和接收返回值斑鼻;
objc_msgSendSuper:和objc_msgSend類似,這里把消息發(fā)送給父類的實例猎荠;
objc_msgSendSuper_stret:和objc_msgSend_stret類似坚弱,這里把消息發(fā)送給父類的實例并接收返回值;
objc_msgSend的調用過程
1.
先檢查這個selector是否存在关摇,不存在則忽略荒叶;
2.
接著檢查selector的target是否為nil,向nil對象發(fā)送任何消息都會被忽略掉输虱;
3.
前面兩步?jīng)]問題些楣,則先在isa指針指向的class的cache里面找有沒有方法調用記錄,如果有宪睹,則運行對應的函數(shù)愁茁,如果沒有則在class的methodLists查找方法,沒有則通過super_class指針找到父類的類對象結構體亭病,然后從methodLists查找方法鹅很,如果仍找不到,則繼續(xù)通過
super_class向上一級父類結構體中查找罪帖,直到根類(NSObject)促煮;
4.
如果還是找不到,則進入消息動態(tài)解析整袁;
消息動態(tài)解析
動態(tài)解析流程圖:
具體解析:
1.
通過resolveInstanceMethod菠齿,該方法決定是否動態(tài)添加方法。如果返回YES坐昙,則通過class_addMethod動態(tài)添加方法绳匀,消息得到處理,結束民珍;如果返回NO襟士,則進入下一步;
2.
這一步會步入forwardingTargetForSelector方法嚷量,用于指定備選對象響應這個selector,不能指定為self逆趣,如果放回某個對象則會調用對象的方法蝶溶,結束。如果放回nil,則進入第三步抖所;
3.
這一步梨州,我們通過methodSignatureForSelector進行方法簽名,如果返回nil田轧,則消息無法處理暴匠。如果返回methodSignature則進入下一步;
4.
這步調用forwardInvocation方法傻粘,我們通過anInvocation對象做很多處理每窖,比如修改實現(xiàn)方法,修改響應對象弦悉,如果方法方法調用成功窒典,則結束。如果失敗稽莉,則進入doesNotRecognizeSelector瀑志,如果沒有實現(xiàn)這個方法會crash
實戰(zhàn)
通過runtime動態(tài)創(chuàng)建類和對象
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
void sayFunction(id self, SEL _cmd, id some){
NSLog(@"%@歲的%@說:%@",object_getIvar(self, class_getInstanceVariable([self class], "_age")),object_getIvar(self, class_getInstanceVariable([self class], "_name")),some);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//動態(tài)創(chuàng)建類
Class MyPeople = objc_allocateClassPair([NSObject class], "Person", 0);
//為類添加成員變量
class_addIvar(MyPeople, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
class_addIvar(MyPeople, "_age", sizeof(int),sizeof(int),@encode(int));
//注冊方法("v@:@"代表返回值+參數(shù)列表)
SEL say = sel_registerName("say:");
class_addMethod(MyPeople, say, (IMP)sayFunction, "v@:@");
//注冊類
objc_registerClassPair(MyPeople);
//創(chuàng)建實例對象
id peopleInstance = [[MyPeople alloc]init];
[peopleInstance setValue:@"李明" forKey:@"name"];
//獲取成員變量
Ivar age = class_getInstanceVariable(MyPeople, "_age");
object_setIvar(peopleInstance, age, @18);
//發(fā)送消息
objc_msgSend(peopleInstance,say,@"你好呀");
//銷毀實例對象
peopleInstance = nil;
//當類或子類的實例存在,則不能銷毀類
objc_disposeClassPair(MyPeople);
}
return 0;
}
tip:
默認會出現(xiàn)以下錯誤:
objc_msgSend()報錯Too many arguments to function call ,expected 0,have3
直接通過objc_msgSend(self, setter, value)是報錯污秆,說參數(shù)過多劈猪。
請這樣解決:
Build Setting–> Apple LLVM 7.0 – Preprocessing–> Enable Strict Checking of objc_msgSend Calls 改為 NO
通過runtime獲取類的相關信息(屬性、實例變量良拼、方法)
#import <Foundation/Foundation.h>
@interface People : NSObject{
NSString* _nationality;
}
@property(nonatomic,copy)NSString* name;
@property(nonatomic,strong)NSNumber* age;
/**
* 獲取所有屬性
**/
-(NSDictionary*)getAllProperties;
/**
* 獲取所有實例變量
**/
-(NSDictionary*)getAllIvars;
/**
* 獲取所有方法
**/
-(NSDictionary*)getAllMethods;
@end
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
-(NSDictionary *)getAllProperties{
unsigned int count = 0;
NSMutableDictionary* result = [@{} mutableCopy];
objc_property_t* properties = class_copyPropertyList([self class], &count);
for (NSUInteger i = 0; i<count; i++) {
const char* propertyName = property_getName(properties[i]);
NSString* name = [NSString stringWithUTF8String:propertyName];
id propertyValue = [self valueForKey:name];
if (propertyValue) {
result[name] = propertyValue;
}else{
result[name] = @"value 不能為 nil";
}
}
free(properties);
return result;
}
-(NSDictionary *)getAllIvars{
unsigned int count = 0;
NSMutableDictionary* result = [@{} mutableCopy];
Ivar* ivars = class_copyIvarList([self class], &count);
for (NSUInteger i = 0; i<count; i++) {
const char* ivarName = ivar_getName(ivars[i]);
NSString* name = [NSString stringWithUTF8String:ivarName];
id value = [self valueForKey:name];
if (value) {
result[name] = value;
}else{
result[name] = @"value 不能為 nil";
}
}
free(ivars);
return result;
}
-(NSDictionary*)getAllMethods{
unsigned int count = 0;
NSMutableDictionary* result = [@{} mutableCopy];
Method* methods = class_copyMethodList([self class], &count);
for (NSUInteger i = 0; i<count; i++) {
const char* methodName = sel_getName(method_getName(methods[i]));
NSString* name = [NSString stringWithUTF8String:methodName];
//獲取參數(shù)列表
int args = method_getNumberOfArguments(methods[i]);
result[name] = [NSString stringWithFormat:@"args count is %d",args-2 ];
}
free(methods);
return result;
}
#import <Foundation/Foundation.h>
#import "People.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
People* p = [[People alloc]init];
p.name = @"羅大錘";
p.age = @(30);
[p setValue:@"中國" forKey:@"nationality"];
NSDictionary* properties = [p getAllProperties];
NSDictionary* ivars = [p getAllIvars];
NSDictionary* methods = [p getAllMethods];
NSLog(@"屬性為:%@",properties);
NSLog(@"實例變量:%@",ivars);
NSLog(@"方法:%@",methods);
}
return 0;
}
打印結果:
2016-05-13 11:22:36.298 runtime 之 獲取類的相關信息(屬性岸霹、實例變量、方法)[2783:307932] 屬性為:{
age = 30;
name = "\U7f57\U5927\U9524";
}
2016-05-13 11:22:36.299 runtime 之 獲取類的相關信息(屬性将饺、實例變量贡避、方法)[2783:307932] 實例變量:{
"_age" = 30;
"_name" = "\U7f57\U5927\U9524";
"_nationality" = "\U4e2d\U56fd";
}
2016-05-13 11:22:36.299 runtime 之 獲取類的相關信息(屬性、實例變量予弧、方法)[2783:307932] 方法:{
".cxx_destruct" = "args count is 0";
age = "args count is 0";
getAllIvars = "args count is 0";
getAllMethods = "args count is 0";
getAllProperties = "args count is 0";
name = "args count is 0";
"setAge:" = "args count is 1";
"setName:" = "args count is 1";
}
Program ended with exit code: 0
通過Runtime給category添加屬性
#import "People.h"
typedef void(^CallBackSomething)();
@interface People (testCategory)
@property(nonatomic,copy)NSString* address;
@property(nonatomic,copy)CallBackSomething block;
@end
#import "People+testCategory.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People (testCategory)
-(void)setAddress:(NSString *)address{
objc_setAssociatedObject(self, @selector(address), address, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)address{
return objc_getAssociatedObject(self, @selector(address));
}
-(void)setBlock:(CallBackSomething)block{
objc_setAssociatedObject(self, @selector(block), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(CallBackSomething)block{
return objc_getAssociatedObject(self, @selector(block));
}
@end
利用Runtime給對象歸檔和解檔
#import <Foundation/Foundation.h>
@interface People : NSObject<NSCoding>{
NSString* _nationality;
}
@property(nonatomic,copy)NSString* name;
@property(nonatomic,strong)NSNumber* age;
@end
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
//歸檔
-(void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int count = 0;
Ivar* ivars = class_copyIvarList([self class],&count);
for (NSUInteger i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char* name = ivar_getName(ivar);
NSString* key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
//解檔
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int count = 0;
Ivar* ivars = class_copyIvarList([self class],&count);
for (NSUInteger i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char* name = ivar_getName(ivar);
NSString* key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end
利用Runtime實現(xiàn)Model與字典互轉
#import <Foundation/Foundation.h>
@interface People : NSObject
@property(nonatomic,copy)NSString* name;
@property(nonatomic,strong)NSNumber* age;
/**
* 字典 轉 Model
**/
-(instancetype)initWithDictionary:(NSDictionary*) dict;
/**
* Model轉換成字典
**/
-(NSDictionary*)covertToDictionary;
@end
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
-(instancetype)initWithDictionary:(NSDictionary *)dict{
if (self = [super init]) {
for (NSString* key in dict) {
id val = dict[key];
SEL setter = [self propertySetterByKey:key];
if (setter) {
objc_msgSend(self, setter,val);
}
}
}
return self;
}
-(NSDictionary *)covertToDictionary{
unsigned int count = 0;
objc_property_t* properties = class_copyPropertyList([self class], &count);
if (count!=0) {
NSMutableDictionary* result = [@{} mutableCopy];
for (NSUInteger i = 0; i<count; i++) {
const char * propertyName = property_getName(properties[i]);
NSString* name = [NSString stringWithUTF8String:propertyName];
SEL getter = [self propertyGetterByKey:name];
if (getter) {
id value = objc_msgSend(self, getter);
if (value) {
result[name] = value;
}else{
result[name] = @"value 為 nil";
}
}
}
free(properties);
return result;
}
free(properties);
return nil;
}
#pragma mark - 生成setter
-(SEL)propertySetterByKey:(NSString*)key{
//key的首字母大寫
NSString* propertySetterName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
SEL setter = NSSelectorFromString(propertySetterName);
if ([self respondsToSelector:setter]) {
return setter;
}
return nil;
}
-(SEL)propertyGetterByKey:(NSString*)key{
SEL getter = NSSelectorFromString(key);
if ([self respondsToSelector:getter]) {
return getter;
}
return nil;
}
@end
消息動態(tài)解析(一)
#import <Foundation/Foundation.h>
@interface People : NSObject
@property(nonatomic,copy)NSString* name;
/** m文件不實現(xiàn)方法刮吧,通過runtime動態(tài)添加方法
* 通過resolveInstanceMethod:方法決定是否動態(tài)添加方法。
如果返回Yes則通過class_addMethod動態(tài)添加方法掖蛤,消息得到處理杀捻,結束;如果返回No
**/
-(void)sing;
@end
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
+(BOOL)resolveInstanceMethod:(SEL)sel{
if ([NSStringFromSelector(sel) isEqualToString:@"sing"]) {
class_addMethod([self class], sel, (IMP)otherIMP, "V@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void otherIMP(id self ,SEL cmd){
NSLog(@"%@正在唱歌",((People*)self).name);
}
@end
#import <Foundation/Foundation.h>
#import "People.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
People* p = [[People alloc]init];
p.name = @"張全蛋";
[p sing];
}
return 0;
}
打印結果:
2016-05-13 11:24:16.905 runtime 之 消息動態(tài)解析(一)[2819:311517] 張全蛋正在唱歌
Program ended with exit code: 0
消息動態(tài)解析(二)
修改Bird唱歌方法的調用對象
#import <Foundation/Foundation.h>
@interface Bird : NSObject
@property(nonatomic,copy)NSString* name;
-(void)sing;
@end
#import "Bird.h"
#import "People.h"
@implementation Bird
//第一步蚓庭,不動態(tài)添加方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
return NO;
}
//第二步致讥,不指定備選對象響應sselector
-(id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
//第三步,返回方法簽名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//第四部器赞,修改調用對象
-(void)forwardInvocation:(NSInvocation *)anInvocation{
People* p = [[People alloc]init];
p.name = @"張鐵柱";
[anInvocation invokeWithTarget:p];
}
#import <Foundation/Foundation.h>
#import "Bird.h"
#import <objc/runtime.h>
#import <objc/message.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Bird* b = [[Bird alloc]init];
b.name = @"小鳥";
[b sing];
}
return 0;
}
打印結果:
2016-05-13 11:29:58.335 runtime 之 消息動態(tài)解析(二)[2963:322829] 張鐵柱正在唱歌
Program ended with exit code: 0
消息動態(tài)解析(三)
修改Person唱歌方法的實現(xiàn)
#import <Foundation/Foundation.h>
@interface People : NSObject
-(void)sing;
@end
#import "People.h"
@implementation People
//不動態(tài)添加方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
return NO;
}
//不指定備選響應對象
-(id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
//返回方法簽名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//修改調用方法
-(void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation setSelector:@selector(dance)];
[anInvocation invokeWithTarget:self];
}
//若不實現(xiàn)forwardInvocation垢袱,則會調用此方法(可以注釋掉forwardInvocation方法來做實驗)
-(void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"消息無法處理:%@",NSStringFromSelector(aSelector));
}
-(void)dance{
NSLog(@"跳舞中");
}
@end
#import <Foundation/Foundation.h>
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
People* p = [[People alloc]init];
[p sing];
}
return 0;
}
打印結果:
2016-05-13 11:33:01.871 runtime 之 消息動態(tài)解析(三)[3073:329356] 跳舞中
Program ended with exit code: 0