category
分类是可以为已有类添加方法,不同于继承可以再不影响现有类的情况下添加方法,也可以重写现有类函数。
作用
- 可以将类的实现分散到多个不同文件或多个不同框架中,方便代码管理。也可以对框架提供类的扩展
- 对私有方法的前向引用:如果其他类中的方法未实现,在你访问其他类的私有方法时编译器报错这时使用类别,在类别中声明这些方法(不必提供方法实现),编译器就不会再产生警告
- 向对象添加非正式协议:创建一个NSObject的类别称为“创建一个非正式协议”,因为可以作为任何类的委托对象使用。有两个方面的局限性: (1)无法向类中添加新的实例变量,类别没有位置容纳实例变量。(2)名称冲突,即当类别中的方法与原始类方法名称冲突时,类别具有更高的优先级。类别方法将完全取代初始方法从而无法再使用初始方法。
使用
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CategoryTest : NSObject
@end
NS_ASSUME_NONNULL_END
#import "CategoryTest.h"
@implementation CategoryTest
@end
#import "CategoryTest.h"
NS_ASSUME_NONNULL_BEGIN
@interface CategoryTest (Category_test)
-(void)test1;
+(void)test2;
@end
NS_ASSUME_NONNULL_END
#import "CategoryTest+Category_test.h"
@implementation CategoryTest (Category_test)
-(void)test1{
NSLog(@"test1");
}
+(void)test2{
NSLog(@"test2");
}
@end
category能否添加属性?
答:正常来说category并不能添加属性,因为一个属性包含set get 和成员变量,但是category没有控件存储成员变量所以不能正常添加属性。直接添加属性在调用时会崩溃,需要在分类中实现set get 方法。
@interface CategoryTest (Category_test)
@property(nonatomic)NSString * str;
@end
- (void)setStr:(NSString *)str{
}
-(NSString *)str{
return nil;
}
在实现set get 方法需要有变量存储属性值,这时候就需要用到runtime的关联方法。
objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。这两个方法可以让一个对象和另一个对象关联,就是说一个对象可以保持对另一个对象的引用,并获取那个对象。
/*
runtime 在底层维护一个关联关系,通过objc_setAssociatedObject将对象和对象通过key关联起来。objc_getAssociatedObject通过key可以获取相关联对象。通过objc_setAssociatedObject最后一个参数可以看出关联方法本身也是可以设置类似属性参数的。
*/
#import <objc/runtime.h>//导入头文件
static NSString *strKey = @"strKey";
- (void)setStr:(NSString *)str{
objc_setAssociatedObject(self, &strKey, str, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)str{
NSString *res = objc_getAssociatedObject(self, &strKey);
return res;
}
结构
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;
通过上面知道category的底层结构,在结构体里面并没有存储成员变量位置,所以也解释正常情况下为什么分类不能常规添加属性。当分类中重写类的方法时候会覆盖原有方法,应该在运行时,系统会将类所有的分类方法合并成到一起,分类的方法在原方法之前,所以调用的时候会出现分类方法覆盖原方法。注:不同分类都重写同一个类方法,可以通过调整分类文件的编译顺序,调整方法执行那个分类的
category 与 Extension
Extension是特殊的category,它不能像category脱离类本身生成单独的.m文件,但是可以单独创建生成 .h 文件。可以为类添加方法,协议,属性,成员等,一般写在.m中用来隐藏类的私有消息,私有成员。
- 在编译器决议,是类的一部分,在编译器和头文件的@interface和实现文件里的@implement一起形成了一个完整的类。
- Extension一般用来隐藏类的私有消息,你必须有一个类的源码才能添加一个类的Extension,所以对于系统一些类,如NSString,就无法添加类扩展
写法:
#import "CategoryTest.h"
@interface CategoryTest()
@end//Extension
@implementation CategoryTest
@end
对比category:
- Extension 在编译期决议,category是在运行期决议。
- Extension 需要在源码基础上添加,category不需要源码
- Extension可以添加成员在编译时是类的一部分,category不能添加成员可以在运行期关联对象
- Extension通常做一些类的私有化,category做类的拆分,函数添加等。
Category延伸
1.在类的 +load 方法中可以调用Category里声明的方法吗?
可以,因为附加Category到类的工作会先于 +load 方法的执行。
2.类和Category的 +load 方法调用顺序是什么样的?
+load 的执行顺序是:先类,后category。而各个category的 +load执行顺序是根据编译顺序决定的。
3.关联对象存在哪?如何存储?对象销毁时候如何处理关联对象?
所有的关联对象都由AssociationsManager管理,AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。
相当于把所有对象的关联对象都存在一个全局map面。而map的key是这个对象的指针地址,value一个AssociationsHashMap,里面保存了关联对象的键值对。
runtime的销毁对象函数 objc_destructInstance 里面会判断这个对象有没有关联对象,如果有,会调用 object_remove_assocations 做关联对象的清理工作。
网友评论