iOS中的Category类别

问题: OC中类别(Category)是什么?
Category类别是Objective-C语言中提供的一个灵活的类扩展机制。类别用于在不获悉、不改变原来代码的情况下往一个已经存在的类中添加新的方法,只需要知道这个类的公开接口,而不需要知道类的源代码。类别只能为已存在的类添加新的功能扩展方法,而不能添加新的属性。类别扩展的新方法有更高的优先级,会覆盖同名的原类的已有方法。

问题: Category类别与其他特性的比较?
与继承Inheritance的比较:
1.子类继承是进行类扩展的另一种常用方法,当然基于子类继承的扩展更加自由、正式,既可以扩展属性也可以扩展方法。类别可以在不获悉、不改变原来代码的情况下往里面添加新的方法,但也只能添加方法,不能添加属性,属于功能上的扩展。类别扩展的优点是不需要创建一个新的类,而是在系统中已有的类上直接扩展、拼装,不需要更改系统类就可以添加并使用扩展方法。

2.相对于子类继承扩展,类别的另一明显优势是实现了功能的局部化封装,扩展的功能只会在本类被引用时看到。例如,假设原类为UIButton,现在要使用类别扩展一些用于模块A的方法,那么这些扩展方法就可以定义在一个叫做UIButton+A.h的头文件中,只有在引用UIButton+A.h的地方,才能看到原UIButton类有模块A添加的那些扩展方法,如果不需要模块A的功能,不引用UIButton+A.h头文件就看不到UIButton的那些扩展方法的存在,实现扩展模块的隔离。

与扩展Extension的比较:

Category类别和Extension扩展的明显不同在于,后者可以添加属性。另外后者添加的方法是必须要实现的。Extension可以认为是一个私有的匿名的Category,因为Extension定义在.m文件头部,添加的属性和方法都没有暴露在头文件,在不考虑运行时特性的前提下这些扩展属性和方法只能类内部使用,一定程度上可以说是私有的。 ***

问题: 类别有什么作用和好处?类别的局限性和使用注意事项

作用:

可以将类的实现分散到多个不同文件或多个不同框架中(扩充新的方法);
可以创建对私有方法的前向引用;
可以向对象添加非正式协议;
局限性:

类别只能向原类中添加新的方法,且只能添加而不能删除或修改原方法,不能向原类中添加新的属性;
类别向原类中添加的方法是全局有效的而且优先级相对最高,如果和原类的方法重名,会无条件覆盖掉原来的方法,造成难以发现的潜在危险,因此使用类别添加方法一定注意保证是单纯的添加新方法,避免覆盖原来的方法(可以通过添加该类别的方法前缀来防止冲突),否则原方法被类别覆盖了,团队中其他成员不知情的情况下用到这个被覆盖的方法会出现意想不到的问题而难以察觉纠正。

问题: Category类别和Extension类扩展的使用方法?
类别和扩展的区别我们已经知道了,最明显的是类别不可以添加新属性而类扩展可以。类别的使用方法很简单,就是新建某个类的类别扩展文件,然后添加新的方法。而类扩展并不常用,只是常用在.m文件中的头部进行头文件的私有属性变量补充,也就是所谓的类的Continuous区域,是将不想暴露给外部的一些变量定义在类扩展中。

创建类别或扩展文件:
为工程添加新文件并选择Objective-C File:


80.png

填写自定义的扩展名后缀,然后选择文件类型,这里选择Category类别或者Extension扩展,最后选择要扩展的已有类:


81.png

82.png

创建之后得到对应的类别文件:


84.png

添加新的扩展方法:
这里以扩展NSString类的方法为例展示类别扩展的具体用法,分别添加一个类方法和实例方法,并在需要的地方调用:

类别头文件方法声明:

/* NSString+Category.h */
#import <Foundation/Foundation.h> 
@interface NSString (Category) {
    /* 不可以添加实例变量,编译器会直接报错!*/
}

/* 实例变量被禁止,此处添加属性变量是无意义的,定义的新的属性,编译器没有实现存取方法,自己也无法手动实现存取方法,因为无法获取加下划线的实例变量,除非利用运行时强行实现存取方法则可以成功为类别添加属性,代价较高 */
//@property (nonatomic, copy) NSString *newString; 
/** * 扩展一个类方法 */
+ (void)categoryClassMethodOfString;

/** * 扩展一个实例方法 */
- (void)categoryInstanceMethodOfString;

类别方法实现,类别的方法可以不实现,不实现则不可调用否则会崩溃:

/* NSString+Category.m */
#import "NSString+Category.h" 
@implementation NSString (Category)

+ (void)categoryClassMethodOfString {
    NSLog(@"categoryClassMethodOfString");
}

- (void)categoryInstanceMethodOfString {
    NSLog(@"categoryInstanceMethodOfString");
}

@end

在需要的类中引入类别头文件NSString+Category.h,然后即可调用新方法:

#import "NSString+Category.h" /* 1.调用类别扩展的类方法 */
[NSString categoryClassMethodOfString];
    
/* 2.调用类别扩展的实例方法 */
NSString *string = [NSString stringWithFormat:@""];
[string categoryInstanceMethodOfString];

类扩展的一般用法:
类扩展即类的.m文件中@implementation之前开始的部分,所谓的类的continuous区域:

@interface class name ()
// ... @end

类扩展的作用本来是用于私有函数的前向声明,但最新编译器无需声明也有相同的效果,因此私有方法可在.m文件中任意位置直接写实现而无需在此处进行前向声明,如果在此处声明函数那么一定要在后面进行实现,否则编译器会给出警告。现在类扩展区域的作用主要是快速定义类的私有属性,即将暴露给外部的属性变量定义在头文件中,而不想暴露给外部的属性则直接定义在类扩展区域。【注意这里的私有属性和私有方法并不是绝对私有的,OC中没有绝对的私有方法和私有变量,因为即使它们隐藏在.m实现文件里不暴露在头文件中,开发者仍然可以利用runtime运行时机制对其暴力访问,只是一般情况可以达到私有的效果】

/* 类扩展区域 */
@interface ViewController ()

/* 类扩展属性(默认为private, 类外部不可访问) */
@property (nonatomic,copy) NSString *extensionVariable;

/** * 类扩展方法声明,可省略,要在立刻在后面进行函数实现 */
- (void)extensionInstanceMethod;
+ (void)extensionClassMethod;

@end
86.png

问题: 为什么类别只能添加扩展方法而不能添加属性变量?
这个问法有点歧义,实际问的是为什么向类别添加属性会失败,而非官方为何有意不让类别扩展属性,如果是有意,则可能从设计上考虑保持类别特性的单纯,专门用来扩展功能,和继承的角色区别开,防止类别污染被扩展的类。

话说回来,在类别中扩展属性不能成功的原因是无法在类别中取得属性的加下划线的实例变量名,导致无法手动实现实例变量的存取方法。在类别中定义了属性后,属性其实也成功添加到了类的属性列表中,但编译器只为其声明了存取方法,没有实现,同时又没有合成加下划线的实例变量名,导致无法访问实例变量也无法自己手动实现其存取方法,对于一个不能访问的属性则失去了存在的意义。

但是如果使用运行时的武器,我们其实可以强行实现类别中属性的存取方法,实现在类别中扩展属性。这里在运行时,实现为NSString类扩展一个叫做newString的属性:

/* NSString+Category.h */
#import <Foundation/Foundation.h> 
@interface NSString (Category)
/* 在类别中扩展属性 */
@property (nonatomic, copy) NSString *newString;
@end
/* NSString+Category.m */
#import "NSString+Category.h" #import <objc/runtime.h> 
@implementation NSString (Category)
/** * 运行时强行实现newString的getter和setter */
- (NSString *)newString {
    return objc_getAssociatedObject(self, @"newString");
}
- (void)setNewString:(NSString *)newString {
    objc_setAssociatedObject(self, @"newString", newString, OBJC_ASSOCIATION_COPY);
}
@end
/* main.m */
#import <Foundation/Foundation.h>
#import "NSString+Category.h"

int main(int argc, const char * argv[]) {
    NSString *string;
    /* 调用newString的setter方法 */
    string.newString = @"newString";
    /* 调用newString的getter方法 */
    NSLog(@"%@",string.newString);
    return 0;
}

问题: iOS中什么是‘扮演者’?
Objective-C允许应用中的一个类完全替代另一个类,并称其为替代类‘扮演’了被替代的类。所有本来发送给被替代的类的消息都会转而被‘扮演者’类所接收,消息也不需要在发送给‘扮演者’之前先发给被替代的类了。对于类的‘扮演’也有一些限制:一个类可能只能扮演一个它的直接或间接父类,‘扮演者’类不能再定义被替代类所没有的新的实例变量(但是可以定义或者重写方法)。
‘扮演’,和Category类别有类似之处,都允许扩展已有的类,但‘扮演’有两个类别所没有的特性:

一个‘扮演者’类可以通过super关键字调用父类中已经覆盖了的方法,因此可以辅助被替代类的功能实现;
一个‘扮演者’类可以覆盖在类别中定义的方法,优先级更高;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容