Runtime小笔记(二)

前言

上篇主要讲Runtime的一些术语描述和定义,Runtime的主要应用是用于消息的传递,今天会结合一些实战例子来讲下OC的消息传递机制。

普通消息传递

在OC里,对象调用方法叫作发送消息,对象调用方法在Runtime里被转化为objc_msgSend函数来实现


[receiver oneMethod];
//transfer to :
objc_msgSend(receiver, @selector(oneMethod));

Runtime会根据类型自动转换为下列某个函数:

objc_msgSend:普通的消息都会通过该函数发送;
objc_msgSend_stret:消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值;
objc_msgSendSuper:和objc_msgSend类似,这里把消息发送给父类的实例;
objc_msgSendSuper_stret:和objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值;

objc_msgSend的调用过程

1. 先检查这个selector是否存在,不存在则忽略;
2. 接着检查selector的target是否为nil,向nil对象发送任何消息都会被忽略掉;
3. 前面两步没问题,则先在isa指针指向的class的cache里面找有没有方法调用记录,如果有,则运行对应的函数,如果没有则在class的methodLists查找方法,没有则通过super_class指针找到父类的类对象结构体,然后从methodLists查找方法,如果仍找不到,则继续通过
super_class向上一级父类结构体中查找,直到根类(NSObject);
4. 如果还是找不到,则进入消息动态解析

消息动态解析

动态解析流程图:

objective-runtime-6.png

具体解析:
1. 通过resolveInstanceMethod,该方法决定是否动态添加方法。如果返回YES,则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回NO,则进入下一步;
2. 这一步会步入forwardingTargetForSelector方法,用于指定备选对象响应这个selector,不能指定为self,如果放回某个对象则会调用对象的方法,结束。如果放回nil,则进入第三步;
3. 这一步,我们通过methodSignatureForSelector进行方法签名,如果返回nil,则消息无法处理。如果返回methodSignature则进入下一步;
4. 这步调用forwardInvocation方法,我们通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象,如果方法方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector,如果没有实现这个方法会crash

实战

通过runtime动态创建类和对象


#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 {
        //动态创建类
        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@:@"代表返回值+参数列表)
        SEL say = sel_registerName("say:");
        class_addMethod(MyPeople, say, (IMP)sayFunction, "v@:@");
        
        //注册类
        objc_registerClassPair(MyPeople);
        
        //创建实例对象
        id peopleInstance = [[MyPeople alloc]init];
        
        [peopleInstance setValue:@"李明" forKey:@"name"];
        
        //获取成员变量
        Ivar age = class_getInstanceVariable(MyPeople, "_age");
        object_setIvar(peopleInstance, age, @18);
        
        //发送消息
        objc_msgSend(peopleInstance,say,@"你好呀");
        
        //销毁实例对象
        peopleInstance = nil;
        
        //当类或子类的实例存在,则不能销毁类
        objc_disposeClassPair(MyPeople);
    }
    return 0;
}

tip:
默认会出现以下错误:
objc_msgSend()报错Too many arguments to function call ,expected 0,have3
直接通过objc_msgSend(self, setter, value)是报错,说参数过多。
请这样解决:
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];
        //获取参数列表
        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实现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

消息动态解析(一)

#import <Foundation/Foundation.h>

@interface People : NSObject

@property(nonatomic,copy)NSString* name;

/** m文件不实现方法,通过runtime动态添加方法
 *  通过resolveInstanceMethod:方法决定是否动态添加方法。
    如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回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 之 消息动态解析(一)[2819:311517] 张全蛋正在唱歌
Program ended with exit code: 0

消息动态解析(二)

修改Bird唱歌方法的调用对象


#import <Foundation/Foundation.h>

@interface Bird : NSObject
@property(nonatomic,copy)NSString* name;
-(void)sing;
@end
#import "Bird.h"
#import "People.h"
@implementation Bird


//第一步,不动态添加方法
+(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 之 消息动态解析(二)[2963:322829] 张铁柱正在唱歌
Program ended with exit code: 0

消息动态解析(三)

修改Person唱歌方法的实现

#import <Foundation/Foundation.h>

@interface People : NSObject
-(void)sing;
@end
#import "People.h"

@implementation People

//不动态添加方法
+(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];
}

//若不实现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 之 消息动态解析(三)[3073:329356] 跳舞中
Program ended with exit code: 0

以上demo地址

参考文章:
Objective-C Runtime 1小时入门教程
详解Runtime运行时机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 611评论 0 2
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,539评论 33 466
  • objc_getAssociatedObject返回与给定键的特定对象关联的值。ID objc_getAssoci...
    有一种再见叫青春阅读 1,558评论 0 7
  • Runtime是什么 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我...
    SuAdrenine阅读 866评论 0 3