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