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. Laravel5设计json api时候的一些道道

    对于返回数据格式没规整的问题 在开发api的时候,这个问题是和客户端交涉最多的问题,比如一个user结构,返回的字段原本是个user_name的,它应该是string类型.但是呢,由于数据库设计这个字 ...

  2. 关于Entity Framework自动关联查询与自动关联更新导航属性对应的实体注意事项说明

    一.首先了解下Entity Framework 自动关联查询: Entity Framework 自动关联查询,有三种方法:Lazy Loading(延迟加载),Eager Loading(预先加载) ...

  3. codeMirror插件使用讲解

    codeMirror是一款十分强大的代码编辑插件,提供了十分丰富的API,最近在项目中用到了这款插件,于是在这里给大家分享下使用方法和心得: codeMirror调用非常方便 首先在页面中载入插件CS ...

  4. Python语言特性之4:类变量和实例变量

    类变量就是供类使用的变量,实例变量就是供实例使用的.如下面的代码: class Person: name = "Tacey" p1 = Person() p2 = Person() ...

  5. DP - 2016网易杭研笔试题A

    2016网易杭研笔试题A Problem's Link ------------------------------------------------------------------------ ...

  6. Emit学习(1) - HelloWorld

    之前看过Dapper(使用到了Emit), CYQ.Data(另一种思路,没有使用Emit)类的框架之后, 也想自己做一个小框架玩一下, 不过此时能力太过欠缺, 做不了Cyq.Data或者PDF.Ne ...

  7. jquery可见性选择器(综合)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. Delphi iOS 开启文件共享 UIFileSharingEnabled

    Apple 在 iOS 提供了文件共享(FileSharing)功能,让 App 有一个对外窗口的目录,透过 iTunes 可以任意管理这个目录的文档内容(可拖入文档,也能将文档拖出备份). 如果 A ...

  9. 【转】数据库无关的GO语言ORM - hood

    项目地址:https://github.com/eaigner/hood 这是一个极具美感的ORM库. 特性 链式的api 事务支持 迁移和名字空间生成 模型变量 模型时间 数据库方言接口 没有含糊的 ...

  10. Python Sqlite3以字典形式返回查询结果

    sqlite3本身并没有像pymysql一样原生提供字典形式的游标. cursor = conn.cursor(pymysql.cursors.DictCursor) 但官方文档里已经有预留了相应的实 ...