美文网首页将来跳槽用iOS面试
iOS你在项目中用过 runtime 吗?举个例子

iOS你在项目中用过 runtime 吗?举个例子

作者: SunnyLeong | 来源:发表于2018-02-10 14:29 被阅读1649次

    Objective-C 语言是一门动态语言,编译器不需要关心接受消息的对象是何种类型,接收消息的对象问题也要在运行时处理。

    pragramming 层面的 runtime 主要体现在以下几个方面:

    补充一个大家经常用的、但是很容易忽视的 runtime 应用吧:

    动态获取 class 和 slector

    NSClassFromString(@"MyClass");
    NSSelectorFromString(@"showShareActionSheet");
    
    

    给分类添加属性KVC/KVO了。

    大多数情况下,我们是不会直接用 runtime 写业务代码的,所以,大部分时候,我们只会在一些平时用起来很方便的框架里面,看到有用到 runtime 的黑魔法(当然你也可以自己造轮子)。runtime 用起来的最大感受就是,底层写一堆 runtime,外面用起来超清爽!对于那些“代码艺术家”来说,简直是爽到爆。

    我在项目中使用runtime比较少:

    1.在category中添加属性
    2.修改系统方法,如重写objectAtIndex方法阻止数组越界崩溃。

    大概说说一下自己在开发中用到的一些基于 runtime 的开源框架吧,具体应用自己可以去看看源码:

    1.Aspects(AOP必备,“取缔” baseVC,无侵入埋点)
    2.MJExtension(JSON 转 model,一行代码实现 NSCoding 协议的自动归档和解档)
    3.JSPatch(动态下发 JS 进行热修复)
    4.NullSafe(防止因发 unrecognised messages 给 NSNull 导致的崩溃)
    5.UITableView-FDTemplateLayoutCell(自动计算并缓存 table view 的 cell 高度)
    6.UINavigationController+FDFullscreenPopGesture(全屏滑动返回)


    runtime 的原理和用法可以看看官方文档

    1. Objective-C Runtime Programming Guide Interacting with the Runtime

    2. Objective-C Runtime Reference

    3. About Key-Value Coding

    4. Introduction to Key-Value Observing Programming Guide


    20180212 更新


    在引导页上使用了runtime

    .h代码如下:

    #import <UIKit/UIKit.h>
    
    @interface UIViewController (KSGuid)<UICollectionViewDataSource,UICollectionViewDelegate,UIScrollViewDelegate>
    
    //*实现引导页的控制器,外部不用调用即可实现GuidView,可以修改下面的图片*/
    
    @end
    
    /*这里是要展示的图片,修改即可,当然不止三个  1242 * 2208的分辨率最佳,如果在小屏手机上显示不全,最好要求UI重新设计图片*/
    
    #define ImageArray @[@"guid01",@"guid02",@"guid03",@"guid04"]
    
    /** pageIndicatorTintColor*/
    #define pageTintColor [[UIColor whiteColor] colorWithAlphaComponent:0.5];
    /** currentPageIndicatorTintColor*/
    #define currentTintColor [UIColor whiteColor];
    
    
    /*
    如果要修改立即体验按钮的样式
    重新- (UIButton*)removeBtn方法即可
    */
    
    

    .m代码如下

    #import "UIViewController+KSGuid.h"
    #import <objc/runtime.h>
    
    #define CollectionView_Tag 15
    #define RemoveBtn_tag 16
    #define Control_tag 17
    
    #define FIRST_IN_KEY @"FIRST_IN_KEY"
    
    @interface KSGuidViewCell : UICollectionViewCell
    
    @property (nonatomic, copy) NSString* imageName;
    @property (nonatomic, strong) UIImageView* imageView;
    @end
    @implementation KSGuidViewCell
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            _imageView = [[UIImageView alloc] initWithFrame:self.bounds];
            _imageView.contentMode = UIViewContentModeScaleAspectFill;
            _imageView.userInteractionEnabled = YES;
            [self.contentView addSubview:_imageView];
        }
        return self;
    }
    - (void)setImageName:(NSString *)imageName{
        if (_imageName != imageName) {
            _imageName = [imageName copy];
        }
        _imageView.image = [UIImage imageNamed:imageName];
    }
    @end
    
    /************************以上是KSGuidViewCell,以下才是UIViewController+KSGuid******************************/
    
    @implementation UIViewController (KSGuid)
    
    #pragma mark- 
    #pragma mark 这里是退出的按钮
    - (UIButton*)removeBtn{
        //移除按钮样式
        UIButton* removeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [removeBtn addTarget:self action:@selector(removeGuidView) forControlEvents:UIControlEventTouchUpInside];
        removeBtn.hidden = (self.imageArray.count != 1);
        removeBtn.tag = RemoveBtn_tag;      //注意这里的tag
        
        //***********************这里面可以自定义*******************************//
        CGFloat btnW = 128;
        CGFloat btnH = 35;
        CGFloat btnX = CGRectGetMidX(self.view.frame) - btnW / 2;
        CGFloat btnY = CGRectGetMaxY(self.view.frame) * 0.83;
        removeBtn.frame = CGRectMake(btnX, btnY, btnW, btnH);
        
        removeBtn.layer.cornerRadius = 4;
        removeBtn.layer.borderColor = [UIColor whiteColor].CGColor;
        removeBtn.layer.borderWidth = 1.;
        
        [removeBtn setTitle:@"进入婚匠" forState:UIControlStateNormal];
        [removeBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        removeBtn.titleLabel.font = [UIFont systemFontOfSize:18.];
         //********************自定义结束**********************************//
       
        return removeBtn;
    }
    
    #pragma mark-
    #pragma mark 这里填充图片的名称
    - (NSArray<NSString*>*)imageArray{
        return ImageArray;
    }
    
    + (void)load{
        
        NSString* versoin = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
        NSString* versionCache = [[NSUserDefaults standardUserDefaults] objectForKey:FIRST_IN_KEY];
        //启动时候首先判断是不是第一次
        
        if ([versoin isEqualToString:versionCache]) {
            return;
        }
        
        //以下代码只在程序安装初次运行时候执行
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            Method method1 = class_getInstanceMethod(self.class, @selector(viewDidLoad));
            Method method2 = class_getInstanceMethod(self.class, @selector(guidViewDidLoad));
            
            BOOL didAddMethod =
            class_addMethod(self.class,
                            @selector(viewDidLoad),
                            method_getImplementation(method2),
                            method_getTypeEncoding(method2));
            
            if (didAddMethod) {
                class_replaceMethod(self.class,
                                    @selector(guidViewDidLoad),
                                    method_getImplementation(method1),
                                    method_getTypeEncoding(method1));
            } else {
                method_exchangeImplementations(method1, method2);
            }
        });
    }
    
    - (void)guidViewDidLoad{
        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            //这里的代码只在程序安装初次打开,并且在第一个控制器里面执行
            //初始化视图
            [self setupSubViews];
        });
    
        //这是调用工程里面的viewDidLoad
        [self guidViewDidLoad];
    }
    
    #pragma mark- 
    #pragma mark 初始化视图
    
    - (void)setupSubViews{
        
        //界面样式
        UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init];
        flowLayout.itemSize = [UIScreen mainScreen].bounds.size;
        flowLayout.minimumLineSpacing = 0;
        flowLayout.minimumInteritemSpacing = 0;
        flowLayout.sectionInset = UIEdgeInsetsZero;
        flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
        UICollectionView* collectionView = [[UICollectionView alloc]
                          initWithFrame:self.view.bounds
                          collectionViewLayout:flowLayout];
        collectionView.dataSource = self;
        collectionView.delegate = self;
        collectionView.pagingEnabled = YES;
        collectionView.showsVerticalScrollIndicator = NO;
        collectionView.showsHorizontalScrollIndicator = NO;
        collectionView.backgroundColor = [UIColor whiteColor];
        [collectionView registerClass:[KSGuidViewCell class] forCellWithReuseIdentifier:@"KSGuidViewCell"];
        
        collectionView.tag = CollectionView_Tag;
        [self.view addSubview:collectionView];
        
        [self.view addSubview:self.removeBtn];
        
        UIPageControl* control = [[UIPageControl alloc] init];
        
        CGFloat controlW = 170;
        CGFloat controlH = 20;
        CGFloat controlX = CGRectGetMidX(self.view.frame) - controlW / 2;
        CGFloat controlY = CGRectGetMaxY(self.view.frame) - 38;
        control.frame = CGRectMake(controlX, controlY, controlW, controlH);
        control.numberOfPages = ImageArray.count;
        control.pageIndicatorTintColor = pageTintColor;
        control.currentPageIndicatorTintColor = currentTintColor;
        
        control.tag = Control_tag;
        [self.view addSubview:control];
    }
    
    #pragma mark-
    #pragma mark UICollectionViewDataSource
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
        return self.imageArray.count;
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
        
        KSGuidViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KSGuidViewCell" forIndexPath:indexPath];
        cell.imageName = self.imageArray[indexPath.row];
    
        return cell;
    }
    
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
        
        NSUInteger index = scrollView.contentOffset.x / CGRectGetWidth(self.view.frame);
        [self.view viewWithTag:RemoveBtn_tag].hidden = (index != self.imageArray.count - 1);
        
        UIPageControl* control =[self.view viewWithTag:Control_tag];
        control.currentPage = index;
        
    }
    
    - (void)removeGuidView{
        
        NSString* versoin = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    
        [[NSUserDefaults standardUserDefaults] setObject:versoin forKey:FIRST_IN_KEY];
        [[NSUserDefaults standardUserDefaults] synchronize];
        
        [[self.view viewWithTag:Control_tag] removeFromSuperview];
        [[self.view viewWithTag:RemoveBtn_tag] removeFromSuperview];
        [[self.view viewWithTag:CollectionView_Tag] removeFromSuperview];
    }
    
    
    @end
    
    
    demo详见github上

    相关文章

      网友评论

      • Funnyer:引导页上使用了runtime和不使用runtime实现,有什么优点吗?
      • Funnyer://这是调用工程里面的viewDidLoad
        [self guidViewDidLoad];
        不明白这是为什么?能解释一下吗?
        5f165a00895a:@Funny_艺术家 是啊,他只是调用ViewDidLoad的时候走到了你自己写的guidviewdidload这个方法里去了。
        Funnyer:@Jason丶King 哦,你的意思是不是就和viewdidload 调用方式一样的,只是方法名换了?但是我打断点看到调用[self guidviewdidload]之后会掉用viewdidload一次,这也是正常的么?
        5f165a00895a:我的理解是:因为 guidViewDidLoad 这个方法已经在load时,和ViewDidLoad替换了方法< method_exchangeImplementations >,所以, 此时调用guidViewDidLoad 就是调用 ViewDidLoad
      • iOS_:你好,你的demo在9.0系统跑过吗

      本文标题:iOS你在项目中用过 runtime 吗?举个例子

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