因为Category在iOS平时的开发中用的比较多,所以本文从各个方面介绍一下Category,很多地方都是自己的理解,欢迎各位看到的同学批评指正,多多交流。
一、Category作用
- 为已存在的类添加方法;对类进行了很好的扩展,应对变化的需求。
- 可以把一个类的实现分散到多个文件中,使得每个文件不至于庞大,而且可以聚集同一逻辑的代码在一个文件中。同时,也可以按需加载方法。
- 同一个类可以由多个人共同完成。
二、为什么category不能添加属性或者说是成员变量
这个问题从runtime的角度进行分析。
下图是objc_class结构体:
classOfRuntime.png
不详细介绍每一个字段的含义了,主要介绍与category相关的部分:
在上面的objc_class结构体中,ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。类似的是protocol,因为它是编译器处理的,所以可以添加变量。category是在运行时处理的。
三、怎么样可以实现添加属性或者说添加成员变量
上面说了category中不可能添加成员变量或属性。因为系统无法合成实现属性所需的实例变量,所以category中也无法添加@property。但有时候确实需要添加属性,那有没有其他办法,可以不改变对象的内存结构,变相的给对象增加成员变量。
我们可以Runtime的objc_getAssociatedObject和objc_setAssociatedObject方法来模拟属性的get和set方法,用关联对象来模拟实例变量,这样就有了@property的三要素,只是跟@property的实现机制是完全不一样的。
有两种方式,第一种代码更简洁:
.h文件:
@property (nonatomic) NSString Id;
.m文件,要#import <objc/runtime.h>
- (NSString *)Id
{
return objc_getAssociatedObject(self, @selector(Id));
}
- (void)setId:(NSString *)Id
{
objc_setAssociatedObject(self, @selector(Id), Id, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
第二种:
static void *UIViewPoint =@"UIViewPoint";
@implementation UIView (Point)
@dynamic pointView;
- (void)setPointView:(UIView *)pointView {
objc_setAssociatedObject(self, UIViewPoint, pointView,OBJC_ASSOCIATION_RETAIN);
}
- (UIView *)pointView {
return objc_getAssociatedObject(self, UIViewPoint);
}
这两个方法的原型如下:
id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
@selector(Id) 是参数中的 key,方法二中使用了静态指针 static void * 类型的参数来代替,第一种方法因为省略了声明参数的代码,并且能很好地保证 key 的唯一性,所以更简洁。
OBJC_ASSOCIATION_RETAIN_NONATOMIC 从字面意思就能猜到它是和属性的修饰符是对应的。对应关系见如下的定义:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
当属性是基本数据类型的时候,可能只是需要添加get和set方法,不需要实例变量的时候,可以在category中这样添加属性,这种方式,同样没有改变对象的内存结构。
@property (nonatomic) CGFloat left;
@property (nonatomic) CGFloat right;
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)right {
return self.left + self.width;
}
- (void)setRight:(CGFloat)right {
if(right == self.right){
return;
}
CGRect frame = self.frame;
frame.origin.x = right - frame.size.width;
self.frame = frame;
}
我查了很多资料,很多人认为category可以添加属性,但是不可以添加成员变量,我觉得这种说法是不严谨的,所以写了这篇博客,来总结一下。如果有理解的不对的地方,希望看到的朋友指正,大家多多交流。
网友评论
category 是基于运行时的:
typedef struct category_t {
const char *name; //类的名字
classref_t cls; //类
struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表
struct method_list_t *classMethods; //category中所有添加的类方法的列表
struct protocol_list_t *protocols; //category实现的所有协议的列表
struct property_list_t *instanceProperties; //category中添加的所有属性
} category_t;
结构体中并没有成员变量这个list,所以无法在categoty添加成员变量,添加属性是可以的,但是不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法,但你可以使用运行时实现关联对象并可以引用。
runtime源码下载:
https://opensource.apple.com/tarballs/objc4/
比如680版本就是Objective-C 2.0
category 才是基于运行时的,结构体如下:
typedef struct category_t {
const char *name; //类的名字
classref_t cls; //类
struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表
struct method_list_t *classMethods; //category中所有添加的类方法的列表
struct protocol_list_t *protocols; //category实现的所有协议的列表
struct property_list_t *instanceProperties; //category中添加的所有属性
} category_t;