Category和Extension

375 阅读3分钟

 利用Objective-C的动态运行时分配机制,可以为现有的类(自己的或系统的或三方库的)添加新方法,这种为现有的类添加新方法的方式称为类别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的方法成功,需要时,导入头文件直接使用即可:(大多数情况都直接写在PCH文件里面)

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

二. 扩展类的属性(结合runtime可以添加属性,利用关联对象实现get和set方法)

举个例子,比如我们需要做一个button防止在规定的时间内不能连续点击的要求,我们需要向所有button添加一个属性,这个属性就是控制时间范围

.h文件里定义并暴露属性

@property (nonatomic, assign) NSTimeInterval qi_eventInterval;//button设置的时间属性

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

#import "UIButton+QiEventInterval.h"

#import <objc/runtime.h>static char * const qi_eventIntervalKey = "qi_eventIntervalKey";static char * const eventUnavailableKey = "eventUnavailableKey";@interface UIButton ()@property (nonatomic, assign) BOOL eventUnavailable;@end@implementation UIButton (QiEventInterval)+(void)load{    [super load];    Method method = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));    Method qi_method = class_getInstanceMethod(self, @selector(qi_sendAction:to:forEvent:));    method_exchangeImplementations(method, qi_method);}- (void)qi_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {    if (self.eventUnavailable == NO) {        self.eventUnavailable = YES;        [self qi_sendAction:action to:target forEvent:event];        [self performSelector:@selector(setEventUnavailable:) withObject:@(NO) afterDelay:self.qi_eventInterval];    }}-(NSTimeInterval)qi_eventInterval {   return [objc_getAssociatedObject(self, qi_eventIntervalKey) doubleValue];}- (void)setQi_eventInterval:(NSTimeInterval)qi_eventInterval {    objc_setAssociatedObject(self, qi_eventIntervalKey, @(qi_eventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (BOOL)eventUnavailable {    return [objc_getAssociatedObject(self, eventUnavailableKey) boolValue];}- (void)setEventUnavailable:(BOOL)eventUnavailable {    objc_setAssociatedObject(self, eventUnavailableKey, @(eventUnavailable), OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end

load和initialize的对比

维度

load

initialize

是否需要调用Super

不需要

不需要

调用时机

类或者category加载到runtime时由系统自动调用 在main函数之前

在类或者子类在第一次收到消息时调用(类消息或者实例消息)在main函数之后

调用方式

直接通过函数的内存地址调用的,不走msg_send,不走OC消息转发,每个类的load方法都是独立的

走OC消息转发, msg_send

调用次数

只会调用一次

1. 可能会调用多次,如果子类没有实现该方法,则子类在第一次收到消息时会调用父类的方法。2.如果在子类收到消息前,父类没有收到过消息,那么会先调用父类的initialize方法之后再调用子类的initialize方法。

调用顺序

父类-子类-category 。1.所有的类都加载完之后才会调用所有的category。 2.不同的类的load执行顺序跟编译顺序有关,可以在Target -> Build Phases -> Compile Sources 中调整顺序。但所有的category都是在所有的类的load都执行完之后,才会调的。 3.多个类的多个category或者一个类的不同category的load方法的顺序同样跟编译顺序有关,可以在compiles source中调整。

父类-子类(如果category有实现,会只调用category中的实现;父类的category会覆盖父类的实现,子类的category会覆盖子类的category实现)

使用

一般用来实现 Method Swizzle

一般用来初始化全局变量 或者 静态变量

Extension

1、 什么是extension

extension被开发者称之为扩展、延展、匿名分类。extension看起来很像一个匿名的category,但是extension和category几乎完全是两个东西。和category不同的是extension不但可以声明方法,还可以声明属性、成员变量。extension一般用于声明私有方法,私有属性,私有成员变量。

2、 extension的存在形式

category是拥有.h文件和.m文件的东西。但是extension不然。extension只存在于一个.h文件中,或者extension只能寄生于一个类的.m文件中。比如,viewController.m文件中通常寄生这么个东西,其实这就是一个extension