oc的运行期环境(runtime)

oc语言的动态特性

oc语言的动态特性来自ObjC Runtime ,其实是一个 runtime 库,基本上用 C 和汇编写的,这个库作用就是加载类的信息,进行方法的分发和转发之类的。具体为对象接受消息(oc是消息型语言)之后,调用何种方法直到运行期才能决定,编译器看到此消息后将其转为标准的C语言函数调用,函数objc_msgSend为消息传递的核心函数; objc_msgSend根据消息选择子的调用适当的方法,在对象所属类中搜寻方法列表,找到之后跳转到实现代码,若找不到会沿着继承体系继续往上找,找到之后再跳转;若始终找不到(无法响应选择子),则启动消息转发机制,包括动态方法解析和消息转发,若经过消息转发流程之后,还是无法处理选择子,则会抛出异常

理解oc中的一些定义和消息发送
//oc中的方法定义
typedef struct objc_selector  *SEL;
SEL aSel = @selector(goHome);

//oc中的类定义
typedef struct objc_class   *Class;
typedef struct objc_object  {
    Class isa; //isa 指向所属的类
} *id;  //id 为指向对象的指针

struct objc_class {
    Class isa; // 指向metaclass
    Class super_class ; // 指向父类
    const char *name ; // 类名
    long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
    long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含实例方法和变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
    long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
    struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
    struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;
    struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
    struct objc_protocol_list *protocols; // 存储该类声明遵守的协议
}

typedef struct objc_method *Methodstruct objc_method{ 
    SEL method_name OBJC2_UNAVAILABLE; // 方法名 char 
    *method_types OBJC2_UNAVAILABLE;
    IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}

//IMP (Method Implementations) ,IMP 是一个函数指针,这是由编译器生成的,当你发起一个 ObjC 消息之后,最终它会执行的那个代码,就是由这个函数指针指定的
typedef id (*IMP)(id self,SEL _cmd,...); 

//传递消息(编译期其实是在传递消息,并不代表一定被执行)
[target getMovieTitleForObject:obj]; 

//运行期进行消息的分发和转发
 id returnValue = [object messageName:params];
 id returnValue = objc_msgSend(object,@selector(messageName:), params); 

原型为: void  objc_msgSend(id self, SEL cmd, ...)
消息转发机制(可以用来模拟多重继承)

1.动态方法解析:
在对象收到无法解读消息后,首先将调用其所属类的下列类方法:(消息中的选择子为对象方法)

// 返回值表示能否新增一个实例方法处理此选择子
+ (BOOL)resolveInstanceMethod:(SEL)sel;

若选择子为类方法则是 + (BOOL)resolveClassMethod:(SEL)sel
2.备援接受者; 若上述返回为NO, 则继续调用下列方法:

// 如果上述的方法返回为NO则继续执行下列方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
    //能否把这条消息转发为其他接受者处理,是的话将其返回,不是返回nil
    return nil;
}

3.若返回nil则启用完整的消息转发:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    //创建NSInvocation对象,将消息细节完整封装在其中
    [super forwardInvocation:anInvocation];
}
runtime应用举例:

1.以动态方法解析来实现@dynamic属性,类会自动处理相关属性值得存放与获取操作.细节在 XZSModel 中:

.h 文件
#import 
@interface XZSModel : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * area;
@property (nonatomic, copy) NSString * age;
@end
.m 文件
#import "XZSModel.h"
#import 

@interface XZSModel ()
@property (nonatomic, strong) NSMutableDictionary *storeDict;
@end

@implementation XZSModel

@dynamic name,area,age;

- (instancetype)init {
    if (self = [super init]) {
        _storeDict = [[NSMutableDictionary alloc]init];
    }
    return self;
}

//动态方法解析  在对象收到无法解读消息后,首先将调用其所属类的下列类方法:(消息中的选择子为对象方法)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString hasPrefix:@"set"]) {
        //set 方法
        class_addMethod(self, sel, (IMP)xzsSetter, "v@:@");
    }else {
        class_addMethod(self, sel, (IMP)xzsGetter, "@@:");
    }
    //其中 “v@:@” 表示返回值和参数,这个符号涉及 Type Encoding,可以参考Apple的文档
    return YES;
}
//set方法
void xzsSetter(id self, SEL _cmd, id value) {
    XZSModel *typeSelf = (XZSModel *)self;
    NSMutableDictionary *storeDict = typeSelf.storeDict;
    
   
    //方法例如: "setName:",因此移除"set"和":",并将处理后的字符的第一个字符改为小写如:"N -> n" 
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [selectorString mutableCopy];
    
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    
   //第一个字符改为小写
    NSString *lowerFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowerFirstChar];
    NSLog(@"处理后的key: %@",key);
    
    if (value) {
        [storeDict setValue:value forKey:key];
    }else{
        [storeDict removeObjectForKey:key];
    }
}

// get方法
id xzsGetter(id self, SEL  _cmd){
    XZSModel *typeSelf = (XZSModel *)self;
    NSMutableDictionary *storeDict = typeSelf.storeDict;
    
    NSString *key = NSStringFromSelector(_cmd);
    return [storeDict objectForKey:key];
}

@end

2.利用runtime向类中新增或替换选择子所对应方法的实现

 NSString+XZSAdditions.m 文件
#import "NSString+XZSAdditions.h"

@implementation NSString (XZSAdditions)
 //此方案也称:方法调配(method swizzling)
- (NSString *)xzs_lowercaseSring {
    NSString *lowercase = [self xzs_lowercaseSring];
    NSLog(@"%@ ---> %@",self,lowercase);
    return lowercase;
}

@end
测试验证
    Method originMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
    Method newMethod    = class_getInstanceMethod([NSString class], @selector(xzs_lowercaseSring));
    method_exchangeImplementations(originMethod, newMethod);
    
    NSString *string = @"hEllO worLD!";
    NSString *lowercaseString = [string lowercaseString];

控制台打印结果:
hEllO worLD! ---> hello world!

3.利用runtime获取对象所有属性的方法,给模型自动赋值
获取对象的所有成员变量: class_copyIvarList
获取对象的所有属性: class_copyPropertyList

分类 .m 文件
#import "NSObject + model.h"
#import 

#define BY_IVAR 0

@implementation NSObject (model)

+ (instancetype)modelWithDic:(NSDictionary *)dic {
    
    //获取属性列表
    id obejctSelf = [[self alloc]init];
    unsigned int count;
    
#if BY_IVAR
    Ivar * ivarList = class_copyIvarList(self, &count);
#else
    objc_property_t *properties = class_copyPropertyList(self, &count);
#endif
    
    for (int i = 0; i < count; i++) {
#if BY_IVAR
        Ivar ivar = ivarList[i];
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        NSString *key   = [name substringFromIndex:1];
#else
        objc_property_t property = properties[i];
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        NSString *type = [NSString stringWithUTF8String:property_getAttributes(property)];
        NSString *key   = name;
#endif
        
        if ([dic isKindOfClass:[NSNull class]] || dic == nil) {
            continue;
        }
        id value = dic[key];
        
        if ([value isKindOfClass:[NSDictionary class]]) {
            NSRange range = [type rangeOfString:@"\""];
            type = [type substringFromIndex:range.location + range.length];
            range = [type rangeOfString:@"\""];
            type = [type substringToIndex:range.location];
            Class typeClass = NSClassFromString(type);
            if (typeClass) {
                [typeClass modelWithDic:value];
            }
        }
        if (value) {
            [obejctSelf setValue:value forKey:key];
        }
    }
  
    return obejctSelf;
}

@end

4.利用runtime关联对象给分类添加属性

//设置关联对象值 (object:与谁关联,通常是传self;   key:唯一键,在获取值时通过该键获取,通常是使用static,const void *来声明);  value:关联所设置的值;  policy:内存管理策略
void objc_setAssociatedObject(id object, const void *key, id value, objc _AssociationPolicy policy)
//获取关联对象值
id objc_getAssociatedObject(id object, const void *key)
//移除指定对象的全部关联对象
void objc_removeAssociatedObjects(id object)
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){
OBJC_ASSOCIATION_ASSIGN = 0, // 表示弱引用关联,通常是基本数据类型OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 表示强引用关联对象,是线程安全的
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 表示关联对象copy,是线程安全的
OBJC_ASSOCIATION_RETAIN = 01401, // 表示强引用关联对象,不是线程安全的
OBJC_ASSOCIATION_COPY = 01403 // 表示关联对象copy,不是线程安全的
};
//分类添加属性
#import "UIView + changeColor.h"
#import 

static char changeColorChar;

@implementation UIView (changeColor)
@dynamic changeColor;

- (void)setChangeColor:(UIColor *)changeColor {
    objc_setAssociatedObject(self, &changeColorChar, changeColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIColor *)changeColor {
    UIColor *color = objc_getAssociatedObject(self, &changeColorChar);
    return color;
}

@end

5.模拟多重继承
待续!

参考资料
1.http://www.jianshu.com/p/970ae3bac1ef
2.http://justinyan.me/post/1624
3.52个有效方法
4.Objective-C Runtime Programming Guide

你可能感兴趣的:(oc的运行期环境(runtime))