Runtime
Objective-C运行时是一个运行时库,它提供对Objective-C语言的动态属性的支持,它会尽可能地将许多决定从编译期推迟到运行时。这意味着Objective-C不仅需要编译器,还需要运行时系统来执行编译代码。
Runtime是由C和汇编写成,它实现了Objective-C到C的转化,即面向对象到面向过程的转化。
运行时应用
- 发送消息
- 动态方法解析
- 消息转发
- KVO
参考Demo
- 属性关联
由于某些原因,有时我们不能直接向一个类中添加属性,例如第三方SDK,但是可以通过像其类别(Category)中添加属性关联来达到目的。通常使用的方法为:
1
2
3
4
5
6
7
8
9
10
11
12
// 设置关联
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
// 获取关联对象
id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
// 删除关联
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object);
- 方法交换
通常采用AOP方式,来hook原来的方法,达到某种效果。例如在不侵入原工程代码的情况下集成友盟统计:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(analysis_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 添加方法
BOOL didAddMethod = class_addMethod(class, swizzledMethod, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// 判断是否已存在该方法
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
// 直接交换方法的实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void) analysis_viewWillAppear:(BOOL)animated {
// 调用该方法自身,实际上是调用交换前的方法
[self analysis_viewWillAppear:animated];
// TODO analysis method
// ...
}
- 声明属性
使用的Property属性描述符:
1
typedef struct objc_property * Property;
使用class_copyPropertyList
或protocol_copyPropertyList
获取一个和协议中属性列表,函数原型:
1
2
objc_property_t * class_copyPropertyList(Class cls,unsigned int * outCount)
objc_property_t * protocol_copyPropertyList(Protocol * proto,unsigned int * outCount)
获取属性列表示例:
1
2
3
id LenderClass = objc_getClass(“Lender”);
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList(LenderClass,&outCount);
使用property_getName
获取属性名称:
1
2
const char * property_getName(objc_property_t property)
使用class_getProperty
并protocol_getProperty
分别获取类和协议中给定名称的属性:
1
2
objc_property_t class_getProperty(Class cls,const char * name)
objc_property_t protocol_getProperty(Protocol * proto,const char * name,BOOL isRequiredProperty,BOOL isInstanceProperty)
使用property_getAttributes
获取属性的类型字符串。它返回objc_property_attribute_t结构体列表,包含了name和value。
参考官方文档。
查看Lender类的属性列表:
1
2
3
4
5
6
7
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
- 归档解档
归档:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (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];
// 获取实例变量的名字,C字符串
const char *name = ivar_getName(ivar);
// 将C字符串转化为NSString类型
NSString *nameStr = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
// 利用KVC取出属性对应的值
id value = [self valueForKey:nameStr];
// 实现归档
[aCoder encodeObject:value forKey:nameStr];
}
// 释放,不要忘记他们是Copy来的
free(ivars);
}
解档:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (id)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 stringWithCString:name encoding:NSUTF8StringEncoding];
id value = [aDecoder decodeObjectForKey:key];
// 通过KVC来设置值
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
RunLoop
RunLoop即运行循环,用来对程序运行期间的的输入源进行调度处理。基于RunLoop可以用来监控卡顿以及节省CPU资源,提升程序性能。通过RunLoop的调度,使得线程在无事件处理时进入休眠,有时间处理事保持活跃状态。主线程默认携带一个mainRunLoop,由UIApplicationMain函数开启,在处理大量占用CPU的任务时,可以将其放到空闲的RunLoop模式中执行,以免阻塞。
RunLoop在iOS中由CFRunLoop实现RunLoop对象。Foundation中的NSRunLoop,实际上是基于CoreFoundation中的CFRunLoopRef的封装。
RunLoop模式:
- NSDefaultRunLoopMode:默认Mode,空闲状态;
- NSRunLoopCommonModes:Mode集合;
- UITrackingRunLoopMode:ScrollView滑动时的模式,此时基于NSTimer的定时器不执行;
- UIInitializationRunLoopMode:启动时进入的第一个Mode,之后会切换到默认Mode;
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
可以使用[NSRunLoop currentRunLoop]
来获取当前的RunLoop,scrollView滑动式,基于NSTimer的定时器不执行可以将当前RunLoop模式切换到NSRunLoopCommonModes来解决。如下:
1
2
3
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
相对应的获取方法还有:
1
2
3
4
NSRunLoop *mainLoop = [NSRunLoop mainRunLoop];
// CoreFoundation
CFRunLoopRef runLoopRef = CFRunLoopGetCurrent();
CFRunLoopRef mainLoopRef = CFRunLoopGetMain();
CFRunLoopSource
- source0:触摸事件,如UIEvent,CFSocket这样的事件。
- source1:基于Port的线程间通信,Mach port驱动,CFMachport,CFMessagePort等。
CFRunLoopObserver
在RunLoop运行过程中,有六种状态:
1
2
3
4
5
6
7
8
9
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 进入 loop
kCFRunLoopBeforeTimers , // 触发 Timer 回调
kCFRunLoopBeforeSources , // 触发 Source0 回调
kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
kCFRunLoopExit , // loop退出
kCFRunLoopAllActivities // loop 所有状态
}
流程:
- 通知Observers: 进入Runloop。
- 开启do while来保活线程,通知Observers:RunLoop将要处理Timer回调,Source0回调,执行添加的相应Block。
- 然后如果有Source1需要处理,则处理Source1事件。
- 回调执行后,通知Observers:RunLoop的线程将要进入休眠。
- 进入休眠后等待mach_port消息,如果有新消息,则RunLoop再次唤醒。
- RunLoop线程被唤醒,然后开始处理消息(Timer,dispatch,source1)等事件,及block
- 根据当前RunLoop的状态来判断是否需要走下一个Loop,当外部强制终止(用户退出,线程终止等)或超时,退出loop。
应用
- AFNetworking 2.x 中的常驻线程
- 卡顿检测及处理
- 程序崩溃检测
- tableView图片延迟加载