Category在项目中的实际运用

先看两行代码:
1.

label2.textColor = [UIColor colorWithHexString:@"707070"];

2.

_table.header = [MJRefreshHeader headerWithRefreshingBlock:^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
               // 刷新时的相关处理
            });
        }];

相信大家对上面的两行代码都不会陌生

  • 上一行:UIColor原本是没有读取十六进制颜色值的方法的
  • 下一行:UITableView原本是没有header属性的

那么,How it happened?

Because of the Category!


Category(类别)简介

  1. 利用Objective-C的动态运行时分配机制,可以为现有的类(自己的或系统的或三方库的)添加新方法,这种为现有的类添加新方法的方式称为类别category,他可以为任何类添加新的方法,包括那些没有源代码的类。
  2. 类别使得无需创建对象类的子类就能完成同样的工作。

Category的作用

一. 扩展类的方法
这是我们用的最多的,现在就以扩展UIColor的一个读取16进制颜色的方法为例:

在.m文件中编写扩展的方法

+ (UIColor *)colorWithHexString:(NSString *)color alpha:(CGFloat)alpha
{
    //删除字符串中的空格
    NSString *cString = [[color stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
    // String should be 6 or 8 characters
    if ([cString length] < 6)
    {
        return [UIColor clearColor];
    }
    // strip 0X if it appears
    //如果是0x开头的,那么截取字符串,字符串从索引为2的位置开始,一直到末尾
    if ([cString hasPrefix:@"0X"])
    {
        cString = [cString substringFromIndex:2];
    }
    //如果是#开头的,那么截取字符串,字符串从索引为1的位置开始,一直到末尾
    if ([cString hasPrefix:@"#"])
    {
        cString = [cString substringFromIndex:1];
    }
    if ([cString length] != 6)
    {
        return [UIColor clearColor];
    }

    // Separate into r, g, b substrings
    NSRange range;
    range.location = 0;
    range.length = 2;
    //r
    NSString *rString = [cString substringWithRange:range];
    //g
    range.location = 2;
    NSString *gString = [cString substringWithRange:range];
    //b
    range.location = 4;
    NSString *bString = [cString substringWithRange:range];

    // Scan values
    unsigned int r, g, b;
    [[NSScanner scannerWithString:rString] scanHexInt:&r];
    [[NSScanner scannerWithString:gString] scanHexInt:&g];
    [[NSScanner scannerWithString:bString] scanHexInt:&b];
    return [UIColor colorWithRed:((float)r / 255.0f) green:((float)g / 255.0f) blue:((float)b / 255.0f) alpha:alpha];
}

在.h文件中将方法暴露出来

/* 从十六进制字符串获取颜色 */
+ (UIColor *)colorWithHexString:(NSString *)color alpha:(CGFloat)alpha;

至此,扩展系统类UIColor的方法成功,需要时,导入头文件直接使用即可:

self.view.backgroundColor=[UIColor colorWithHexString:@"f7f7f9"];

二. 扩展类的属性(结合runtime)
这个也是相当实用的,举个??:我们如果要给所有UIButton都添加一个name属性,怎么破?这个时候Category又可以秀一下了(都是套路):
.h文件里定义并暴露属性

/** button的name */
@property (nonatomic,copy) NSString *name;

.m文件先导入<objc/runtime.h>,然后处理set和get方法

static void *strKey = &strKey;

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, & strKey, name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name{
    return objc_getAssociatedObject(self, &strKey);
}

然后就可以使用了??

button.name = @"小button";
NSLog(@"%@",button.name);

三. 在没有源代码的情况下可以用来修复BUG

当知道已有类中某个方法有BUG,但是这个类是以库的形式存在的,我们无法直接修改源代码的时候,Category也可以用于替代这个已有类中某个方法的实体,从而达到修复BUG的目的。

说白了就是替换原有的方法,不过替换之前要先想好是否会影响原有的功能以及是否有更深层的bug在后面等着你。。。??

四. 整体替换
one day,产品跑来告诉我:系统的弹窗太丑了,要全部换成自定义弹窗。我怀着忐忑的心情在工程中输入UIAlertView进行搜索,尼玛,300多个搜索结果。。。当时我首先想到的是新建一个类,让它有和UIAlertView有相同的方法,然后将UIAlertView全部换成新类即可。不过后来仔细想想,方法虽行,但是要替换的太多了,万一出现什么差错就麻烦了。于是乎,我使用了Category,重置了UIAlertView的相关方法,仅仅新建了一个Category文件,没有在原来的工程中进行任何修改和替换。这里,应该有一波666??

如何优雅的使用Category?

关于这一点,你首先得清楚Category和继承的区别:

继承Inherit
这个是面向对象语言都有的一个特性,子类会继承父类的方法和属性。
继承会新建一个子类,但类别不会。

弄清Category和继承的区别后,才知道什么时候用Category,什么时候用继承,这个也是面试官喜欢问的,所以不管怎样请务必融会贯通。
有人说:

如果需要添加一个新的变量,则需添加子类。
如果只是添加一个新的方法,用Category是比较好的选择。

关于什么时候用Category,什么时候用继承,我个人经验如下:

  • 以UIButon为例,如果你希望你项目中所有的UIButton都有一个属性:name,那么这个时候直接用Category给UIButton扩展个属性即可。
  • 同样需要给button添加一个name属性,但是这种button只在某个或几个(反正就是用的情况不多)页面使用时,这时我通常采取继承的方式自定义一个button。

项目实际运用举例

先看一张UI设计图:

项目中会大量使用到带红色边框的button,如果我们每次都来实现这个边框效果必然非常繁琐。这个时候用Category给UIButton扩展一个自定义构造方法便可完美解决这个烦恼??
代码如下:

/**
 *  自定义带边框的button
 *
 *  @param frame       frame
 *  @param borderColor 边框颜色
 *  @param borderWidth 边框宽度
 *
 *  @return 自定义带边框的button
 */
- (instancetype)initWithFrame:(CGRect)frame borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth{
    if (self = [super initWithFrame:frame]) {
        // 边框颜色
        self.layer.borderColor = borderColor.CGColor;
        // 边框宽度
        self.layer.borderWidth = borderWidth;
        // title颜色(与边框颜色一致)
        [self setTitleColor:borderColor forState:UIControlStateNormal];
        // 圆角
        self.layer.cornerRadius = 3;
        // 字号
        [self.titleLabel setFont:[UIFont systemFontOfSize:13]];
    }
    return self;
}

使用:

UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(100, 200, 69, 21) borderColor:[UIColor colorWithHexString:@"ff0000"] borderWidth:2];
    [button setTitle:@"立即购买" forState:UIControlStateNormal];
    [self.view addSubview:button];

效果图:

注意

  1. 如果category扩展的方法名已存在,会覆盖原有的方法名相同,是根据buildPhases->Compile Sources里面的从上至下顺序编译的,即后编译的会被调用。
  2. 新扩展的方法与原方法同名,但是还需要使用父类的实现时,这个时候只能用继承。

分享个我常用的Category

.h 文件

@interface UIView (frameAdjust)

// 左上角x坐标
- (CGFloat)x;
- (void)setX:(CGFloat)x;

// 左上角y坐标
- (CGFloat)y;
- (void)setY:(CGFloat)y;

// 宽
- (CGFloat)width;
- (void)setWidth:(CGFloat)width;

// 高
- (CGFloat)height;
- (void)setHeight:(CGFloat)height;

// 中心点x
- (CGFloat)centerX;
- (void)setCenterX:(CGFloat)x;

// 中心点y
- (CGFloat)centerY;
- (void)setCenterY:(CGFloat)y;

/** 获取最大x */
- (CGFloat)maxX;
/** 获取最小x */
- (CGFloat)minX;

/** 获取最大y */
- (CGFloat)maxY;
/** 获取最小y */
- (CGFloat)minY;

@end

.m 文件

@implementation UIView (frameAdjust)

- (CGFloat)x{
    return self.frame.origin.x;
}

- (void)setX:(CGFloat)x{
    self.frame = CGRectMake(x, self.y, self.width, self.height);
}

- (CGFloat)y{
    return self.frame.origin.y;
}

- (void)setY:(CGFloat)y{
    self.frame = CGRectMake(self.x, y, self.width, self.height);
}

- (CGFloat)width{
    return self.frame.size.width;
}

- (void)setWidth:(CGFloat)width{
    self.frame = CGRectMake(self.x, self.y, width, self.height);
}

- (CGFloat)height{
    return self.frame.size.height;
}

- (void)setHeight:(CGFloat)height{
    self.frame = CGRectMake(self.x, self.y, self.width, height);
}

- (CGFloat)centerX{
    return self.center.x;
}

- (void)setCenterX:(CGFloat)x{
    self.center = CGPointMake(x, self.center.y);
}

- (CGFloat)centerY{
    return self.center.y;
}

- (void)setCenterY:(CGFloat)y{
    self.center = CGPointMake(self.center.x, y);
}

/** 获取最大x */
- (CGFloat)maxX{
    return self.x + self.width;
}
/** 获取最小x */
- (CGFloat)minX{
    return self.x;
}

/** 获取最大y */
- (CGFloat)maxY{
    return self.y + self.height;
}
/** 获取最小y */
- (CGFloat)minY{
    return self.y;
}

@end

后记

Category在项目中的运用不仅仅这些,以上只是抛砖引玉。那么现在问题来了:想必看官们也有自己的一些使用心得吧,真心求分享(不分享的木JJ,自己看着办??)。另,有任何问题欢迎提出??

补充(2016年9月15日)

今天敲代码时,有个功能要多次用到:让UILabel的宽度随文本内容多少而改变。UILabel有一个方法sizeToFit可以完成这个任务,但是,sizeToFit使用后,UILabel的文字就跑到左上角了,也就是说这个时候还要调整label的中心,一次还好,如果每次都这样就太繁琐了。所以这个时候Category又可以派上用场了,代码如下:

  • .h文件:
@interface UILabel (util)

/** 将label的宽度调整到适应文本内容的最低值 */
- (void)adjustWidthToMin;

@end
  • .m文件:
@implementation UILabel (util)

/** 将label的宽度调整到适应文本内容的最低值 */
- (void)adjustWidthToMin{
    // 先记录label原本的中心Y
    CGFloat centerY = self.centerY;
    // 调整label
    [self sizeToFit];
    // 调整中心
    self.centerY = centerY;
}

@end

当你敲代码时感觉系统提供的方法不够用时,尽情的使用Category扩展吧!

补充(2016年10月7日)

今天多次遇到需要在View里执行视图控制器跳转的情况,也就是说获取当前view所在的viewController然后跳转,它的原理是遍历当前view的superView,找到那个viewController。这个时候,又可以给view添加个category了,代码如下:

/** 获取当前view所在的viewController */
- (UIViewController *)getCurrentViewController{
    for (UIView* next = [self superview]; next; next = next.superview)
    {
        UIResponder *nextResponder = [next nextResponder];
        if ([nextResponder isKindOfClass:[UIViewController class]])
        {
            UIViewController *vc = (UIViewController *)nextResponder;
            return vc;
        }
    }
    return nil;
}

下面是一个简单的运用,改变一个button所在的视图控制器的背景颜色(UIButton继承于UIControl继承于UIView)

@interface ViewController (){
    UIButton *_button;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 创建一个button
    _button = [[UIButton alloc]initWithFrame:CGRectMake(90, 90, 90, 90)];
    [self.view addSubview:_button];
    _button.backgroundColor = [UIColor orangeColor];
    [_button addTarget:self action:@selector(changeColor) forControlEvents:UIControlEventTouchDown];
}

- (void)changeColor{
    // 获取button所在的viewController
    UIViewController *currentVC = [_button getCurrentViewController];
    // 改变viewController的背景颜色
    currentVC.view.backgroundColor = [UIColor redColor];
}
时间: 2024-10-27 13:51:08

Category在项目中的实际运用的相关文章

Android在项目中接入腾讯TBS浏览器WebView的教程与注意的地方

腾讯TBS浏览器服务 我们都知道,在Android开发中,经常会用到Webview,而且WebView是出了名的坑的,各种bug.这时候腾讯老哥站出来了,搞了一个TBS浏览器服务这个东西. 说得这么屌,其实就是一个webView控件,然后解析解析网页的内核是他自己做的,叫X5内核(系统原生的WebView用的是WebKit内核),所以我们开发者用的时候,主要就是用这个com.tencent.smtt.sdk.WebView控件 当然这个控件有很多功能,当然也有些要注意的地方. 官网地址:http

Android的学习之路(四)项目中清单文件的学习和android中经常使用的显示单位

1.所谓的清单文件就是项目中的AndroidManifest.xml文件.这个文件但是有大用处的.比方:app的名字,图标.app支持的版本号app的包名等等.以下我就介绍下这个清单文件的各个參数的作用. <manifest xmlns:android="http://schemas.android.com/apk/res/android"命名空间 package="com.example.hello"包名唯一标示一个应用 android:versionCod

Androidmanifest.xml在Android项目中的作用

以下是一个项目中的AndroidManifest.xml文件: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="tw.suggest"                                //指定项目中的程序文件的包

03_Android项目中读写文本文件的代码

编写一下Android界面的项目 使用默认的Android清单文件 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.itheima28.writedata" android:versionCode="1&qu

在Android项目中引入MuPdf

由于公司手机App要加入一个附件查看功能,需要查看PDF文件,在网上找了许多第三方工具,最后选择了MuPDF. 更多第三方工具可以查看大神总结的:http://www.cnblogs.com/pokeGame/archive/2011/06/02/2068575.html MuPDF介绍: Android 设备上轻量级.高品质的 PDF/XPS/CBZ 查看器. MuPDF 上的呈现器专为高质量的抗失真图像量身打造,它以像素级的精度高品质呈现文字和文字间的间距,从而获得最高级别的显示保真度,在设

asp.net Web项目中使用Log4Net进行错误日志记录

使用log4net可以很方便地为应用添加日志功能.应用Log4net,开发者可以很精确地控制日志信息的输出,减少了多余信息,提高了日志记录性能.同时,通过外部配置文件,用户可以不用重新编译程序就能改变应用的日志行为,使得用户可以根据情况灵活地选择要记录的信息. 那么我们如何在Web项目中使用Log4Net呢? 一.基本配置 1.下载Log4Net,地址如下:http://logging.apache.org/log4net/download_log4net.cgi,如下图所示: 2.下载到本地后

软件开发工程师(JAVA)中级考试大纲-----四(四)Log4J的原理及配置;Log4J常用的API;在项目中应用日志框架Log4J关键类和接口介绍;Java properties配置文件log

log4j Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件,甚至是套接口服务器.NT的事件记录器.UNIX Syslog守护进程等:我们也可以控制每一条日志的输出格式:通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程.最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码. 1定义 log4j--log for java(java的日志) 在强调可重用组件开发的今天,除了

iOS项目中使用CocoaPods问题解决方案

文/yehot(简书作者)原文链接:http://www.jianshu.com/p/a2007d8e2607著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 为什么iOS项目中应该使用CocoaPods作为第三方依赖管理工具?因为: (全文完) 开个玩笑.真正的原因是这样: 目录: 从一个bug说起 分析需求及解决方案 确定方案 CocoaPods学习资料 一.从一个bug说起: 1.公司的项目里统一使用SVG格式的图片:2.GitHub上只有一个star数超过一千的SVG解析库

web项目中log4j的配置

log4j是一个很好的开源的日志项目,下面就我在实际中使用的一些情况作一个小结(我所写的是以spring为框架的运用,之所以要提到这点,是因为在spring中专门有处理log4j的地方,而我也用到了这些地方). 在使用的第一步你要明白你所发布的web项目所使用的服务器,因为不同的服务器对于使用log4j是有些不同的,我在实际使用中主要是用tomcat和 jboss两类,对于tomcat,它本身是没有配置log4j的,所以使用起来和常规的一样:而在jboss中它是本身配置了log4j的,所以有时候