iOS-Category与Extension

知 识 文 / 超 人


Category

Category是Objective-C 2.0之后添加的新语言特性,分类和类别都是指的Category。Category的主要作用是为已经存在的类添加方法和属性,其作用是在运行期决定的。
通过clang命令行查看Category源码:

struct _category_t {
    const char *name;//类名字
    struct _class_t *cls;//类
    const struct _method_list_t *instance_methods;////category中所有给类添加的实例方法的列表
    const struct _method_list_t *class_methods;//category中所有添加的类方法的列表
    const struct _protocol_list_t *protocols;//category实现的所有协议的列表
    const struct _prop_list_t *properties;//category中添加的所有属性列表
};

在程序启动时,系统会自动做好class与class对应的Category的映射,会调用remethodizeClass方法来修改class的_method_list_t的结构,从而实现为原类添加方法和协议。这才是runtime实现Category的关键

例如Category文件名为Person+Clothes.m
1.打开终端
2.cd到Person+Clothes.m文件目录
3.在终端中输入xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Clothes.m
4.系统会自动生成Person+Clothes.cpp文件,打开Person+Clothes.cpp文件,搜索_category_t就可以看到Category的结构体了

装饰者模式

装饰者模式:Category 是Objective-C 中对装饰者模式的一种体现。在不改变原有类的前提下,动态的给原类添加一些方法和属性。
继承是面向对象语言三大特性之一,一般开发中,继承使用得比较频繁。而过多的使用继承和多层次的使用继承会造成维护困难,灵活性变得较低。而采用装饰者模式则可以在不修改原代码的情况下,通过灵活的选择组合方式,去达到新增方法和属性的目的。

举例说明装饰者模式:现在我们需要创建一个人的对象,给人穿上各种衣物。
采用传统的方式是:

传统方式

我们需要给人创建一个Person基类,人中要有衣服对象属性,裤子对象属性,鞋子对象属性,给衣服创建一个clothes基类,裤子trousers基类,鞋子shoes基类。然后通过继承方式为裤子等添加每种类型裤子的独有特性,比如裤子的长短区分的7分、9分裤,裤子造型区分的哈伦裤、直筒裤等。然后在给人穿上各种衣物时需要创建各个衣物的子类。把子类对象赋予Person类。来达到穿衣物的目的。 传统的方式只能在固有情况下去搭配,当我们需要给人穿上一件连体衣物时,那么我们就不需要裤子与鞋子对象属性,但这两个属性已经是Person类固有的,不能减少。而衣服的对象属性在原本创建的时候是按照上半身定位的,并没有考虑连体衣物这一类。这个时候就需要重新去修改Person这个类的属性。或者用继承的方式去写一个专门穿连体衣的这一类人。但这样就显得不灵活。

采用装饰者模式的方式是:

装饰者模式

我们只要创建一个Person的基类,为Person增加一些额外分类(Category)。当我们需要给人穿衣服的时候,我们给Person添加衣服相关的方法属性。而不需要穿衣服时,则不用添加衣服相关的Category,Person也没有衣物相关的对象属性与方法。这样Person类就显得简单且不需要因为其他原因去修改调整,基类不动。

Category的创建使用方法

1.New File的时候选择Object-C File

选择Object-C File

2.填入Category文件名,选择Category和Category原类
选择

3.创建成功后
创建成功后

4.Person+Clothes.h文件会报错,因为Person类找不到。所以需要在Person+Clothes.h文件中导入Person类#import "Person.h"
image.png

5.需要用Category时,导入Category类即可。#import "Person+Clothes.h"
导入"Person+Clothes.h"

什么是成员变量和属性
@interface ViewController :UIViewController
{
    UIButton *selectButton;//实例变量
    int *number;//成员变量
}
@property (nonatomic, retain) UIButton *button;//属性
@end

{ }中声明的都是成员变量,所以selectButton与number是成员变量,而button则是属性。
而selectButton为实例变量,因为selectButton的类型是UIButton,它是一个OC对象。需要实例化。所以称之为实例变量。

在以前苹果的编译器都是用GCC,如果我们要声明一个属性,那么我们必须为这个属性,声明一个成员变量。

@interface ViewController :UIViewController
{
    UIButton *button;//实例变量
    int _number;//成员变量
}
@property (nonatomic, retain) UIButton *button;//属性
@end

在GCC时代,我们要添加一个button属性,那么就必须在{}中为button声明一个成员变量。

后来因为苹果为OC新增了许多特性,GCC不能很好的支持,苹果就支助Chris Lattner创建了LLVM开源库。LLVM是使用GCC作为前端来对用户程序进行语义分析产生IF(Intermidiate Format),然后LLVM使用分析结果完成代码优化和生成。在图形处理上,LLVM运行时的编译是架在OpenGL栈上的,所以OpenGL栈能够在LLVM中产出更高效率的图形代码。如果显卡足够高级,这些代码会直接扔入GPU执行,而对于一些不支持全部OpenGL特性的显卡(比如Intel GMA卡),LLVM能够把这些指令优化成高效的CPU指令,使程序依然能够正常运行。

而在LLVM时代里,我们只需要声明一个属性即可,因为LLVM库,如果发现属性没有匹配的实例变量,它将自动创建一个以下划线开头的实例变量。所以我们在代码中可以直接使用下划线开头去调用一个属性。LLVM时代不需要在对应.m文件的@implementation中声明@ synthesize,@property会告诉LLVM自动生成对应getter和setter方法。

@interface ViewController :UIViewController
{
    int _number;//成员变量
}
@property (nonatomic, retain) UIButton *button;//属性
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
   
   _button = [UIButton new];
  self->_number = 1;
}

{}中声明的成员变量不会自动生成getter和setter方法。所以成员变量不能通过点语法进行访问。成员变量可以直接调用也可以通过->语法进行访问。

为什么会将Category的时候突然说到成员变量与属性呢?
因为Category中不能添加成员变量,但可以通过关联对象的方式添加属性。

为什么Category不能添加成员变量呢?
从文章最开头的Category结构体中我们可以发现,结构体中有属性列表,有协议列表,有类方法列表,有对象方法列表,但却唯独没有成员变量列表,所以Category中不能声明成员变量。当然大家可能会想到,属性在LLVM编译器下不是会自动生成成员变量吗。但是在Category中声明的属性,不会自动生成成员变量,也不会自动生成getter和setter方法,所以在Category声明的属性需要自己实现getter和setter方法。而Category中为属性添加getter和setter方法需要通过关联对象的方式进行。

关联对象

为什么Category中为属性添加getter和setter方法需要用到关联对象呢,因为Category中不能声明成员变量,那么也就意味着不能通过下划线的方式访问变量。不能通过下划线的方式那么就只能通过点语法的方式访问,而 点语法 本身就是调用的getter与setter方法,这样就造成了死循环。为了避免死循环,我们就采用了关联对象的方式去保存与获取对象。

#import "Person+Clothes.h"
#import <objc/runtime.h>


@implementation Person (Clothes)

//衣服颜色属性所记录的关联对象key值
static const char clothesColorKey = '\0';
- (void)setClothesColor:(UIColor *)clothesColor
{
    //通过关联对象方式保存对象
    objc_setAssociatedObject(self, &clothesColorKey, clothesColor, OBJC_ASSOCIATION_RETAIN);
}

- (UIColor *)clothesColor
{
    //通过关联对象方式获取对应key值里的对象
    return objc_getAssociatedObject(self, &clothesColorKey);
}


@end

关联方法说明:

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

objc_setAssociatedObject方法说明:建立关联对象并保存
参数说明:
object和key对应objc_getAssociatedObject;
object:value所在对象的类
key:建立关联对象的储存key值,只要是一个指针就可以
value:需要和object建立关联引用对象的value;
policy:关联策略,相当于给@property添加关键字。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN,// 关联方式采用assign
    OBJC_ASSOCIATION_RETAIN_NONATOMIC,// 非原子的强引用
    OBJC_ASSOCIATION_COPY_NONATOMIC,// 非原子的copy引用
    OBJC_ASSOCIATION_RETAIN,// 原子性的强引用
    OBJC_ASSOCIATION_COPY, // 原子性的copy引用   
};
/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

objc_getAssociatedObject方法说明:这个函数先根据对象地址在 AssociationsHashMap 中查找其对应的 ObjectAssociationMap 对象,如果能找到则进一步根据 key 在 ObjectAssociationMap 对象中查找这个 key 所对应的关联结构 ObjcAssociation ,如果能找到则返回 ObjcAssociation 对象的 value 值,否则返回 nil;Map跟字典的键值对关系是一样的。采用哈希表方式存储。
objc_getAssociatedObject有两个参数,第一个参数为从该object中获取关联对象,第二个参数为想要获取关联对象的key;

/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

objc_removeAssociatedObjects:删除所有关联对象
object:需要删除所关联对象的对象

无论在 MRC 下还是 ARC 下关联对象都不需要 dealloc 的时候释放,被关联的对象在生命周期内要比对象本身释放的时间晚很多,它们会在被 NSObject -dealloc 调用的 object_dispose()方法中释放。


Extension

Extension称为扩展、延展、匿名分类。Extension和Category几乎完全是两个东西。Extension不但可以声明方法,还可以声明属性、成员变量。Extension一般用于声明私有方法,私有属性,私有成员变量。
但是Extension只存在于一个.h文件中,并寄生于一个类的.m文件中。它就相当于把.m文件中@ interface 里的代码提取出来单独放在一个.h文件中,但是类文件外部不能访问Extension文件声明的属性、变量与方法。Extension是在编译期就已经决定好的部分,它就是类的一部分,在编译期与.h里的@interface和.m里的@implement一起形成一个完整的类。Extension一般用来隐藏类的私有信息,你必须先有一个类的源码才能添加这个类的Extension,所以我们无法给系统的类添加Extension。

Extension的创建与使用

1.New File的时候选择Object-C File


选择Object-C File

2.填入Extension文件名,选择Extension和需要扩展Extension的类


选择Extension

3.生成后只有一个.h文件
生成的Extension的.h文件

4.在文件中写入你想要私有的方法属性成员变量

#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person ()
{
    int chest_Circumference;//胸围
    int waist_Circumference;//腰围
    int hip_Circumference;//腿围
}

//年龄
@property (nonatomic, assign) int age;
//手机号
@property (nonatomic, copy) NSString *phone;

- (NSMutableDictionary *)getUserCircumference;

@end

NS_ASSUME_NONNULL_END

5.然后在需要的地方引入Extension的.h文件

#import "Person+Private.h"

@implementation Person

@end

Extension的内容相对较少,因为Extension用得非常少。可能个人对Extension的作用理解还不够深入,目前项目中非常少使用。

由上面的Category与Extension的内容可知,前者是在运行期决定,后者是在编译期就决定了。注定了Category是无法添加实例变量,而Extension可以添加。看过我iOS-APP的启动流程和生命周期读者应该知道,在Category加载之前,类的内存布局已经初始化确定好,而在后面的运行期去添加实例变量就会破坏类的内存布局,这对编译型语言而言是不可取的。


扩展知识点

GCC(GNU Compiler Collection,GNU编译器套装):是一套由 GNU 开发的编程语言编译器。GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言。GCC 很快地扩展,变得可处理 C++。之后也变得可处理 Fortran、Pascal、Objective-C、Java, 以及 Ada与其他语言。

LLVM:是一个开源编译器框架,这个库提供了与编译器相关的支持,能够进行程序语言的编译期优化、链接优化、在线编译优化、代码生成。而Clang编译器就是基于LLVM的一个编译器。

Clang编译器:是LLVM编译器工具集的一个用于编译C、C++、Objective-C的前端。它的编译速度较快、内存占用较小、设计更简单清晰、扩展性强。并兼容GCC编译器。是苹果为了取代GCC而产生的编译器。

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

推荐阅读更多精彩内容

  • 一、概述 Category是Objective-C 2.0之后添加的语言特性,Category又叫分类、类别、类目...
    Imkata阅读 955评论 5 5
  • 一 类别的简介 在开发中有时会用到Category,类别有三个作用: (1)可以将类的实现分散到多个不同文件或多个...
    々莫等闲々阅读 435评论 0 0
  • 写在前面 最近终于抽出时间来对这篇文章进行更改了,在这个过程中多谢大家的指导和意见,当时写这篇文章的时候原本是想记...
    劉光軍_MVP阅读 13,363评论 42 128
  • 一、分类的使用注意事项: 1、分类只能增加方法,不能增加成员变量。 原因: Category是运行时决议,因为在...
    GSChan阅读 413评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,495评论 16 22