Category详解及和Extension的区别
Category为什么不能添加实例变量,但是可以添加方法?
关联对象
-
Category: 类别,分类
1. 类别是一种为现有的类添加新方法的方式 2. 可以添加属性@property,但不会生成成员变量,也不会生成setter和getter方法实现,但是可以通过runtime关联对象生成setter和getter方法实现 # 3. 是在 运行时决议 的
. 优点
将类的实现代码分散到不同文件或框架 可以减少单个文件的体积 可以把不同的功能组织到不同的Category中 可以按需加载需要相关功能的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;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
1. Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议等信息
2. 在程序运行的时候,runtime会将Category的数据合并到类信息中(类对象、元类对象中)
. Category加载处理过程
1. 通过runtime加载该类的所有category数据
2. 所有category的方法、属性、协议数据合并至一个大数组中,后参与编译的category数据会在数组前面
3. 将合并后的分类数据(方法、属性、协议),插入至原来数据前
. Q
- 如果多个category中存在同名的方法,运行时调用那个方法
由编译器决定,最后一个参与编译的方法会被调用
-
category中的方法会覆盖原有方法?
1.不会覆盖, 2.category加载完成后,类的方法列表中会有两个该方法,只是category的方法被放到了列表前面,运行时查找方法的时候按顺序查找,只要找到对应名字的方法,就会停止 3.这样看起来是原来方法被覆盖了
-
为什么Category不能添加成员变量, 而可以添加方法
Category为什么不能添加实例变量,但是可以添加方法?1. 成员变量时属于类实例的,而方法不属于类实例 2. 分类不能改变类实例的内存结构
从runtime源码出发,Class提供的方法列表存在于
class_rw_t
中,提供了读写方法;成员变量列表存在于class_ro_t
中,只提供了读方法Objective-C中类的定义
typedef struct objc_class *Class;
objc_class结构体
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
OBJC2中objc_class的实际定义
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
...
public:
/// 方法列表,提供了读写方法
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData) {
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
/// 成员变量列表,只提供了读方法
const class_ro_t *safe_ro() {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
...
}
-
分类可以为类添加属性吗?属性和成员变量的区别是什么
可以添加属性 成员变量是属于类实例,存在于类的内存结构中, 属性是属于语法糖,会自动为该类生成成员变量及setter和getter方法;如果类声明了属性,以及自己实现了setter和getter方法,系统(编译器)就不会自动生成成员变量,如果要关联成员变量,需使用: @synthesize name = _name;
-
属性的实质
1. 属性就是OC提供的语法糖,自动生成成员变量,并根据不同语义为成员变量自动生成读写方法 2. 可以自己实现读写方法,如果读写方法全部由自己实现,编译器不会自动生成成员变量(可利用@synthesize name = _name,强制编译器生成成员变量) 3. category可以声明属性,不过其针对的是属性的读取方法,不会生成成员变量 4. protocol可以声明属性,不过遵循该协议的类,不一定要拥有该属性,只要拥有setter和getter方法即可
-
Extension
1. 匿名分类 2. 可以给类添加属性和方法 3. ~编译时决议~, 是类的一部分 4. 伴随类产生,伴随类消失 5. 可以放在.h和.m文件中,但是方法必须在@impletion中实现 6. 一般 用来隐藏类的私有信息,必须拥有类的源码才能添加,对于系统类就无法添加
objc_setAssociatedObject
关联对象原理探究
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) {
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
// 判断参数有效性
if (!object && !value) return;
// 如果该对象禁止关联对象,则返回
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 找到object对象指针
DisguisedPtr disguised{(objc_object *)object};
// 存储着关联策略policy和关联对象的值value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
// value引用(引用计数加一)
association.acquireValue();
{
// 关联对象并不是存储在关联对象本身内存中,而是存储在全局统一的一个容器中
// 由 AssociationsManager 管理并在它维护的一个单例 Hash 表 AssociationsHashMap 中存储
// 线程安全
AssociationsManager manager;
// 获取到容器中存储的哈希表
AssociationsHashMap &associations(manager.get());
// value存在
if (value) {
// 从associations中object关联对象的结果迭代器
// try_emplace的用法为,如果key值存在,直接返回value;若不存在,添加一个key&value对
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
// 如果map中有值,标识为存在关联对象
object->setHasAssociatedObjects();
}
/* establish or replace the association */
// 获取关联对象的关联值map
auto &refs = refs_result.first->second;
// 从refs中获取object的关联对象键值为key的结果迭代器
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
// 若存在旧值,替换为新值
association.swap(result.first->second);
}
} else { // value不存在(nil),删除关联关系
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
// value释放(引用计数-1)
association.releaseHeldValue();
}
-
objc_getAssociatedObject
原理探究
id
_object_get_associative_reference(id object, const void *key)
{
// 关联对象
ObjcAssociation association{};
{
// 关联对象全局容器
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
// object关联对象的迭代器
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
// object关联对象map
ObjectAssociationMap &refs = i->second;
// object关联对象为key的迭代器
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
// 判断value 语义,如果是retain,引用计数加
association.retainReturnedValue();
}
}
}
// 返回值
return association.autoreleaseReturnedValue();
}
-
objc_removeAssociatedObjects
原理探究
void _object_remove_assocations(id object)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
// release everything (outside of the lock).
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
该方法一般用不到,如果要删除某个关联属性,直接将其置位nil即可