分类(category)
分类不是类
- 动态给类添加方法;
- 或把类中方法按照逻辑分开,优化代码结构;
- 给类添加方法不会影响子类;
- 把Framework私有方法公开;
- 模拟多继承;(protocol 也是解决多继承问题)
使用
Test类的.h和.m
- 基本使用
#import <Foundation/Foundation.h>
@interface Test : NSObject
@property (nonatomic, copy) NSString *name;
-(void)testMethod;
@end
#import "Test.h"
#import "Test+Category.h"
@implementation Test
-(void)testMethod{
[self categoryMethod];
}
@end
Test分类的.h 和.m
.h文件
Test 的分类
#import "Test.h"
//格式 类名 (分类名) 分类名一般和分类的功能相关
@interface Test (Category)
//分类中方法
-(void)categoryMethod;
@end
.m 文件
#import "Test+Category.h"
@implementation Test (Category)
-(void)categoryMethod{
self.name = @"name";
[self testMethod];
}
@end
类中引入分类的头文件可以调用分类中的方法, 分类中可以使用类中的属性和方法(仅能使用.h中的属性和方法)。当然可以通过- (id)performSelector:(SEL)aSelector;调用私有方法;
- 添加"成员变量"
分类中可以通过runtime来添加绑定"成员变量"
static const char *K_MEMBER_VARIABLE = "KMemberVriable";//命名规则 特殊前缀+属性名
-(void)setMemberVribale:(NSString *)memberVribale{
objc_setAssociatedObject(self, K_MEMBER_VARIABLE, memberVribale, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)memberVribale{
return objc_getAssociatedObject(self, K_MEMBER_VARIABLE);
}
通过
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
OBJC_ASSOCIATION_ASSIGN 相当于 @property (nonatomic, weak)
OBJC_ASSOCIATION_RETAIN_NONATOMIC 相当于 @property (nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC 相当于 @property (nonatomic, copy)
OBJC_ASSOCIATION_RETAIN 相当于 @property (atomic, strong)
OBJC_ASSOCIATION_COPY 相当于 @property (atomic, copy)
根据对象的特点去选择
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
//第一个参数 和那个对象关联, 第二个参数 Key(通过Key进行存取), 第三个参数 存入的值,
//第四个参数 关联策略
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
//第一个参数 和那个对象关联, 第二个参数 Key(通过Key进行存取)
以上就是基本使用。
Category源码
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;//实例方法
struct method_list_t *classMethods; //类方法
struct protocol_list_t *protocols; //代理
struct property_list_t *instanceProperties; //属性 注意property_list_t和ivar_list_t(成员变量)不同
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
...
};
可以看到在category的结构体中,包含我们常用的实例方法,类方法,代理和属性,并没有包括成员变量列表,这也解释了,为什么不能使用成员变量;
分类中的属性和方法是怎么添加到类中的呢?
runtime被加载后第一个执行的方法是_objc_init()
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
查看里面面的map_images -> map_images_nolock -> _read_images ,在_read_images function中的一段
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
源码注释里写的很清楚,
- 注册category和category的target class 对应function addUnattachedCategoryForClass
- 重新创建target class 的method remethodizeClass
重点在remethodizeClass
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
在看 attachCategories function
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
上面代码按照顺序看很容易理解,把category的methodList, proplist, protolist 全部添加到class_rw_t的对应的列表中。
具体举例 methods.attachLists function
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
从
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
可知 把category中的method放到了class的方法之前 ,也就是说method没有被替换只是把category放到的target class之前,也就是所谓的覆盖。想调用被'被覆盖的方法',通过runtime中methodList 倒着查表就可以调用。
扩展(Extension)
Extension是在编译期决议的,伴随类的生命周期,一般用来隐藏类的信息包括私有属性,成员变量和方法。你必须在类的.m文件中才能添加Extension。
@interface Test ()
//私有属性
@property (nonatomic, copy) NSString *privateName;
//私有方法
-(void)privateMethod;
@end
对比:
属性 | Method | 优 | 劣 | |
---|---|---|---|---|
Category | 分类中属性必须通过setter/getter实现。可以通过关联对象的形式保存。 | 可以添加方法,但是方法会覆盖类的方法,如果有多个分类中有相同的方法会调用最后一个加载的方法 | 可以通过方法功能不同将类拆分多个分类,方便使用和维护 可以向类中添加方法,但又不会影响该类的子类 使用方便,不用通过继承方式给类添加新的功能 把Framework私有方法公开 模拟多继承;(protocol 也是解决多继承问题) |
不能给已有类添加成员变量 分类中可以有相同的方法,可能会造成方法调用错误(覆盖类中的方法,调用最后一个加载的分类) (方法名相同问题可以通过不同的分类对方法添加前缀解决) |
Extension | 可以成员变量 保存到对象的成员变量列表中,私有变量 | 可以添加方法,方法会覆盖父类中的方法,不能存在相同的方法,私有方法 | 封装私有变量和方法,隐藏不想给外部的变量和方法 |