知 识 文 / 超 人
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
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而产生的编译器。
网友评论