美文网首页
Runtime实战

Runtime实战

作者: 叫我李五 | 来源:发表于2017-06-08 22:37 被阅读37次

    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。AFNetworkingSDWebImage都是这样个用法。

    这样的好处是:

    1. 占用空间小,只有一个字节。
    2. 静态变量,地址不会改变,使用地址作为key总是唯一的且不变的。
    3. 避免和其他框架定义的key重复,或者其他key将其覆盖的情况。
    4. 比如在其他文件(仍然是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添加成员变量

    我们需要设置一个倒计时的时间属性。写其SetterGetter方法。

    就是运用objc_getAssociatedObjectobjc_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

    欢迎大家给我点星星✨诶。

    相关文章

      网友评论

          本文标题:Runtime实战

          本文链接:https://www.haomeiwen.com/subject/sutxqxtx.html