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