内存管理
- assign:
在setter方法中的实现是直接赋值, 一般用于修饰基本数据类型. 修饰对象时候, 不会增加引用计数(在assign修饰的指针指向的对象被释放后, assign修饰的指针让指向原来的对象地址, 这时候,如果继续使用这个指针访问这个地址会产生悬垂指针) - retian:
MRC下使用, ARC使用strong, 表示修饰的指针强引用着指向的对象(将指针原来指向的旧对象释放掉,然后指向新对象,同时引用计数 + 1), setter中的实现是release旧值, ratain新值. - copy:
setter 方法的实现是 release 旧值,copy 新值,用于 NSString、block 等类型. 使用copy修饰的属性的set方法是调用objc_setProperty函数赋值的。 - mutableCopy
setter 方法的实现是 release 旧值,mutableCopy 新值. - strong:
ARC下使用, 效果同retian. 使用strong修饰的属性的set方法是内存平移找到地址, 然后直接复赋值的。 - weak:
ARC 下才能使用。主要修饰弱引用,不增加对象引用计数变化,可以避免循环引用。修饰的对象在被释放之后,会自动将指针置为 nil,不会产生悬垂指针. - unsafe_unretained
MRC下使用, ARC下基本不用, 用法同assign, 会产生悬垂指针.
原子性
- atomic :
原子性, 编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁, 从而保证赋值和取值的原子性操作是线程安全的. 但是不能保证操作是安全的, 比如修饰一个OC对象, 可以保证OC对象的赋值和取值是线程安全的, 但是不能保证为这个OC对象的属性/成员变量的赋值/取值操作是线程安全的. - nonatomic
非原子性, 以为编译器会默认生成和互斥锁, 这会操作耗时, 所以很多都是直接使用nonatomic.
读写权限
- readwrite:
- 含有读写权限, 我的做法通常是.h中声明为readonly, 在.m中声明为readwrite, 这样外部对这个属性有只有读权限, 而内部对这个属性有读写权限.同时编译器也会生成对应setter和getter的方法名和实现.
- readonly:
- 只包含读权限. 编译器也只会生成getter的方法名和实现.
- setter:
- 修改setter默认的方法名.
- getter:
- 修改getter默认的方法名.
weak的实现原理
-
原理
runtime 维护了一个 weak 表,用于存储指向某个对象的所有 weak 指针。weak 表其实是一个 hash 表,key 是所指对象的地址, value 是 weak 指针的地址数组,是数组的原因是一个对象可能被多个弱引用指针指向。 -
流程
- 初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
- 添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
- 在objc_storeWeak()函数中, 通过 SideTable newTable = &SideTables()[newObj]; 找到 对应的 SideTable(StripedMap 中一共只有8个节点,而在我们的APP中,一般都会不只有8个对象,因此,多个对象一定会 重用同一个SideTable节点 )
- 通过 SideTable 找到复用此SideTable的所有对象的 weak_table_t
- 通过weak修饰的指针指向的对象, 在weak_table_t中找到所有指向这个对象的弱引用指针数组 weak_entry_t.
- 释放时,调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。
-
weak 对象自动被置为 nil 的流程
- 对象的引用计数为 0 时,执行 dealloc
- 在 dealloc 中,调用了_objc_rootDealloc 函数
- 在_objc_rootDealloc 中,调用了 object_dispose 函数,
-
调用 objc_destructInstance该函数内部作用如下:
- 为 C++ 的实例变量们(iVars)调用 destructors
- 为 ARC 状态下的 实例变量们(iVars) 调用 -release
- 解除所有使用 runtime Associate 方法关联的对象
- 解除所有 __weak 引用
- 调用 free()
-
最后调用 objc_clear_deallocating,
- 从 weak 表中获取废弃对象的地址为键值的记录
- weak 修饰符变量的地址,赋值为 nil(weak_clear_no_lock 方法)
- 将 weak 表中该记录删除
- 从引用计数表中删除废弃对象的地址为键值的记录
-
代码
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj) {
ASSERT(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc() {
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && // 无优化过isa指针
!isa.weakly_referenced && // 无弱引用
!isa.has_assoc && // 无关联对象
!isa.has_cxx_dtor && // 无cxx析构函数
!isa.has_sidetable_rc)) { // 不存在引用计数器是否过大无法存储在isa中(使用 sidetable 来存储引用计数)
// 直接释放
assert(!sidetable_present());
free(this);
} else {
// 下一步
object_dispose((id)this);
}
}
// 如果不能快速释放,则调用 object_dispose()方法,做下一步的处理
static id _object_dispose(id anObject) {
if (anObject==nil) return nil;
objc_destructInstance(anObject);
anObject->initIsa(_objc_getFreedObjectClass ());
free(anObject);
return nil;
}
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.
if (cxx) object_cxxDestruct(obj); // 销毁成员变量
if (assoc) _object_remove_assocations(obj); // 释放动态绑定的对象
obj->clearDeallocating();
}
return obj;
}
/*
* clearDeallocating一共做了两件事
*
* 1、将对象弱引用表清空,即将弱引用该对象的指针置为nil
* 2、清空引用计数表
* - 当一个对象的引用计数值过大(超过255)时,引用计数会存储在一个叫 SideTable 的属性中
* - 此时isa的 has_sidetable_rc 值为1,这就是为什么弱引用不会导致循环引用的原因
*/
inline void objc_object::clearDeallocating() {
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer 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());
}```
// - SideTables 类型:StripedMap<SideTable>。SideTables 的使用:SideTable *table = &SideTables()[obj] 它的作用正是根据 objc_object 的指针计算出哈希值,然后从 SideTables 这张全局哈希表中找到 obj 所对应的 SideTable。
struct SideTable {
spinlock_t slock;
// - 对象的引用计数
RefcountMap refcnts;
weak_table_t weak_table;
}
// - weak_table_t 是全局的保存弱引用的哈希表。以 object ids 为 keys,以 weak_entry_t 为 values。
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
// - 保存所有指向某个对象的弱引用变量的地址
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被弱引用的对象
union {
struct {
weak_referrer_t *referrers; // 弱引用该对象的对象指针地址的hash数组
uintptr_t out_of_line_ness : 2; // 是否使用动态hash数组标记位
uintptr_t num_refs : PTR_MINUS_2; // hash数组中的元素个数
uintptr_t mask; // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。
uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent) // 构造方法,里面初始化了静态数组
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
typedef objc_object ** weak_referrer_t;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
##weak_table_t 的缩放
1. 当weak_table 3/4及以上的空间已经被使用的时候, 就会触发扩容的操作, 每次扩充后的空间都是当前空间的2倍.
2. 当weak_table 目前的大小不小于1024个weak_entry_t的空间,并且低于1/16的空间被占用。缩小后的空间是当前空间的1/8。
##strong和copy 修饰属性的区别
// - 使用strong和copy 修饰属性
@interface Person : NSObject
@property (nonatomic, copy) NSString *cStr;
@property (nonatomic, strong) NSString *sStr;
@end
NSString *str = @"abcdefghigklmn";
NSMutableString *mStr = [NSMutableString stringWithString:@"abc"];
Person *p = [Person new];
p.cStr = str;
p.sStr = str;
NSLog(@"// - 李超群 console [log] ...str: %@, p.copyStr: %@, p.strongStr: %@", str, p.cStr, p.sStr);
// - 李超群 console [log] ...str: abcdefghigklmn, p.copyStr: abcdefghigklmn, p.strongStr: abcdefghigklmn
/*
str 的地址是 0x100611280, 地址内的内容是
08 04 62 08 02 00 00 00
C8 07 00 00 00 00 00 00
0E 88 02 00 01 00 00 00
0E 00 00 00 00 00 00 00
0~8 字节---> 08 04 62 08 02 00 00 00 isa指针
9~16 字节--> C8 07 00 00 00 00 00 00 一脸懵逼
17~24 字节-> 0E 88 02 00 01 00 00 00 字符串所在的内存
*/
// - memory write 0x010002880E 62
// - str的数据变为 bbcdefghigklmn;
// - p.copyStr : bbcdefghigklmn;
// - p.strongStr : bbcdefghigklmn;
NSLog(@"// - 李超群 console [log] ...str: %@, p.copyStr: %@, p.strongStr: %@", str, p.cStr, p.sStr);
// - 李超群 console [log] ...str: bbcdefghigblmn, p.copyStr: bbcdefghigblmn, p.strongStr: bbcdefghigblmn
p.cStr = mStr;
p.sStr = mStr;
NSLog(@"// - 李超群 console [log] ...str: %@, p.copyStr: %@, p.strongStr: %@", mStr, p.cStr, p.sStr);
// - 李超群 console [log] ...str: abc, p.copyStr: abc, p.strongStr: abc
[mStr appendString:@"d"];
NSLog(@"// - 李超群 console [log] ...str: %@, p.copyStr: %@, p.strongStr: %@", mStr, p.cStr, p.sStr);
// - 李超群 console [log] ...str: abcd, p.copyStr: abc, p.strongStr: abcd
##深拷贝和浅拷贝
1. 结论
只要出现 mutable(string/array.mutableCopy, mutableString/mutableArray.copy, mutableString/mutableArray.mutableCopy.)就是深拷贝,此时是将内容拷贝在一个新的地址上, 修改原有的变量不会影响新拷贝的变量(数组的浅/深拷贝, 只是指向了新的数组地址, 数组里边的元素的地址还是原来的元素的地址, 不会深拷贝)
而string/array.copy就是浅拷贝, 拷贝出来一个指针指向原有的变量, 此时如果修改原有的变量的值, 就会影响浅拷贝的对象
网友评论