一、类扩展分析
1.1 category 与 extension
category
和extension
开发中经常遇到,他们的区别对比简单总结下:
category(类别、分类)
- 专门用来给类添加方法。
- 不能给类添加成员属性,添加了成员变量,也无法取到。
- 可以通过
runtime
给分类添加属性。 - 分类中用
@property
定义变量,只会生成变量的geter & setter
方法的声明,不能生成方法的实现和带下划线的成员变量。
extension(扩展)
- 可以称为特殊分类(匿名分类)。
- 可以给类添加成员属性,但是是私有变量。
- 可以给类添加方法,也是私有方法。
1.2 extension 底层实现分析
对于extension
我们一般常用的方式是将extension
与类的实现放在一起都放在.m
中:
也可以创建单独的extension
:
这个时候就只生成了对应的
.h
文件。
-
extension
要在类的声明之后,实现之前。
@interface HPObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *age;
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface HPObject ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, copy) NSString *ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation HPObject
- (void)instanceMethod {
NSLog(@"%s",__func__);
}
+ (void)classMethod {
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod {
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod {
NSLog(@"%s",__func__);
}
@end
将上面的代码转成.cpp
查看对应的实现:
可以看到
extension
的内容与类的内容合并到了一起。并且没有搜到与category_t
类似的extension_t
。
在realizeClassWithoutSwift
中条件断点源码验证:
可以看到
ro
中已经有了extension
的方法了(包括setter & getter
)。
- 分类会影响到类的编译和加载。
- 类扩展不会影响类的编译和加载。
- 类扩展可以写多个,最后也都是合并进主类了,不影响。(写在同一个文件或者不同文件都可以)
- 类扩展必须与主类在一起,最好定义在主类
.m
中,单独定义还是要导入主类.m
中。(单独写意义不大)
如果
extension
是单独的文件声明的,需要将.h
文件导入主类的.m
中(分类不行),否则不会将extension
中定义的属性和成员变量加入类中(方法能加入是因为方法本身是实现在主类中的)。所以单独声明定义extension
没有任何意义,只是能这样做而已。
二、关联对象
分类中添加属性后会报警告:
Property 'cat_name' requires method 'cat_name' to be defined - use @dynamic or provide a method implementation in this category
本质上是因为分类中用
@property
定义变量,只会生成变量的geter & setter
方法的声明,不能生成方法的实现和带下划线的成员变量。本质上是因为没有成员变量,getter & setter
无法进行存值与取值。
这个时候可以通过关联对象给类添加属性:
- (void)setCat_name:(NSString *)cat_name {
//对象、标识符、value、策略
return objc_setAssociatedObject(self, "cat_name", cat_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cat_name {
return objc_getAssociatedObject(self, "cat_name");
}
2.1 关联对象存储值
那么关联对象是怎么实现的呢?
2.1.1 objc_setAssociatedObject
objc_setAssociatedObject
的源码实现:
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
内部直接调用了_object_set_associative_reference
。
818
版本的实现与779
版本不同,779
版本中objc_setAssociatedObject
的实现调用的是SetAssocHook.get()
2.1.2 _object_set_associative_reference
源码解读:
//关联对象 存储 object -> cat_name -> value - policy
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
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
DisguisedPtr disguised{(objc_object *)object};
//包装 {policy, value} 为 ObjcAssociation
ObjcAssociation association{policy, value};
//根据 policy 对 value 进行操作
association.acquireValue();
bool isFirstAssociation = false;
{
//manager 不是单例,调用构造函数创建 manager,内部进行了加锁和解锁。
AssociationsManager manager;
//AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap。它是在`map_images`的时候初始化。
AssociationsHashMap &associations(manager.get());
if (value) {//有值
//创建/插入bucket
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {//第一次,也就是插入 bucket 的时候 second 为 true
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
//这个时候 association 还没有存
auto &refs = refs_result.first->second;
//相当于第二层
//这时候的key就是成员变量的key,将 association 插入桶中。有值的情况下没有插入,没有值的情况下才插入。
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {//second为false 证明 LookupBucketFor 找到了。
// result.first->second 为 association。将旧的值替换为新的。association 变为旧值。
association.swap(result.first->second);
}
} else {//没有值,进行清空处理。
//找到对象的ref
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
//找到对应key的 association
auto it = refs.find(key);
if (it != refs.end()) {
//交换值,也就是内存中存储的it修改为nil,association修改为之前的值。
association.swap(it->second);
//擦除内存中it数据。
refs.erase(it);
if (refs.size() == 0) {
//如果对象的ref没有关联对象了,则整个对象擦除。
associations.erase(refs_it);
}
}
}
}
}
//只在第一次标记对象是否有关联对象
if (isFirstAssociation)
object->setHasAssociatedObjects();
//释放旧值。
association.releaseHeldValue();
}
- 对象,值以及类是否禁用关联对象的逻辑判断。
- 将对象包装成
DisguisedPtr
统一类型。 - 将策略和值包装为
ObjcAssociation
类型并且根据策略对值进行操作。
inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
AssociationsManager manager
调用构造函数创建manager
,内部进行了加锁和解锁。本身不是单例。AssociationsHashMap
通过manager
获取,本质上可以认为是一个单例。是在map_images
的时候进行的初始化。-
value
有值的情况:-
try_emplace
创建插入bucket
。在这一次将object
对应的map
插入单例map
中。 - 根据
refs_result.second
判断是否是第一次插入。如果原先已经存在则second
为false
,否则为true
。isFirstAssociation
进行第一次标记。 -
refs_result.first->second
获取到的相当于是object
对应的map
,这个时候map
中还没有存储构造的association
,也就是value-policy
。 - 再次
try_emplace
将key
与association
传入就相当于要存储关联对象到map
了(内层)。- 值存在的时候不会存储进去与上面一样通过
second
返回结果。为false
证明之前有值。会走到association.swap
逻辑。
- 值存在的时候不会存储进去与上面一样通过
-
association.swap
是为了将旧值交换出来,存储新值进去在后续做释放旧值的存在。
可以通过赋值两次进行验证:
-
-
value
没有值的情况:-
associations
中找到object
对应的map
存为refs_it
。 -
map
不为结束标记则取refs_it->second
,根据key
取association
存入it
。 - 不为结束标记则对
it
进行交换,也就是map
中替换为{policy,nil}
,将原先的值存入association
。然后擦除map
中的值。 - 如果该对象已经没有关联对象了,则擦出该对象的关联对象
map
。擦除相当于标记位墓碑(erase
)。
-
根据
isFirstAssociation
,标记对象是否有关联对象。通过
association
释放旧值。
2.1.2.1 disguised 与 association 验证
2.1.2.2 refs_result 结构
refs_result
结构:
格式化后结构如下:
类型:
std::pair<
objc::DenseMapIterator<
DisguisedPtr,
objc::DenseMap<
const void *,
objc::ObjcAssociation,
objc::DenseMapValueInfo,
objc::DenseMapInfo,
objc::detail::DenseMapPair<
const void *,
objc::ObjcAssociation
>
>,
objc::DenseMapValueInfo<
objc::DenseMap<
const void *,
objc::ObjcAssociation,
objc::DenseMapValueInfo,
objc::DenseMapInfo,
objc::detail::DenseMapPair<
const void *,
objc::ObjcAssociation
>
>
>,
objc::DenseMapInfo<
DisguisedPtr
>,
objc::detail::DenseMapPair<
DisguisedPtr,
objc::DenseMap<
const void *,
objc::ObjcAssociation,
objc::DenseMapValueInfo,
objc::DenseMapInfo,
objc::detail::DenseMapPair<
const void *,
objc::ObjcAssociation
>
>
>,
false
>,
bool
>
值:
{
first = {
Ptr = 0x0000000101016c70
End = 0x0000000101016cb0
}
second = true
}
first
对应objc::DenseMapIterator
,second
对应最后一个bool
值也就是true
。
2.1.3 AssociationsManager
class AssociationsManager {
using Storage = ExplicitInitDenseMap, ObjectAssociationMap>;
//静态变量,声明在 AssociationsManager 中是一个全局变量,只能 AssociationsManager 调用。 相当于单例。
static Storage _mapStorage;
public:
//析构和构造函数,内部进行了加锁和解锁。
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
//设置和获取 AssociationsHashMap,由于 _mapStorage 是静态的,所以 AssociationsHashMap 也就相当于单例
AssociationsHashMap &get() {
return _mapStorage.get();
}
//类方法,在 _objc_associations_init 调用。也就是`map_images`的时候。
static void init() {
_mapStorage.init();
}
};
-
_mapStorage
是一个静态变量,声明在 AssociationsManager 中是一个全局变量,只能 AssociationsManager 调用。 相当于单例。 -
AssociationsManager
在构造和析构函数中进行加解锁操作。 -
AssociationsHashMap
通过AssociationsManager
的get
方法获取。 -
AssociationsHashMap
在AssociationsManager
的类方法init
中初始化。
2.1.3.1 AssociationsManager 模仿验证:
struct HPObjectS {
HPObjectS() {
printf("Creat HPObjectS\n");
}
~HPObjectS() {
printf("release HPObjectS\n");
}
};
调用:
int main(int argc, const char * argv[]) {
@autoreleasepool {
HPObjectS s;
}
return 0;
}
输出:
Creat HPObjectS
release HPObjectS
出了作用域就被释放了。所以AssociationsManager
不是单例,内部进行了加解锁操作。
验证
c++
的构造和析构函数需要修改.m
文件为.mm
或者修改type
为c++
:
2.1.4 AssociationsHashMap
typedef DenseMap, ObjectAssociationMap> AssociationsHashMap;
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
-
AssociationsHashMap
是通过AssociationsManager
的get()
方法获取的。 - 初始化时在
AssociationsManager
的类方法init()
中。(这里相当于是总表的初始化)
2.1.4.1 AssociationsHashMap 单例验证
在_object_set_associative_reference
中添加如下测试代码(也可以多次调用进入进行测试):
//manager 不是单例,调用构造函数创建 manager,内部进行了加锁和解锁。
AssociationsManager manager;
//AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap。它是在`map_images`的时候初始化。
AssociationsHashMap &associations(manager.get());
//测试代码
printf("manager: %p, associations: %p\n",&manager,&associations);
AssociationsManager manager1;
AssociationsHashMap &associations1(manager1.get());
printf("manager1: %p, associations1: %p\n",&manager1,&associations1);
AssociationsManager manager2;
AssociationsHashMap &associations2(manager2.get());
printf("manager2: %p, associations2: %p\n",&manager2,&associations2);
//end
输出:
manager: 0x7ffeefbff460, associations: 0x10035e188
manager1: 0x7ffeefbff448, associations1: 0x10035e188
manager2: 0x7ffeefbff438, associations2: 0x10035e188
-
AssociationsHashMap
多次输出地址一致,为单例对象。 -
AssociationsManager
多次输出地址不一致,普通对象,只是内部对静态变量有操作而已。
由于
AssociationsManager
中有加锁解锁操作,先暂时注释掉加锁解锁操作,否则会造成死锁(锁递归):
2.1.4.2 AssociationsHashMap 初始化
上面的代码分析可以看到在AssociationsManager
的类方法init()
中有对_mapStorage
的初始化:
static void init() {
_mapStorage.init();
}
那么初始化是在什么时机调用的呢?既然是单例那么应该只调用一次,直接在init
中打断点有如下调用堆栈:
libobjc.A.dylib`objc::AssociationsManager::init() at objc-references.mm:124:21
libobjc.A.dylib`_objc_associations_init at objc-references.mm:137:5
libobjc.A.dylib`arr_init at NSObject.mm:2214:5
libobjc.A.dylib`map_images_nolock(mhCount=261, mhPaths=0x00007ffeefbf4e90, mhdrs=0x00007ffeefbf40f0) at objc-os.mm:544:9
libobjc.A.dylib`map_images(count=261, paths=0x00007ffeefbf4e90, mhdrs=0x00007ffeefbf40f0) at objc-runtime-new.mm:3259:12
- 是在
map_images
中初始化的。
调用的入口是map_images_nolock -> arr_init()
,arr_init
的实现:
void arr_init(void)
{
//内存页初始化
AutoreleasePoolPage::init();
//散列表初始化,引用计数表和弱引用表
SideTablesMap.init();
//关联对象map初始化
_objc_associations_init();
}
-
AutoreleasePoolPage
内存页初始化。 -
SideTablesMap
散列表初始化,其中包含引用计数表和弱引用表。 -
_objc_associations_init
关联对象表初始化,内部调用了AssociationsManager::init()
。
2.1.5 try_emplace
template
std::pair try_emplace(const KeyT &Key, Ts &&... Args) {
//与类的cache的bucket_t好像
BucketT *TheBucket;
//通过key(key是包装的 DisguisedPtr,也就是要存储到的对象。) 找bucket
//TheBucket 没有被const 修饰,走的第二个 LookupBucketFor。TheBucket指针传递,值会被带回来。
//通过 LookupBucketFor TheBucket 要么有值要么为空桶
if (LookupBucketFor(Key, TheBucket))
//second value 为false
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
//走到这里证明,bucket为空桶。则将bucket插入桶中。 objc::detail::DenseMapPair
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...);
//first value, second value 赋值为 true
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
-
try_emplace
在value
有值的情况下会调用两次,第一次key
为包装的对象DisguisedPtr
,第二次key
为关联对象的key
。这里也就说明整个关联对象表是一个双层结构。 -
LookupBucketFor
查找key
对应的bucket
,也就是对象对应的map
以及关联对象key
对应的value
(都是包装后的)。 - 找到则包装后返回,没有找到就插入
bucket
。为了返回原始值进行释放,这也就是有旧值的情况下,值不被替换的原因。
2.1.5.1 LookupBucketFor
LookupBucketFor
有两个,BucketT
参数不同,一个用const
修饰了:
两个,其中进行了内部调用:
template
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
const BucketT *ConstFoundBucket;
//调用上面的LookupBucketFor,result 为 true 证明 Val 对应的bucket已经存在。否则bucket不存在,返回了一个空桶。
bool Result = const_cast(this)
->LookupBucketFor(Val, ConstFoundBucket);
//赋值bucket给传进来的参数。
FoundBucket = const_cast(ConstFoundBucket);
return Result;
}
- 这里调用了
LookupBucketFor
,两者参数不一样。返回值为result
,找没有找到bucket
,也就对应map
以及value
。 -
FoundBucket
是指针传值,内部修改后外部也就修改了。
template
//BucketT 被 const 修饰了
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
//获取buckets地址
const BucketT *BucketsPtr = getBuckets();
//获取buckets数量
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
//key & (容量 - 1 ),类似于cache的mask,也就是找到index
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
//与cache插入类似
while (true) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
//找到了bucket,也就是bucket已经存在了。LLVM_LIKELY 就是 fastpath
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
// If we found an empty bucket, the key doesn't exist in the set.
// Insert it and return the default value.
// 找到了空桶
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
// If this is a tombstone, remember it. If Val ends up not in the map, we
// prefer to return it than something that would require more probing.
// Ditto for zero values.
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// Otherwise, it's a hash collision or a tombstone, continue quadratic
// probing.
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
//重新计算下标
BucketNo += ProbeAmt++;
//再hash
BucketNo &= (NumBuckets-1);
}
}
- 这块逻辑与方法缓存很像,首先通过
容量 -1
(类似mask
)计算BucketNo
,也就是找到index
。 - 根据
index
查找bucket
,bucket
有值返回true
,没有值返回false
。 - 没有找到的情况下重新计算下标,再
hash
。
2.1.5.2 InsertIntoBucket
template
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
TheBucket->getFirst() = std::forward(Key);
::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...);
return TheBucket;
}
- 先进行扩容/找到
bucket
。 - 设置
bucket
对应的值。
InsertIntoBucketImpl
template
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
BucketT *TheBucket) {
unsigned NewNumEntries = getNumEntries() + 1;
unsigned NumBuckets = getNumBuckets();
// 3/4 扩容
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
//2倍扩容
this->grow(NumBuckets * 2);
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
……
return TheBucket;
}
内部进行了查找扩容,返回对应的bucket
。
- 这里也遵循 负载因子(
3/4
) 以及2
倍扩容。 -
(总容量 - 已有元素)* 8 <= 总容量
相当于7/8
判断。
2.1.6 setHasAssociatedObjects
标记对象是否有关联对象
inline void
objc_object::setHasAssociatedObjects()
{
//Tagged Pointer 直接返回
if (isTaggedPointer()) return;
//纯指针 && 有默认的 release,retain等方法,非future类,非元类
if (slowpath(!hasNonpointerIsa() && ISA()->hasCustomRR()) && !ISA()->isFuture() && !ISA()->isMetaClass()) {
//获取_noteAssociatedObjects 方法
void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)this, @selector(_noteAssociatedObjects));
//不为消息转发,也就是找到了方法。
if ((IMP)setAssoc != _objc_msgForward) {
//调用 _noteAssociatedObjects
(*setAssoc)((id)this, @selector(_noteAssociatedObjects));
}
}
//设置新的isa
isa_t newisa, oldisa = LoadExclusive(&isa.bits);
do {
newisa = oldisa;
//纯指针/已经有关联对象标记
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
//isa关联对象标记
newisa.has_assoc = true;
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}
-
isa
纯指针走_noteAssociatedObjects
逻辑,系统会判断是否实现了_noteAssociatedObjects
方法。 -
isa
非纯指针直接设置has_assoc
标记。 -
_noteAssociatedObjects
系统并没有实现,应该是提供给我们实现的。但是以_
开头应该是私有方法。一般情况下我们是用不到的。
在判断是否有关联对象的方法中:
inline bool
objc_object::hasAssociatedObjects()
{
if (isTaggedPointer()) return true;
if (isa.nonpointer) return isa.has_assoc;
return true;
}
可以看到默认的情况下返回true
。
⚠️设值的时候是一个对象,那么可以设置类对象,元类对象,只不过释放就要自己操作了。否则就只能类销毁的时候释放了。也就是伴随着应用程序的整个生命周期了。
2.2 关联对象读取值
上面分析了关联对象的存储值,有存储就有对应的取值,那么应该也是按照两层AssociationsHashMap
获取的。objc_getAssociatedObject
内部直接调用了_object_get_associative_reference
。
2.2.1 _object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
//整体单例map
AssociationsHashMap &associations(manager.get());
//找到对象对应的map 参数是 DisguisedPtr
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
//找到key对应的 association
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
//返回 association的 _value 也就是我们存储的 value。
return association.autoreleaseReturnedValue();
}
- 获取整体单例
associations (AssociationsHashMap)
。 - 包装
object
为DisguisedPtr
从associations
获取对象对应的i(AssociationsHashMap)
。 - 通过
key
从内层i(ObjectAssociationMap)
获取ObjcAssociation
。 - 从
ObjcAssociation
获取value
返回。(ObjcAssociation
为{_policy,_value}
)
这样就与存储的时候两层结构对应上了,整个结构图下:
2.3 关联对象的释放
官方提供了objc_removeAssociatedObjects
供我们释放关联对象。
//移除关联对象
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object, /*deallocating*/false);
}
}
内部实现是调用_object_remove_assocations
。
2.3.1 _object_remove_assocations
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
//将值全部存入 refs 临时空间中
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if (!deallocating) {//对象非释放的情况下
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
//重新将系统的关联对象插入
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
//没有重新插入的话则擦除associations
associations.erase(i);
}
}
// Associations to be released after the normal ones.
SmallVector laterRefs;
// release everything (outside of the lock).
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released.
if (deallocating)
//dealloc的时候系统关联对象,先放入laterRefs 稍后释放,否则不处理。
laterRefs.append(&i.second);
} else {
//释放非系统的关联对象
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
//dealloc 的情况下释放系统的关联对象
later->releaseHeldValue();
}
}
- 根据第二个参数
deallocating
判断是不是dealloc
的时候调用的。 - 将对象对应的
ObjectAssociationMap
存入refs
临时空间,本来的空间i
置空。 - 非
deallocating
情况下(也就是自己调用),将系统的关联对象(通过policy & OBJC_ASSOCIATION_SYSTEM_OBJECT
判断)重新插入i
,并标记是否重新插入didReInsert
。 - 没有重新插入的情况下擦除
object
对应的ObjectAssociationMap
。 - 创建
laterRefs
记录稍后要释放的ObjcAssociation
。 - 循环
refs
非系统的关联对象直接释放,系统的关联对象判断是否deallocating
,deallocating
的情况下加入laterRefs
。 - 循环释放
laterRefs
,也就是系统的关联对象(deallocating
的情况下才有值)。
关联对象流程
-
设值流程:
- 1.创建一个
AssociationsManager
管理类。 - 2.获取唯一的全局静态哈希
AssociationsHashMap
。 - 3.判断是否插入的关联值是否存在:
- 3.1存在走插入流程
- 3.1.1 创建一个空的
ObjectAssociationMap
去取查询的键值对。 - 3.1.2 如果发现没有这个
key
就插入一个 空的BucketT
进去并返回。 - 3.1.3 标记对象存在关联对象(
isFirstAssociation
)。 - 3.1.4 用当前策略和值组成一个
ObjcAssociation
替换原来BucketT
中的空。(有旧值需要交换释放旧值)
- 3.1.1 创建一个空的
- 3.2不存在就走关联对象插入空流程 (插入空置,相当于清除)
- 3.2.1 根据
DisguisedPtr
找到ObjectAssociationMap
中的iterator
迭代查询器。 - 3.2.2 根据
key
找到ObjcAssociation
。 - 3.2.3 交换关联对象,擦除关联对象。
- 3.2.4 如果
object
对应的ObjectAssociationMap
没有值了,则擦除。
- 3.2.1 根据
- 3.1存在走插入流程
- 4.根据是否第一个关联对象设置是否存在关联对象。
- 5.释放旧值
- 1.创建一个
-
取值流程:
- 1.创建一个
AssociationsManager
管理类。 - 2.获取唯一的全局静态哈希
AssociationsHashMap
。 - 3.根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器。 - 4.如果这个迭代查询器不是最后一个,获取
ObjectAssociationMap
(这里有策略和value
)。 - 5.根据
key
找到ObjectAssociationMap
中对应的ObjcAssociation
。 - 6.返回
ObjcAssociation
中的_value
。
- 1.创建一个
-
释放流程:
- 1.创建一个 AssociationsManager 管理类。
- 2.获取唯一的全局静态哈希
AssociationsHashMap
。 - 3.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器。
- 3.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器。
- 4.如果这个迭代查询器不是最后一个元素,获取
ObjectAssociationMap
。 - 5.循环遍历
ObjectAssociationMap
在非deallocing
的情况下将系统关联对象重新插入 。 - 6.遍历循环如果是系统关联对象,
deallocating
的情况下将系统的加入临时数组,释放非系统的关联对象。 - 7.如果
6
中的临时数组有数据,遍历释放。(也就是deallocating
的情况下释放系统的关联对象)。
总结: 关联对象的存储结构其实就是两层哈希map
, 存取的时候进行两层处理(类似二维数组),删除的时候由于存在非dealloc
的情况以及系统添加的关联对象,以及擦除值的操作,也需要两层操作。
三、dealloc
既然关联对象是和对象绑定在一起的,那么在对象释放的时候关联对象肯定也要释放,在平常的开发中我们一般不回去主动调用释放关联对象的API
objc_removeAssociatedObjects
,根据上面的分析在dealloc
中肯定调用了_object_remove_assocations
。
那么在dealloc
中都做了什么呢?
- (void)dealloc {
_objc_rootDealloc(self);
}
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
dealloc
直接调用了_objc_rootDealloc-> obj->rootDealloc
,核心逻辑在rootDealloc
中。
3.1 rootDealloc
inline void
objc_object::rootDealloc()
{
//Tagged Pointer
if (isTaggedPointer()) return; // fixme necessary?
//isa非纯指针
//弱引用
//关联对象
//c++析构函数
//引用计数表
//有这五种情况则不能直接释放,否则可以直接free。
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
//释放对象
object_dispose((id)this);
}
}
- 在
isa
非纯指针,有弱引用,有关联对象,有c++
析构函数,有引用计数表的情况下不能直接释放对象走object_dispose
逻辑,否则直接释放。
3.2 object_dispose
id
object_dispose(id obj)
{
if (!obj) return nil;
//毁坏对象
objc_destructInstance(obj);
free(obj);
return nil;
}
内部进行了对象的毁坏后释放对象,核心逻辑就在objc_destructInstance
中了。
3.2.1 objc_destructInstance
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
//调用C++析构函数
if (cxx) object_cxxDestruct(obj);
//移除关联对象
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
//清除其它逻辑
obj->clearDeallocating();
}
return obj;
}
- 调用
c++
析构函数。 - 移除关联对象。
-
clearDeallocating
清空弱引用表以及引用计数表。
3.2.2 clearDeallocating
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {//纯指针
// Slow path for raw pointer isa.
//清空散列表 两个不调用通一个方法的原因是 纯指针需要 SIDE_TABLE_WEAKLY_REFERENCED 判断是否有弱引用,nonpointer 可以通过isa字段判断
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {//非纯指针 有弱引用或者引用计数表
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
根据是否纯指针分为两个逻辑,纯指针调用sidetable_clearDeallocating
清空散列表,非纯指针调用clearDeallocating_slow
。
3.2.3 sidetable_clearDeallocating & clearDeallocating_slow
sidetable_clearDeallocating
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
//在散列表中找到自身
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
//通过it判断是否有弱引用
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
//清空自身弱引用
weak_clear_no_lock(&table.weak_table, (id)this);
}
//擦除引用计数表
table.refcnts.erase(it);
}
table.unlock();
}
- 在散列表中找到自己。
- 清空自己的弱引用。根据
SIDE_TABLE_WEAKLY_REFERENCED
判断是否有弱引用。 - 擦除自己的引用计数表。
clearDeallocating_slow
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
//散列表
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
//清除弱引用表中自己
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
//擦除引用计数表中自己
table.refcnts.erase(this);
}
table.unlock();
}
- 获取散列表。
- 通过
isa.weakly_referenced
判断是否有弱引用,有弱引用则清除弱引用。 - 判断是否有弱引用计数表(
isa.has_sidetable_rc
),有则擦除弱引用表中自己的计数。
weak_clear_no_lock
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//要清除的对象
objc_object *referent = (objc_object *)referent_id;
//
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
……
// zero out references weak对象引用表
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
//weak引用置为nil
*referrer = nil;
}
else if (*referrer) {
……
objc_weak_error();
}
}
}
//移除弱引用表中的entry
weak_entry_remove(weak_table, entry);
}
- 在若引用计数表中找到自己的
weak_entry_t
。 - 根据
out_of_line
获取指向对象的弱引用指针referrers
与inline_referrers
。 - 循环弱引用指针数组将弱引用指针置为
nil
。 - 将弱引用
weak_entry_t
从弱引用表中删除。
dealloc调用流程:
- 不能直接释放(弱引用、关联对象、c++析构函数、引用计数表)
- 1.调用
c++
析构函数。 - 2.移除关联对象。
- 3.清空弱引用表(弱引用指针数组全部指针置为
nil
)。 - 4.擦除引用计数表中自己。
- 5.
free
。
- 1.调用
- 能直接释放,
free
。
已知的一个常识是在
dealloc
中不需要调用[super dealloc]
,但是在源码分析中并没有看到调用父类的dealloc
方法,在llvm
中发下如下代码:- (void)dealloc { [_myproperty release]; [super dealloc]; }
可以看到应该在编译阶段
llvm
自动添加了super dealloc
的调用。反汇编后也能看到在编译阶段就已经编译进去了:
四、案例分析
关联对象怎么添加weak属性?
在设置关联对象的时候,objc_AssociationPolicy
的取值如下:
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. */
};
在这个枚举中并没有发现weak
,只发现OBJC_ASSOCIATION_ASSIGN
的注释中有weak
描述,但是搜索OBJC_ASSOCIATION_ASSIGN
却没有调用的地方,那也就说明它不会主动置为nil
。
既然这样只能另辟蹊径,存储的时候只能是强引用,但是释放后要置为nil
。一种主流的实现方案是使用block
,block
持有弱引用。
示例代码如下:
@property (nonatomic, weak) id weak_obj;
- (void)setWeak_obj:(id)weak_obj {
id __weak weakObject = weak_obj;
//存储 weak 类型的 obj
id (^block)(void) = ^{ return weakObject; };
return objc_setAssociatedObject(self, "weak_obj", block, OBJC_ASSOCIATION_COPY);
}
- (id)weak_obj {
//获取block
id (^block)(void) = objc_getAssociatedObject(self, "weak_obj");
//执行block,取到block返回的obj
id weakObject = (block ? block() : nil);
return weakObject;
}
实际上关联对象仍然是强引用,但是block
内部持有了变量的弱引用。这样就相当于加了个中间层。在weak_obj
释放后,block
就获取不到weak_obj
了。
关联对象在什么情况下造成内存泄漏?
关联对象可以理解就是持有了一个对象,如果是retain等方式的持有,而该对象也持有了本类,那就导致了循环引用。一般block
造成循环引用的情况比较多。