Objective-C特性:Runtime
Method Swizzling 和 AOP 实践
引子:在OC中如何运用运行时编程
运行时系统通过三种形式在应用中出现:
Level-1:OC源码
OC编译器会捕获class和category定义、protocol声明中的以下信息
◈ method selectors
◈ instance variable templates
◈ ……
并转化成运行时结构,你需要知道编写的OC代码会变成什么;其中最常见的运行时函数就是发送消息
Level-2:NSObject Methods - 动态类型
有一些NSObject方法通过对RTS执行introspection来查询信息,相信大家都用过这些属性/函数:
▣ 类型识别:-class和-superclass
▣ 测试对象的继承、行为和规范
◈ - isKindOfClass:
◈ - isMemberOfClass:
◈ - respondsToSelector:
◈ - conformsToProtocol:
▣ 获取方法的信息
◈ - methodForSelector: // 包含实例方法和类方法
◈ + instanceMethodForSelector: // 仅限于实例方法
◈ - methodSignatureForSelector: // 包含实例方法和类方法
◈ + instanceMethodSignatureForSelector: // 仅限于实例方法
Level-3:Runtime Functions
所有的函数可以在Objective-C Runtime Reference找到,这些函数大概可以分为两类:
◈ 通过C语言来做一些类似编译器的工作,比如动态生成类、动态为一个已有类添加一个新属性等
◈ 另一部分函数构成了NSObject函数的基础
注意:在这个level使用运行时,需要引入头文件:#import <objc/runtime.h>
1 动态创建协议、类、对象
#动态创建类
// 步骤一:给类分配内存
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
-----------------------------------------------------------------------------------
// 步骤二:配置类的成员变量、属性、方法和遵守的协议
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
SEL sel_registerName ( const char *str ); // 也可使用更方便的语法糖 @selector
IMP imp_implementationWithBlock(id block);
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
BOOL class_addProtocol(Class cls, Protocol *protocol) ; // 如果实现了协议的required方法,可以此声明class遵守了协议
-----------------------------------------------------------------------------------
// 步骤三:将类注册到应用中
void objc_registerClassPair ( Class cls );
// 步骤四:销毁类及其子类 => 确保调用前没有类/子类的实例!!!
objc_disposeClassPair ( Class cls );
#动态创建对象 - 最佳实例:Json转Model
// 步骤一:给类(结构体的实例)分配内存 - 类似于+alloc
id class_createInstance ( Class cls, size_t extraBytes );
id objc_constructInstance ( Class cls, void *bytes ); // 在指定位置创建类实例
id object_copy ( id obj, size_t size );
// 步骤二:设置对象的类型
Class object_setClass ( id obj, Class cls );
// 步骤三:设置对象的成员变量(需根据类型(OC对象/其他)用不同的方法)
Ivar class_getInstanceVariable ( Class cls, const char *name ); // 实例成员变量
Ivar class_getClassVariable ( Class cls, const char *name ); // 类变量
void object_setIvar ( id obj, Ivar ivar, id value ); // 也可使用KVC
或
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 步骤四:销毁类实例 - 注意:不会移除相关引用
void * objc_destructInstance ( id obj );
id object_dispose ( id obj );
#动态创建协议
// 步骤一:创建新的协议(如果同名的协议已经存在,则返回nil)
Protocol * objc_allocateProtocol ( const char *name );
// 步骤二:操作协议的属性、方法
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
// 步骤三:注册协议(注意:不同于类,协议注册到运行时系统后不可再修改 and ready to use. )
void objc_registerProtocol ( Protocol *proto );
// 步骤四:协议关联到类
BOOL class_addProtocol ( Class cls, Protocol *protocol );
2 遍历属性/成员变量
#遍历成员变量
// 步骤一:获取成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 步骤二:获取成员变量的名字和类型编码
const char * ivar_getName ( Ivar v );
const char * ivar_getTypeEncoding ( Ivar v ); // 比如"@\"NSString\""
#遍历属性
// 步骤一:获取属性列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 步骤二:获取属性的名字和特性信息
const char * property_getName ( objc_property_t property ); // 返回属性名(区别于成员变量名)
const char * property_getAttributes ( objc_property_t property );
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );
3 方法变形(Method Swizzling)
通过这一技术,我们可以在运行时通过修改类的分发表中SEL和IMP的对应关系,来修改方法的实现。
举例:跟踪在程序中每一个view controller展示给用户的次数,其中的5个关键点有标注
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
// 1. Swizzling应该总是在+load的dispatch_once中执行
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class]; // 类方法:Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 2. 要做什么?- 修改映射关系为:originalSelector->IMP(swizzledMethod), swizzledSelector->IMP(originalMethod)
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
// 3. 避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。
- (void)xxx_viewWillAppear:(BOOL)animated {
// 4. 总是调用方法的原始实现(除非有更好的理由不这么做):这意味着通常我们只是给原来的实现附加一些代码
[self xxx_viewWillAppear:animated];
// 5. 输出viewWillAppear:而不是xxx_viewWillAppear:,说明对象是通过viewWillAppear:进入到实现的
NSLog(@"real SEL name is %@", NSStringFromSelector(_cmd));
}
@end
知识扩展:
Aspect-Oriented Programming(AOP)
类似记录日志、身份验证、缓存等事务非常琐碎,与业务逻辑无关,很多地方都有,又很难抽象出一个模块,这种程序设计问题,业界给它们起了一个名字叫横向关注点(Cross-cutting concern),AOP的作用就是分离横向关注点来提高模块复用性,它可以在既有的代码添加一些额外的行为(记录日志、身份验证、缓存)而无需修改代码。
开源工具
Aspects
危险性
Method Swizzling就像一把瑞士小刀,如果使用得当,它会有效地解决问题。但使用不当,将带来很多麻烦。它的危险性主要体现以下几个方面:
◈ Method swizzling is not atomic
◈ Changes behavior of un-owned code
◈ Possible naming conflicts
◈ Swizzling changes the method’s arguments
◈ The order of swizzles matters
◈ Difficult to understand (looks recursive)
◈ Difficult to debug
参考
从AOP框架学习iOS Runtime
![]()
![]()
4 关联对象
@interface NSObject (CategoryName)
@property (nonatomic,copy) NSString *name;
@end
----------------------------------------------------
@implementation NSObject (CategoryName)
static void *nameKey = "nameKey"; // 设置为静态私有变量,避免外界修改
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, &nameKey);
}
@end
网友评论