目录
1.Runtime常用API
2.Runtime的应用
3.Runtime相关面试题
1.Runtime常用API
类相关:
//动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(<#Class _Nullable __unsafe_unretained superclass#>, <#const char * _Nonnull name#>, <#size_t extraBytes#>)
例:Class myClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
//注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
//销毁一个类
void objc_disposeClassPair(Class cls)
//获取isa指向的Class
Class object_getClass(id obj)
//设置isa指向的Class
Class object_setClass(id obj, Class cls)
//判断一个OC对象是否为Class
BOOL object_isClass(id obj)
//判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
//获取 父类
Class class_getSuperclass(Class cls)
成员变量相关:
//获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
//拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
//动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
//获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
属性相关:
//获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
//拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
//动态添加属性
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)
//获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
方法相关:
//获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
//方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
//拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
//选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
//用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
2.Runtime的应用
1.给分类扩展属性
在某些场景系统类提供的属性并不能满足我们的业务需求,所以我们可以通过Runtime来实现给系统类添加属性,比如下面给UIButton添加一个字典属性:
//UIButton+Category.h
#import <UIKit/UIKit.h>
@interface UIButton (Category)
@property(nonatomic,strong)NSDictionary * dict;
@end
//UIButton+Category.m
#import "UIButton+Category.h"
#import <objc/runtime.h>
@implementation UIButton (Category)
-(void)setDict:(NSDictionary *)dict{
objc_setAssociatedObject(self, @selector(dict), dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSDictionary*)dict{
return objc_getAssociatedObject(self, _cmd);
}
@end
//使用
#import "UIButton+Category.h"
- (void)viewDidLoad {
[super viewDidLoad];
UIButton * button = [UIButton buttonWithType: UIButtonTypeCustom];
button.dict = @{@"key":@"value"};
NSLog(@"%@",button.dict[@"key"]);
}
这里使用了runtime的关联对象,具体原理可看这篇文章最后一节Category底层原理分析
2.遍历类的所有成员变量(字典转模型、自动归档解档)
通过runtime API获取类的变量名及KVC实现一个简单的字典转模型功能
//NSObject+Model.h
#import <Foundation/Foundation.h>
@interface NSObject (Model)
+ (instancetype)ModelWithDict:(NSDictionary *)dict;
- (void)transformDict:(NSDictionary *)dict;
@end
//NSObject+Model.m
#import "NSObject+Model.h"
#import <objc/runtime.h>
@implementation NSObject (Model)
+ (instancetype)ModelWithDict:(NSDictionary *)dict {
NSObject * obj = [[self alloc]init];
[obj transformDict:dict];
return obj;
}
- (void)transformDict:(NSDictionary *)dict {
Class cla = self.class;
// count:成员变量个数
unsigned int outCount = 0;
// 获取成员变量数组
Ivar *ivars = class_copyIvarList(cla, &outCount);
// 遍历所有成员变量
for (int i = 0; i < outCount; i++) {
// 获取成员变量
Ivar ivar = ivars[i];
// 获取成员变量名字
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 成员变量名转为属性名(去掉下划线 _ )
key = [key substringFromIndex:1];
// 取出字典的值
id value = dict[key];
// 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
if (value == nil) continue;
// 利用KVC将字典中的值设置到模型上
[self setValue:value forKeyPath:key];
}
//需要释放指针,因为ARC不适用C函数
free(ivars);
}
@end
//调用:
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *dict = @{
@"name" : @"lanlin",
@"age" : @18,
@"sex" : @"男"
};
Student * student = [Student ModelWithDict:dict];
NSLog(@"%@,%d,%@",student.name,student.age,student.sex);
//打印结果:lanlin,18,男
}
同样使用runtime的API也能应用于对象的归解档:
// 解档使用
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int 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];
if(value){
[self setValue:value forKey:key];
}
}
free(ivars);
}
return self;
}
// 归档使用
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int 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];
if(value){
[aCoder encodeObject:value forKey:key];
}
}
free(ivars);
}
4.方法交换
关于方法交换详细内容可参考runtime原理与实践: 方法交换篇(Method Swizzling),这里引用了这篇文章中的例子做了分析.
统计VC加载次数并打印
UIViewController+Logging.m
#import "UIViewController+Logging.h"
#import <objc/runtime.h>
@implementation UIViewController (Logging)
// 使用时机:在+load方法进行
+ (void)load
{
swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}
- (void)swizzled_viewDidAppear:(BOOL)animated
{
//注意:这里表面看是死循环,实际上方法已经被交换了
//调用swizzled_viewDidAppear 实际上是调用了系统的 viewDidAppear
[self swizzled_viewDidAppear:animated];
// Logging
NSLog(@"%@", NSStringFromClass([self class]));
}
//交换方法
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// 获取原来的方法和要交换的方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 判断方法是否被实现
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);
}
}
5.KVO的实现
可以参考之前写的文章,包含系统如何实现KVO原理与自己利用runtime相关API实现一个KVO
探究 OC对象、isa指针及KVO实现原理
6.利用消息转发机制解决方法找不到的异常问题
可参考上篇文章,有详细的分析,实现消息转发的相关方法即可。 Runtime总结(二)
3.Runtime相关面试题
- 网上看了许多题,主要是围绕runtime的isa结构,类的结构,源码,消息转发,应用场景几个方面,下面举几个常见例子和个人感觉比较好的一篇面试题分享。
1.讲一下runtime的消息机制
2.消息转发机制流程是怎么样的
3.什么是runtime?项目中有用过吗?