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:@(-) 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[]];
[imageView setAnimationImages:images];
[imageView setAnimationDuration:animationDuration];
imageView.size = images[].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: 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

App开发流程之使用分类(Category)和忽略编译警告(Warning)的更多相关文章

  1. 20个可以帮你简化iOS app开发流程的工具

    这里推荐20个可以帮你简化iOS app开发流程的工具.很多开发者都使用过这些工具,涉及原型和设计.编程.测试以及最后的营销,基本上涵盖了整个开发过程. 原型和设计 有了一个很好的创意后,你要做的不是 ...

  2. iOS开发之工具篇-20个可以帮你简化移动app开发流程的工具

    如果想进入移动app开发这个领域,你总能从别的开发者或者网上或者书上找到各种各样的方法和工具,对于新手来说,还没有摸清门路就已经陷入迷茫了.这里推荐20个可以帮你简化app开发流程的工具.很多开发者都 ...

  3. app开发流程有哪些

    app开发流程是需求方和供求方相互协调的过程,一般分为需求分析.功能设计.功能实现.项目测试.上线等几个步骤,下面我们就来一起看看ytkah团队进行app开发各个流程主要做哪些事情,让您对app开发设 ...

  4. 深度讲解智能硬件手机APP开发流程

    常州做APP开发公司紫竹云科技分析,智能硬件产品的软件开发,除了APP和后台之外还有一个固件端的开发,由于固件是要运行产品上的,不过此时的硬件也是刚开始进行研发,所以是无法提供硬件来运行固件的.因此在 ...

  5. App开发流程之加密工具类

    科技优家 2016-09-08 18:10 从这篇记录开始,记录的都算是干货了,都是一些编程日常的积累. 我建议先将基础的工具加入项目,后续的开发效率会呈指数增长.如果在专注功能开发过程中,才发现缺少 ...

  6. APP开发流程

    1.申请一个开发者账号: 2. App的idea形成: 3. App的主要功能设计: 4. App的大概界面构思和设计(使用流程设计): 5. 大功能模块代码编写: 6. 大概的界面模块编写: 7. ...

  7. iOS 直播类APP开发流程分解:

    1 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示1.数据采集:摄像机及拾音器收集视频及音频数据,此时得到的为原始数据涉及技术或协议:摄像机:CCD.C ...

  8. App开发流程之右滑返回手势功能

    iOS7以后,导航控制器,自带了从屏幕左边缘右滑返回的手势功能. 但是,如果自定义了导航栏返回按钮,这项功能就失效了,需要自行实现.又如果需要修改手势触发范围,还是需要自行实现. 广泛应用的一种实现方 ...

  9. App开发流程之图像处理工具类

    先罗列一下工具类中提供的方法: /** * 根据原始view和毛玻璃样式,获取模糊视图,并自动作为原view的subview(如果不需要作为子视图,自行调用removeFromSuperview) * ...

随机推荐

  1. PyQt写的五子棋

    技术路线 GUI的实现 使用PyQt技术作为基础.PyQt是一个支持多平台的客户端开发SDK,使用它实现的客户端可以运行在目前几乎所有主流平台之上. 使用PyQt,Qt设计器实现UI,通过pyuic4 ...

  2. c# 中基类变量指向派生类对象的实例化

    这一篇文章转载自:http://www.xuebuyuan.com/390279.html 我对这篇文章进行了一一的验证,确实是这样子的,也明白了很多东西,觉得很有用,转载过来希望能够帮助大家. 1. ...

  3. C#基础整理参数

    基本概念 把数据传入方法中,可以使方法有多个返回值. 参数的传递 值参数,通过将实参的值复制到形参的方式传递数据.值参数的实参可以是变量或者是表达式

  4. C#串口通信

    通过COM1发送数据,COM2接收数据.当COM2接收完本次发送的数据后,向COM1发送信息通知COM1本次数据已发完,COM1接到通知后,再发下一段数据.这样可以确保每次发送的数据都可以被正确接收. ...

  5. Clr编写Insert Triggr

    在CLR编写一个插入娄据的触发器. 这个触发器是当对表插入数据时,即时把刚才插入的数据显示出来: 可复制代码: public static void tri_RetrieveJustInsertedD ...

  6. 全动态Portlet点击后选中样式

    1  背景概述 在配置公司云平台的帮助信息过程中,由于使用的全动态portlet的数据URL获取到的是静态数据,没有办法在后台做选中的逻辑判断,所以需要在前台来控制选中列表的样式,这里将对前台选中列表 ...

  7. C#根据网址生成静态页面

    HoverTree开源项目中HoverTreeWeb.HVTPanel的Index.aspx文件 是后台管理的首页. 包含生成留言板首页,以及显示用户名,退出等功能. 根据网址生成页面的方法: boo ...

  8. jquery列表顺序倒转排序效果

    html文件代码: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type&quo ...

  9. MDI窗体容器 权限设置

    MDI窗体容器:它可以让其它窗体在它的内部打开,无法超出它的范围将某个窗体的属性:IsMdiContainer设置为true - 窗口样式如何将其它窗体在它的内部打开?窗体对象名.MdiParent ...

  10. C# 图片自由变换 任意扭曲

    之前想过要做个地铁驾驶的游戏,其中想把一些原本是矩形图片弄成一个梯形,但是发现GID+上面没有类似的方法.于是在谷歌谷了一下.没有!只能找到令人垂涎的,并没有源码.按照自己的想法尝试了一两天,有点效果 ...