Category不允许为已有的类添加新的成员变量,实际上允许添加属性的,因为即使你添加了@property (nonatomic,xxxx) ,它既不会生成实例变量,也不会生成setter、getter方法,所以你添加了也无法使用。
那为什么category不能添加成员变量?
看了一下网上的说法,了解一下,如下
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
typedef struct objc_class *Class;
objc_class结构体的定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
在上面的objc_class结构体中,
ivars是objc_ivar_list(成员变量列表)指针;
methodLists是指向objc_method_list指针的指针。
主要是这句话:在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。
下面了做个Demo测试一下:
-
先创建一个NSArray的Category,准备给它加一个name的属性(当然这样做法是毫无意义的,它just只是举个例子啊),上段测试代码:
.h中声明属性 -
然后在.m中调用
运行到aa.name就直接炸了,找不到setter方法,同样的getter方法就不测试了都一样。
要想实现,我们首先需要自己去添加setter、getter方法,直接在NSArray+Property.m文件里加就可以了,但是要真正添加可以使用的属性,还需要利用Runtime来关联对象,我们在setter方法里关联一个对象,在getter方法里获取对应key关联的对象。下面看实现:
.h
@interface NSArray (Property)
@property (nonatomic,copy) NSString * name;
-(void)testMethod;
@end
.m
#import "NSArray+Property.h"
#import <objc/runtime.h>
//定义常量 必须是char类型,类似字典的key值,根据key存取值
static char * KEY = "key";
@implementation NSArray (Property)
-(void)setName:(NSString *)name{
/*
objc_AssociationPolicy参数使用的策略:
OBJC_ASSOCIATION_ASSIGN; //assign策略
OBJC_ASSOCIATION_COPY_NONATOMIC; //copy策略
OBJC_ASSOCIATION_RETAIN_NONATOMIC; // retain策略
OBJC_ASSOCIATION_RETAIN;
OBJC_ASSOCIATION_COPY;
*/
/*
关联方法:
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
参数:
* id object 给哪个对象的属性赋值
const void *key 属性对应的key
id value 设置属性值为value
objc_AssociationPolicy policy 使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择NONATOMIC
*/
objc_setAssociatedObject(self, KEY, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString*)name{
return objc_getAssociatedObject(self, KEY);
}
-(void)testMethod{
NSLog(@"方法随便写");
}
@end
ViewController中测试
NSArray * aa = [[NSArray alloc]initWithObjects:@"1",@"2", nil];
aa.name = @"xixi";
NSLog(@"aa有一个的name属性被赋值了,查看name值为 : %@",aa.name);
[aa testMethod];
**打印结果**
输出: aa有一个的name属性被赋值了,查看name值为 : xixi
输出: 方法随便写
上面代码如果你不主动实现setter、getter方法,并用Runtime去关联的话是会报错找不到setter、getter方法,可以自己去试试。Runtime机制其实还有很多方便用处,请查看 Runtime的常用方式及实现
哦,还有点儿要注意了,如果有两个分类,他们都实现了相同的方法,如何判断谁先执行?
1、在本类和分类有相同的方法时,优先调用分类的方法再调用本类的方法。
2、分类之间的执行顺序,可以通过targets -> Build Phases -> Complie Source
进行调节,注意执行顺序是从上到下的。(有两个相同方法名的分类)
再追加点儿对类扩展(extension)说明:
extension和Category不同,类扩展即可以声明成员变量又可以声明方法,但是没有像Category有.m文件。extension听上去很复杂,但其实我们很早就认识它了。
你记得继承自UIViewController的ViewController和继承自NSObject的类有什么不同么?
继承自UIViewController的ViewController类有这个
@interface ViewController() //这个玩意儿,就是类扩展的写法
@end
类扩展可以定义在.m文件中,这种扩展方式中定义的变量都是私有的,也可以定义在.h文件中,这样定义的代码就是共有的,类扩展在.m文件中声明私有方法是非常好的方式。
参考文章:Objective-C的Category与关联对象实现原理
网友评论