Runtime实战
Runtime简称运行时,就是iOS系统在运行的时候的一种机制,其中最主要的是消息机制,是一套底层的纯C语言的API。
实际上,平时我们编写的OC代码,底层都是基于Runtime实现,最终转成了底层的Runtime代码(C语言代码)。
这篇文章主要写我学习Runtime其中一种使用,给Category动态添加实例变量。
更深入了解其原理,探究底层实现,建议看看雷纯锋的Objective-C Associated Objects 的实现原理。
使用函数
先import"objc/runtime.h"
得头文件,主要用到了以下两个方法:
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
其作用主要:
-
objc_setAssociatedObject
用于给对象添加关联对象,传入 nil 则可以移除已有的关联对象; -
objc_getAssociatedObject
用于获取关联对象;
key
在上面的雷纯锋写的博文中,他提议用selector
的getter方法名作key值。
但我感觉用得不习惯,更偏向使用静态static char kAssociatedObjectKey;
,取其地址& kAssociatedObjectKey
为key。AFNetworking
和SDWebImage
都是这样个用法。
这样的好处是:
- 占用空间小,只有一个字节。
- 静态变量,地址不会改变,使用地址作为key总是唯一的且不变的。
- 避免和其他框架定义的key重复,或者其他key将其覆盖的情况。
- 比如在其他文件(仍然是UIView的分类)中定义了同名同值的key,使用
objc_setAssociatedObject
进行设置绑定的属性的时候,可能会将在别的文件中设置的属性值覆盖。
借鉴
"UIImageView+WebCache.h"
分类中,为UIImageView添加等待指示,我把相关代码部分抽出来,也可以下载我的Runtime Demo比较直观的看下。使用Runtime添加几个成员变量。如下:
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
- (void)setShowActivityIndicatorView:(BOOL)show{
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)showActivityIndicatorView{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
在.h中暴露的几个分类方法,以满足调用。
- (void)addActivityIndicator;
- (void)removeActivityIndicator;
- (void)setShowActivityIndicatorView:(BOOL)show;
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
创建一个UIImageView并调用分类,实现效果。
imageview_indicator.png牛刀小试
由以上的学习中,我们实战一下加深印象。
为UIButton添加分类,目的是:实现我们注册登录中常用的发送验证码的倒计时按钮。
runtime添加成员变量
我们需要设置一个倒计时的时间属性。写其Setter
和Getter
方法。
就是运用objc_getAssociatedObject
、objc_setAssociatedObject
- (int)getTimeOut {
NSNumber *number = objc_getAssociatedObject(self, &TAG_TimeOut);
if (number) {
return [number intValue];
}
return 60;
}
- (void)setTimeOut:(int)count {
objc_setAssociatedObject(self, &TAG_TimeOut, [NSNumber numberWithInt:count], OBJC_ASSOCIATION_RETAIN);
}
倒计时计算
使用 Dispatch Source Timer 设置间隔来做定时器,以1s作递减。并返回时间的变化。
- (void)vertificationCode:(void(^)())blockYes blockNo:(void(^)(int time))blockNo
{
__block int timeout = [self getTimeOut];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
if(timeout <= 0) {
dispatch_source_cancel(_timer);
dispatch_main_async_safe(^{
blockYes();
})
} else {
dispatch_main_async_safe(^{
blockNo(timeout);
});
timeout--;
}
});
dispatch_resume(_timer);
}
启动
最后写个启动的方法,以开始执行调用计时器。
- (void)startToCount {
self.enabled = NO;
[self vertificationCode:^{
self.enabled = YES;
[self setTitle:@"重新发送" forState:UIControlStateNormal];
} blockNo:^(int time) {
NSString *strTime = [NSString stringWithFormat:@"%.2d秒", time];
[self setTitle:strTime forState:UIControlStateNormal];
}];
}
效果如下:
runtime.gif总结
首先,使用Runtime的好处是相当明显的----解耦、代码入侵性低、为VC(UIView)
瘦身。
像上述举的两个例子,需要在VC中实现时,写在其中也可以,只是感觉这样会是VC越来越臃肿,代码复用率也不高。
完整Demo地址:请戳这里-->Runtime-Demo
欢迎大家给我点星星✨诶。