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. 如果还是找不到,则进入消息动态解析

消息动态解析

动态解析流程图:

Runtime小笔记(二)_第1张图片
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 
#import 
#import 
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 

@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 
#import 

@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
#import 
#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 
#import 


@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 

@interface People : NSObject{
    
    NSString* _nationality;

}

@property(nonatomic,copy)NSString* name;
@property(nonatomic,strong)NSNumber* age;
@end
#import "People.h"
#import 
#import 

@implementation People

//归档
-(void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int count = 0;
    Ivar* ivars = class_copyIvarList([self class],&count);
    for (NSUInteger i = 0; i

利用Runtime实现Model与字典互转

#import 

@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 
#import 

@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

消息动态解析(一)

#import 

@interface People : NSObject

@property(nonatomic,copy)NSString* name;

/** m文件不实现方法,通过runtime动态添加方法
 *  通过resolveInstanceMethod:方法决定是否动态添加方法。
    如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回No
 **/
-(void)sing;

@end

#import "People.h"
#import 
#import 


@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 
#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 

@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 
#import "Bird.h"
#import 
#import 
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 

@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 
#import "People.h"
#import 
#import 

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运行时机制

你可能感兴趣的:(Runtime小笔记(二))