• App开发流程之使用分类(Category)和忽略编译警告(Warning)


    Category使得开发过程中,减少了继承的使用,避免子类层级的膨胀。合理使用,可以在不侵入原类代码的基础上,写出漂亮的扩展内容。我更习惯称之为“分类”。

    Category和Extension类似,都是对原类的扩展,区别是前者需要提供Category的名称,并且不直接支持属性;后者为匿名,多存在于类的实现文件,观感上实现属性、变量、方法的私有效果。

    主要记录分类使用过程中常涉及的内容:

    1.关联对象的使用

    分类虽然不直接支持属性,但是可以利用关联对象的方法,达到属性的正常使用效果。

    添加常用的刷新类库MJRefresh:https://github.com/CoderMJLee/MJRefresh

    为了避免原代码被侵入,采用了分类方案,给UIScrollView添加新的属性和方法。新建了一个分类UIScrollView+RefreshControl,在.h文件中声明了几个属性:

    /**
     *  头部刷新控件,可以自行设置hidden属性
     */
    @property (nonatomic, strong, readonly) UIView      *refreshHeader;
    
    /**
     *  底部刷新控件,可以自行设置hidden属性
     */
    @property (nonatomic, strong, readonly) UIView      *refreshFooter;
    
    /**
     *  分页数据中,请求的当前页数,考虑到网络请求失败,请自行管理;添加刷新后,默认为1
     */
    @property (nonatomic, assign          ) NSUInteger   refreshPageNum;
    
    /**
     *  分页数据中,每页请求的数量;添加刷新后,默认为10
     */
    @property (nonatomic, assign          ) NSUInteger   refreshCountPerPage;

    在.m文件中关联属性相关对象:

    - (UIView *)refreshHeader
    {
        return objc_getAssociatedObject(self, _cmd);
    }
    
    - (UIView *)refreshFooter
    {
        return objc_getAssociatedObject(self, _cmd);
    }
    
    - (NSUInteger)refreshPageNum
    {
        NSUInteger pageNum = [objc_getAssociatedObject(self, _cmd) integerValue];
        
        return pageNum;
    }
    
    - (void)setRefreshPageNum:(NSUInteger)refreshPageNum
    {
        objc_setAssociatedObject(self, @selector(refreshPageNum), @(refreshPageNum), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSUInteger)refreshCountPerPage
    {
        NSUInteger countPerPage = [objc_getAssociatedObject(self, _cmd) integerValue];
        
        return countPerPage;
    }
    
    - (void)setRefreshCountPerPage:(NSUInteger)refreshCountPerPage
    {
        objc_setAssociatedObject(self, @selector(refreshCountPerPage), @(refreshCountPerPage), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    objc_getAssociatedObject和objc_setAssociatedObject方法分别用于获取和保存关联的对象。_cmd与@selector([方法名])作用类似,都是获取到SEL,不过_cmd表示当前方法的SEL。

    因为是关联对象,所以即便是保存int类型,也需要转换为NSNumber对象,并设置为强引用类型。

     

    2.使用method_exchangeImplementations方法,也就是常说的swizzle技术

    添加常用的图片加载类库SDWebImage:https://github.com/rs/SDWebImage

    但是需要修改缓存图片的路径,缓存路径相关方法在SDImageCache中可以查看。只需要在其init时候,修改名为memCache和diskCachePath的属性。新建了一个分类SDImageCache+CacheHelper.h,然后在实现文件中添加如下代码:

    + (void)load
    {
        __weak typeof(self) weakSelf = self;
        
        static dispatch_once_t once;
        dispatch_once(&once, ^{
            [weakSelf swizzleOriginalSelector:@selector(init) withNewSelector:@selector(base_init)];
        });
    }
    
    + (void)swizzleOriginalSelector:(SEL)originalSelector withNewSelector:(SEL)newSelector
    {
        Class selfClass = [self class];
        
        Method originalMethod = class_getInstanceMethod(selfClass, originalSelector);
        Method newMethod = class_getInstanceMethod(selfClass, newSelector);
        
        IMP originalIMP = method_getImplementation(originalMethod);
        IMP newIMP = method_getImplementation(newMethod);
        
        //先用新的IMP加到原始SEL中
        BOOL addSuccess = class_addMethod(selfClass, originalSelector, newIMP, method_getTypeEncoding(newMethod));
        if (addSuccess) {
            class_replaceMethod(selfClass, newSelector, originalIMP, method_getTypeEncoding(originalMethod));
        }else{
            method_exchangeImplementations(originalMethod, newMethod);
        }
    }
    
    - (instancetype)base_init
    {
        id instance = [self base_init];
        
        [self resetCustomImageCachePath];
        
        return instance;
    }
    
    /**
     *  自定义图片缓存路径
     */
    - (void)resetCustomImageCachePath {
        //reset the memory cache
        NSString *rootDirectory = kAppImageCacheRootDirectory;
        NSCache *memCache = (NSCache *)[self valueForKey:@"memCache"];
        memCache.name = rootDirectory;
        
        //reset the disk cache
        NSString *path = [self makeDiskCachePath:rootDirectory];
        [self setValue:path forKey:@"diskCachePath"];
    }

    主要的方法有:

    class_getInstanceMethod

    method_getImplementation

    class_addMethod

    class_replaceMethod

    method_getTypeEncoding

    method_exchangeImplementations

     

    + (void)load静态方法会在类加载时候,即init前调用,分类的load方法顺序在原类的load方法之后。在这个时候交换init方法,添加修改缓存路径的方法即可达到目的。

    - (instancetype)base_init方法中调用了[self base_init],因为与init方法已经交换,所以该行代码其实就调用了原init方法。

     

    3.使用KVC

    就是因为KVC技术的存在,所以之前说“在观感上达到私有属性和变量的效果”。自定义的分类,不能直接访问memCache和diskCachePath属性,所以上述代码,使用了NSObject对象的方法:

    - (nullable id)valueForKey:(NSString *)key;

    - (void)setValue:(nullable id)value forKey:(NSString *)key;

    只需要知道属性或者变量名称,即可获取值或者设置值。

     

    4.使用performSelector调用对象方法

    KVC可以操作私有属性,针对私有方法,则可以通过对象的如下方法,对其不可见的方法进行调用:

    - (id)performSelector:(SEL)aSelector;

    - (id)performSelector:(SEL)aSelector withObject:(id)object;

    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

    还可以使用如下方法,先判断是否能相应某个指定方法:

    - (BOOL)respondsToSelector:(SEL)aSelector;

     

    添加常用类库SVProgressHUD:https://github.com/SVProgressHUD/SVProgressHUD

    准备为其增加分类方法,实现显示过场时的加载动画效果。新建分类SVProgressHUD+Extension,增加方法如下

    + (void)showAnimationImages:(NSArray<UIImage *> *)images animationDuration:(NSTimeInterval)animationDuration status:(NSString *)status
    {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored"-Wundeclared-selector"
        //写在该范围内的代码,都不会被编译器提示上述类型的警告
        SVProgressHUD *sharedProgressHUD = (SVProgressHUD *)[SVProgressHUD performSelector:@selector(sharedView)];
        __weak SVProgressHUD *weakInstance = sharedProgressHUD;
    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            __strong SVProgressHUD *strongInstance = weakInstance;
            if(strongInstance){
                // Update / Check view hierarchy to ensure the HUD is visible
    //            [strongSelf updateViewHierarchy];
                
                [strongInstance performSelector:@selector(updateViewHierarchy)];
                
                // Reset progress and cancel any running animation
    //            strongSelf.progress = SVProgressHUDUndefinedProgress;
    //            [strongSelf cancelRingLayerAnimation];
    //            [strongSelf cancelIndefiniteAnimatedViewAnimation];
                [strongInstance setValue:@(-1) forKey:@"progress"];
                [strongInstance performSelector:@selector(cancelRingLayerAnimation)];
                [strongInstance performSelector:@selector(cancelIndefiniteAnimatedViewAnimation)];
                
                // Update imageView
    //            UIColor *tintColor = strongSelf.foregroundColorForStyle;
    //            UIImage *tintedImage = image;
    //            if([strongSelf.imageView respondsToSelector:@selector(setTintColor:)]) {
    //                if (tintedImage.renderingMode != UIImageRenderingModeAlwaysTemplate) {
    //                    tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    //                }
    //                strongSelf.imageView.tintColor = tintColor;
    //            } else {
    //                tintedImage = [strongSelf image:image withTintColor:tintColor];
    //            }
    //            strongSelf.imageView.image = tintedImage;
    //            strongSelf.imageView.hidden = NO;
                UIImageView *imageView = (UIImageView *)[strongInstance valueForKey:@"imageView"];
                [imageView setImage:images[0]];
                [imageView setAnimationImages:images];
                [imageView setAnimationDuration:animationDuration];
                imageView.size = images[0].size;
                imageView.hidden = NO;
                [imageView startAnimating];
                
                // Update text
    //            strongSelf.statusLabel.text = status;
                UILabel *statusLabel = (UILabel *)[strongInstance valueForKey:@"statusLabel"];
                statusLabel.text = status;
                
                // Show
    //            [strongSelf showStatus:status];
                [strongInstance performSelector:@selector(showStatus:) withObject:status];
                
                // An image will dismissed automatically. Therefore we start a timer
                // which then will call dismiss after the predefined duration
    //            strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO];
    //            [[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes];
                NSTimer *timer = [NSTimer timerWithTimeInterval:100 target:strongInstance selector:@selector(dismiss) userInfo:nil repeats:NO];
                [strongInstance setValue:timer forKey:@"fadeOutTimer"];
            }
        }];
    #pragma clang diagnostic pop
    }

    可以看到,使用了上述手段,在不侵入原代码的情况下,实现了新增方法效果。

     

    5.即是标题中描述的:忽略编译警告

    先记录忽略代码片段中的编译警告

    注意上述代码中,增加了如下内容:

    #pragma clang diagnostic push
    #pragma clang diagnostic ignored"-Wundeclared-selector"
        //写在该范围内的代码,都不会被编译器提示上述类型的警告
    #pragma clang diagnostic pop

    因为使用performSelector方法时候,不可见的方法名,会被提示“Undeclared selector”的警告。使用上述代码,可以忽略代码片段中指定类型(-Wundeclared-selector)的编译警告。

    同理,也可以用于忽略其他类型的编译警告。

     

    但是,关键问题在于:如何获取编译警告的类型Flag,例如-Wundeclared-selector。

    先注释上述控制代码,即出现编译警告:

     

    然后右键其中一个警告,选择Reveal In Log:

     

    在All Issues中,关注如下内容:

    其中,[-Wundeclared-selector]就是该警告的类型flag。

    再顺便记录一下,自定义warning的控制代码,用于提示自己或者同事:#warning This is a custom warning

    6.忽略指定文件的编译警告

    找出警告类型如上,然后将flag内容修改为类似:-Wno-undeclared-selector,添加到下图中Compiler Flags中:

    这步骤与添加“-fno-objc-arc”的非ARC编译flag一样。

    7.忽略整个工程(Target)的编译警告

    在上图的Build Settings栏下,找到Other Warning Flags项:

    将之前步骤中找到的警告类型flag,加入Other Warning Flags的值中。

    以上记录了分类使用过程中常见的情况。合理使用分类,可以在形式上分离代码;扩展类的属性和方法;减少类继承的复杂层级关系。

    示例代码在Base框架中,Base项目已更新:https://github.com/ALongWay/base.git

  • 相关阅读:
    2013414
    Juqery Html(),append()等方法的Bug
    UpdatePanel 与 jQuery
    (C#)方法参数关键字:ref、out、params详解
    ASP.NET MVC 3 RC2 版本的一些新特性及AllowHtml属性Bug解决办法
    [hystar整理]Entity Framework 教程 转
    SQL 中in exists 和临时表用法
    ASP.NET 用户控件自定义属性、方法、事件
    SQL选择不重复记录
    sql over 用法
  • 原文地址:https://www.cnblogs.com/ALongWay/p/5956005.html
Copyright © 2020-2023  润新知