0%

OC内存管理之weak修饰符实现原理

源码版本:objc4-781

0.weak 修饰符的作用

之前的文章提到过 __weak 修饰符作用:

1.当我们将一个对象赋值给一个 __weak 修饰符的指针变量时,编译器会插入 objc_initWeakobjc_storeWeak 函数将该弱指针变量从旧对象的weak表剔除(如果之前有指向对象的话)并注册到新对象的weak表里,然后将弱指针指向新对象。

2.当使用附有 __weak 修饰符的变量时,编译器会在使用前插入 objc_loadWeakRetained 函数将对象引用计数+1,保证对象在后面的使用过程中不被释放。并在使用后插入 objc_release(tmp1); 释放对象。

3.当弱引用指针变量超出作用域被废弃时,编译器会插入 objc_destroyWeak 将该指针变量从weak表里移除。

4.当对象销毁时,weak表中对象的所有弱引用指针变量会被赋值为nil,避免了野指针崩溃。比如对nil对象发送消息不会有任何反应,需要注意的是如果是对nil进行解引用还是会崩溃的。

这些功能是assign所不具备的,assign唯一与weak相同的地方就是不会导致对象的引用计数+1。

note:weak 只能用于修饰对象类型。并且只能用于iOS5及以上,在iOS4可以使用 __unsafe_unretained 代替.另外weak在MRC下也是有效果的,不过需要设置 Weak References in Manual Retain Release 为 YES。

今天就来看一下weak的上述功能是如何实现的,在看源码之前,可以带着一些问题,比如:

1.使用weak后为啥对象就不会被retain?

2.为了实现对象销毁时所有指向它的弱引用指针变量将被赋值为nil。你是不是得登记保存一下这些弱引用指针变量的地址?源码又是定义了哪些数据结构完成的。

3.一个 __weak 指针变量如果之前指向了A,后面改为指向B,你是不是得把之前登记的给删除(不删除的话会有什么问题),然后登记到新的对象下,这里就涉及到查找,源码为了提高查找效率又是如何优化的。

1.objc_initWeak

对如下代码:

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {
[super viewDidLoad];

XQPerson *p = [XQPerson new];

__weak XQPerson *wp = p;

NSLog(@"hello:%@", wp);
}

__weak XQPerson *wp = p;这一行打好断点。

运行后如下图:

给指针变量添加__weak修饰符后,系统会调用一个objc_initWeak的函数:

1
2
3
4
5
6
7
8
9
10
11
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}

return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}

该函数接受两个参数:

location:双重指针变量,对象指针的指针。

newObj:指针变量,指向一个对象,它的值是对象的地址。

当传入的newObj等于nil时,将对象指针变量置为nil。在上述示例代码中*location = nil;就相当于wp = nil;

接下来可以看下如果是将p赋值给一个__strong类型的指针变量会发生什么:

代码如下:

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {
[super viewDidLoad];

XQModel *model = [XQModel new];

id tmp = model;

NSLog(@"tmp:%@", model);
}

打好断点后运行,会看到如下汇编代码:

可以看到如果是将model赋值给一个 __strong 类型的指针变量,系统会调用到objc_retain函数,对象的引用计数就会加1。

稍微总结一下:

使用 __weak 修饰符时,系统会调用objc_initWeak函数而没有调用类似objc_retain这样的函数,因此对象的引用计数不会发生改变。

使用 __strong 修饰符时,系统会调用objc_retain函数,对象的引用计数会加1。

在继续 weak 的实现原理之前,需要区分指向对象的指针变量对象之间的关系:

指向对象的指针变量是一个变量,它的值是一个地址,这个地址指向某一段内存空间。在上述代码中指针变量 wp 是分配在栈上的,而 wp指向的对象是分配在堆上的。

对象:分配在堆上的一段内存空间,我们需要一个指针变量来引用它,才可以找到并访问这个对象。

它们之间的关系类似于人和人名之间的关系,你可以通过人名找到这个人,但人名不等于人。

后续实现 weak 的数据结构里面要保存的几个关键信息中就包含指针变量的值(即对象的地址)和指针变量的地址。在示例代码中就是保存 wp 的值和 wp自身的地址。

为什么需要保存指针变量的地址呢?因为 weak 修饰符的功能之一就是当对象销毁时将所有指向它的弱引用指针变量赋值为nil。当保存了这些指针变量的地址我们就可以很方便的将这些指针变量赋值为 nil ,而在这个语境下不太可能通过指针变量名来将其置为 nil。

为了实现weak 修饰符的功能,源码中定义了一系列的数据结构。只有充分理解了要干嘛,在看源码时才更有体会。

2.数据结构

在介绍 weak 的实现前,有一些相关的数据结构需要说明一下:

StripedMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
enum { CacheLineSize = 64 };

// StripedMap<T> is a map of void* -> T, sized appropriately
// for cache-friendly lock striping.
// For example, this may be used as StripedMap<spinlock_t>
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif

struct PaddedT {
T value alignas(CacheLineSize);
};

PaddedT array[StripeCount];

static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}

public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}

// Shortcuts for StripedMaps of locks.
void lockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.lock();
}
}

void unlockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.unlock();
}
}

void forceResetAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.forceReset();
}
}

void defineLockOrder() {
for (unsigned int i = 1; i < StripeCount; i++) {
lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
}
}

void precedeLock(const void *newlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
}

void succeedLock(const void *oldlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(oldlock, &array[0].value);
}

const void *getLock(int i) {
if (i < StripeCount) return &array[i].value;
else return nil;
}

#if DEBUG
StripedMap() {
// Verify alignment expectations.
uintptr_t base = (uintptr_t)&array[0].value;
uintptr_t delta = (uintptr_t)&array[1].value - base;
ASSERT(delta % CacheLineSize == 0);
ASSERT(base % CacheLineSize == 0);
}
#else
constexpr StripedMap() {}
#endif
};

StripedMap 字面意思是条纹map,是一个哈希表模板类,key 是对象地址,value 是 T 类型实例,T 里面需要有一把自旋锁。StripedMap里面定义了一个长度为 8或者64的数组,数组元素是T 类型实例。

主要的一个函数:将一个地址映射为数组的下标。

1
2
3
4
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}

SideTablesMap

边表map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}

template <typename Type>
class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)]; //ExplicitInit里有一个数组,数组的长度是sizeof(Type),而sizeof(StripedMap<SideTable>)在iOS上等于512(8*64),mac上为4096(64*64)

public:
template <typename... Ts>
void init(Ts &&... Args) {
new (_storage) Type(std::forward<Ts>(Args)...);
}

Type &get() {
return *reinterpret_cast<Type *>(_storage);
}
};

SideTablesMap是一个静态全局变量,它是在arr_init函数中初始化的:

1
2
3
4
5
6
void arr_init(void) 
{
AutoreleasePoolPage::init();
SideTablesMap.init();
_objc_associations_init();
}

而arr_init函数是在map_images_nolock中被调用的,map_images_nolock又是在map_images中被调用的。

1
2
3
4
5
6
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[]);

extern "C" void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[]);

简而言之,在 runtime 启动时会初始化一个自动释放池AutoreleasePoolPage,一个静态全局变量SideTablesMap,一个关联对象管理器AssociationsManager(保存类别中关联的实例变量)。

SideTablesMap的类型为 ExplicitInit<StripedMap<SideTable>> ,C++的类也没怎么看懂,大致可以把SideTablesMap理解为一个全局的hash数组,数组长度为8(iPhone )或64(Mac),里面存储的是SideTable,因此SideTable不止一个。

源码里一般通过 &SideTables() 获取一个SideTable

1
2
3
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}

比如:SideTable *newTable = &SideTables()[newObj];

通过将传入的对象地址映射为数组index,然后从数组中取出对应的SideTable。

SideTable

边表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;

SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}

~SideTable() {
_objc_fatal("Do not delete SideTable.");
}

void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }

// Address-ordered lock discipline for a pair of side tables.

template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

slock:自旋锁

refcnts:引用计数表。哈希表

weak_table:弱引用表。里面有一个成员变量指向一个哈希表

RefcountMap是一个类型别名:

1
2
3
// RefcountMap disguises its pointers because we 
// don't want the table to act as a root for `leaks`.
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

DisguisedPtr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// DisguisedPtr<T> acts like pointer type T*, except the 
// stored value is disguised to hide it from tools like `leaks`.
// nil is disguised as itself so zero-filled memory works as expected,
// which means 0x80..00 is also disguised as itself but we don't care.
// Note that weak_entry_t knows about this encoding.
template <typename T>
class DisguisedPtr {
uintptr_t value;

static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}

static T* undisguise(uintptr_t val) {
return (T*)-val;
}

public:
DisguisedPtr() { }
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr<T>& ptr)
: value(ptr.value) { }

DisguisedPtr<T>& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
value = rhs.value;
return *this;
}

operator T* () const {
return undisguise(value);
}
T* operator -> () const {
return undisguise(value);
}
T& operator * () const {
return *undisguise(value);
}
T& operator [] (size_t i) const {
return undisguise(value)[i];
}

// pointer arithmetic operators omitted
// because we don't currently use them anywhere
};

指针伪装模板类,DisguisedPtr<T>的作用类似于T *,但不是真正的T *,它里面只有一个无符号整型属性value,并没有一个真的指针成员T *。理论上 value 的值保存被引用对象的地址就可以了,有了对象的地址又有对象的类型,DisguisedPtr的功能已经类似于一个指针变量T *了。

但是DisguisedPtr并没有直接保存被引用对象的地址,而是将其取负后再强转为无符号整数。这么做的目的就是让类似于leaks这样的工具检测不到有这么个东西也保存了对象的地址。

DisguisedPtr中的两个重要方法:

1
2
3
4
5
6
7
8
//伪装对象的地址
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
//解伪装对象的地址
static T* undisguise(uintptr_t val) {
return (T*)-val;
}

weak_entry_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// The address of a __weak variable.
// These pointers are stored disguised so memory analysis tools
// don't see lots of interior pointers from the weak table into objects.
typedef DisguisedPtr<objc_object *> weak_referrer_t;

#if __LP64__
#define PTR_MINUS_2 62
#else
#define PTR_MINUS_2 30
#endif

/**
* The internal structure stored in the weak references table.
* It maintains and stores
* a hash set of weak references pointing to an object.
* If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
* is instead a small inline array.
*/
#define WEAK_INLINE_COUNT 4

// out_of_line_ness field overlaps with the low two bits of inline_referrers[1].
// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address.
// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00
// (disguised nil or 0x80..00) or 0b11 (any other address).
// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state.
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers; //哈希表,动态数组
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; //普通数组,可以存储4个元素,弱引用<=4时保存在这里。起到优化作用。
};
};

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;
}
}
};

weak_entry_t 主要保存了被引用对象自身以及指向它的所有弱引用指针变量的地址。weak entry实体自身被保存在weak表中。

referent:用于保存对象的地址,DisguisedPtr类型,效果类似于objc_object 但并不是真正的objc_object 类型。

接下来是一个联合,主要用于保存该对象的所有弱引用指针变量的地址。使用联合主要是为了优化存储。

如果弱引用个数<=4,则使用inline_referrers固定数组保存。

如果弱引用个数>4,则使用动态数组weak_referrer_t *referrers。referrers其实是一个哈希表,存储在里面的弱引用指针变量的地址并不是按顺序依次排列的,而是将弱引用指针变量的地址映射为一个 index,然后存储在 index 上,这样做的好处就是可以快速查找出它在referrers中的位置,否则只能遍历整个数组。这里就是源码为了提高查找效率做的优化。

out_of_line_ness:是否使用动态hash数组标记位

num_refs:hash数组referrers中的元素个数

mask:hash数组长度-1。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)。

max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过该值)

weak_table_t

1
2
3
4
5
6
7
8
9
10
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; //哈希表,容量不够了会扩容
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};

weak_table_t weak表,是一个全局的弱引用表,也是一个哈希表。被引用的对象的地址作为key,weak_entry_t结构体作为值。

weak_entries: 指针变量,指向一个hash数组,数组里存储弱引用对象的相关信息weak_entry_t。初始时为NULL,第一次插入时才会创建一个长度为64的数组并用weak_entries指向该数组,相当于懒加载。如果事先分配一个长度为64的数组,显然是不必要的,因为有可能没有地方使用到weak指针。

num_entries: hash数组中的元素个数

mask:hash数组长度-1(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)

max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过该值)

ps:在C++里面weak_entry_t就相当于struct weak_entry_t可以省略struct,在C中肯定是没有这样的语法的,我说怎么看起来这么奇怪。

3.weak的实现

objc_initWeak

前面已经知道,使用__weak修饰符时,系统其实是调用了objc_initWeak函数,本节就分析一下objc_initWeak的具体实现:

1
2
3
4
5
6
7
8
9
10
11
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}

return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}

location的值就是对象指针变量的地址,*location 指向旧对象,newObj指向新对象。后面的大部分操作都和这三个值有关。

storeweak

objc_initWeak主要调用了storeweak函数,它的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
static id 
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);

Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;

// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj]; //从SideTablesMap哈希表中获取旧对象的SideTable
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj]; //从SideTablesMap哈希表中获取新对象的SideTable
} else {
newTable = nil;
}

SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}

// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);

// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;

goto retry;
}
}

// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}

// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected

// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}

// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}

SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

return (id)newObj;
}

//
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;

static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}

storeWeak的作用大致如下:

  1. 根据旧对象的地址从SideTablesMap中找到旧SideTable,根据新对象的地址从SideTablesMap中找到新SideTable
  2. 加锁,锁上新旧SideTable。因为后面要操作表里面的东西了,保证同一时刻只能由一个线程操作该表。
  3. unregister,从旧的表中移除当前指针变量地址。
  4. register,在新的表中登记当前指针变量地址。
  5. 将对象的weakly_referenced标志位置为 true,说明该对象是有弱引用指针的,销毁时需要将它们置为 nil 并移除。完成后将指针变量的值指向新对象。
  6. 解锁SideTable

另外,unregister或register时可能会进行weak表的减容或扩容。

这里面主要的两个函数就是weak_unregister_no_lock和weak_register_no_lock了。

weak_register_no_lock

先来看weak_register_no_lock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/** 
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;

if (!referent || referent->isTaggedPointer()) return referent_id;

// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
@selector(allowsWeakReference));
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}

if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}

// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}

// Do not set *referrer. objc_storeWeak() requires that the
// value not change.

return referent_id;
}

大概过程:

  1. 根据对象的地址从weak_table中找到weak_entry_t,把指针变量的地址保存到weak_entry_t里的数组中。
  2. 如果在weak_table没有找到该对象的weak_entry_t,则新创建一个weak_entry_t并把指针变量的地址保存到weak_entry_t里的数组中,扩容weak_table(if needed),将新创建的weak_entry_t插入到weak_table中

正常保存之前会有一些判断,会判断当前对象是否正在销毁,如果正在销毁则根据crashIfDeallocating决定是崩溃还是返回 nil,如果是返回 nil,则指针变量就被赋值为nil 了。对于 objc_initWeak 和 objc_storeWeak函数来说传入的都是DoCrashIfDeallocating,因此不要在对象的 dealloc 方法里再去弱引用它。比如:

1
2
3
- (void)dealloc {
id __weak obj = self; //实际代码当然不会这么明显,比较隐蔽的一种情况是懒加载的view设置delegate为self
}

崩溃:

1
objc[70479]: Cannot form weak reference to instance (0x600001778540) of class ARCPerson. It is possible that this object was over-released, or is in the process of deallocation.

weak_register_no_lock里面调用了很多函数,不过都是哈希表的操作,一个个看。

weak_entry_for_referent

weak_entry_for_referent:根据对象的地址从weak_table中找到weak_entry_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/** 
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
ASSERT(referent);

weak_entry_t *weak_entries = weak_table->weak_entries;

if (!weak_entries) return nil;

size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}

return &weak_table->weak_entries[index];
}

过程:

  1. 首先判断weak_table_t的成员变量weak_entries是否为NULL,如果为NULL说明需要创建哈希数组。
  2. 将对象地址hash 化并按位与weak_table->mask确保不会数组越界,这样就得到一个起始 index。
  3. hash冲突解决,如果 index 处已经有元素但并不等于当前对象,表明存在 hash冲突,则和 index 的下一个位置进行比较。期间如果又找到开始的位置则调用bad_weak_table函数抛出异常。如果超过了最大哈希冲突max_hash_displacement则停止查找也返回 nil,表明哈希表冲突太多了需要扩容了。

这里可以看一下对象地址的hash 化:hash_pointer(referent):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static inline uintptr_t hash_pointer(objc_object *key) {
return ptr_hash((uintptr_t)key);
}

// Pointer hash function.
// This is not a terrific hash, but it is fast
// and not outrageously flawed for our purposes.

// Based on principles from http://locklessinc.com/articles/fast_hash/
// and evaluation ideas from http://floodyberry.com/noncryptohashzoo/
#if __LP64__
static inline uint32_t ptr_hash(uint64_t key)
{
key ^= key >> 4;
key *= 0x8a970be7488fda55;
key ^= __builtin_bswap64(key);
return (uint32_t)key;
}
#else
static inline uint32_t ptr_hash(uint32_t key)
{
key ^= key >> 4;
key *= 0x5052acdb;
key ^= __builtin_bswap32(key);
return key;
}
#endif

/*
Higher-quality hash function. This is measurably slower in some workloads.
#if __LP64__
uint32_t ptr_hash(uint64_t key)
{
key -= __builtin_bswap64(key);
key *= 0x8a970be7488fda55;
key ^= __builtin_bswap64(key);
key *= 0x8a970be7488fda55;
key ^= __builtin_bswap64(key);
return (uint32_t)key;
}
#else
static uint32_t ptr_hash(uint32_t key)
{
key -= __builtin_bswap32(key);
key *= 0x5052acdb;
key ^= __builtin_bswap32(key);
key *= 0x5052acdb;
key ^= __builtin_bswap32(key);
return key;
}
#endif
*/

系统采用了一个不是很棒但是速度很快的哈希函数,不是很棒表明该哈希函数可能会导致一些哈希冲突,散列的不是很均匀。同时注释里列举了一个高质量的哈希函数,但是在某些情况下速度比较慢的哈希函数。可以看到系统对散列均匀程度与速度之间的权衡。

weak_entry_insert

weak_entry_insert:往weak表中插入一个元素weak_entry_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
ASSERT(weak_entries != nil);

size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries); //bad_weak_table就是抛出异常。一般不会到这里
hash_displacement++; //记录冲突的次数
}

weak_entries[index] = *new_entry;
weak_table->num_entries++;

if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}

同样先得到一个起始 index,再进行 hash 冲突解决并记录冲突的次数,然后插入到空位置。最后更新哈希表的最大哈希冲突值max_hash_displacement。

weak_grow_maybe

weak_grow_maybe:扩容

1
2
3
4
5
6
7
8
9
10
// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);

// Grow if at least 3/4 full.
if (weak_table->num_entries >= old_size * 3 / 4) {
weak_resize(weak_table, old_size ? old_size*2 : 64); //old_size为0时,则默认设置为64个槽。
}
}

达到最大空间的3/4时进行扩容。扩容时机:插入元素前,会先判断是否需要扩容。

append_referrer

append_referrer:将指针变量地址添加到weak_entry_t的哈希表里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/** 
* Add the given referrer to set of weak pointers in this entry.
* Does not perform duplicate checking (b/c weak pointers are never
* added to a set twice).
*
* @param entry The entry holding the set of weak pointers.
* @param new_referrer The new weak pointer to be added.
*/
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}

// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}

ASSERT(entry->out_of_line());

if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}

/**
* Grow the entry's hash table of referrers. Rehashes each
* of the referrers.
*
* @param entry Weak pointer hash set for a particular object.
*/
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
ASSERT(entry->out_of_line());

size_t old_size = TABLE_SIZE(entry);
size_t new_size = old_size ? old_size * 2 : 8;

size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;

entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;

for (size_t i = 0; i < old_size && num_refs > 0; i++) {
if (old_refs[i] != nil) {
append_referrer(entry, old_refs[i]);
num_refs--;
}
}
// Insert
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}

注意这里的参数是 new_referrer ,即我们将要把对象指针变量的地址插入到entry的哈希表里。

这里会先判断有没有超出 out_of_line ,如果没有则插入到一个普通数组,否则插入到一个哈希数组里。

上面是 weak_register_no_lock 的整个逻辑。下面看一下 weak_unregister_no_lock

weak_unregister_no_lock

weak_unregister_no_lock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;

weak_entry_t *entry;

if (!referent) return;

if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}

if (empty) {
weak_entry_remove(weak_table, entry);
}
}

// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}

过程:

  1. 找到weak_entry
  2. 调用remove_referrer移除指针变量地址
  3. 判断weak_entry是否为空,为空则从weak_table中将weak_entry也移除。weak_table减容如果可能。

这里主要是remove_referrer和weak_entry_remove的调用。

remove_referrer

remove_referrer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/** 
* Remove old_referrer from set of referrers, if it's present.
* Does not remove duplicates, because duplicates should not exist.
*
* @todo this is slow if old_referrer is not present. Is this ever the case?
*
* @param entry The entry holding the referrers.
* @param old_referrer The referrer to remove.
*/
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}

size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}

代码很多,但逻辑很简单,就是找到old_referrer这个元素的位置,然后将这个位置的内容置为 nil。注意这里并没有将弱引用指针置位nil。

weak_entry_remove

weak_entry_remove:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Remove entry from the zone's table of weak references.
*/
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));

weak_table->num_entries--;

weak_compact_maybe(weak_table);
}

// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);

// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
}

过程:

  1. 如果weak_entry_t使用的是哈希数组则释放哈希数组内存
  2. 将weak_entry_t自身的内存清零,这样weak_table_t中就腾出来一个可用的位置
  3. 将num_entries减 1,即weak_table_t元素个数减 1
  4. 如果weak表里面元素个数小于某个值时,则将weak_table_t进行减容

减容和扩容都会调用weak_resize:

weak_resize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
size_t old_size = TABLE_SIZE(weak_table);

weak_entry_t *old_entries = weak_table->weak_entries;
weak_entry_t *new_entries = (weak_entry_t *)
calloc(new_size, sizeof(weak_entry_t));

weak_table->mask = new_size - 1;
weak_table->weak_entries = new_entries;
weak_table->max_hash_displacement = 0;
weak_table->num_entries = 0; // restored by weak_entry_insert below

if (old_entries) {
weak_entry_t *entry;
weak_entry_t *end = old_entries + old_size;
for (entry = old_entries; entry < end; entry++) {
if (entry->referent) {
weak_entry_insert(weak_table, entry);
}
}
free(old_entries);
}
}

过程:

  1. 开辟一个新的长度为new_size的内存空间
  2. 将旧的哈希表old_entries里的weak_entry_t重新插入(其实就是重新散列)到新的new_entries里,插入前判断entry->referent是否有值。这样已经清零的内存就不会插入。

到此为止objc_initWeak的作用就分析完了。

接下来就是查看当对象销毁时,如何将所有指向它的弱引用指针变量赋值为nil。

dealloc

一个对象销毁时会调用dealloc方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
- (void)dealloc { //NSObject的dealloc,所以在子类中需要调用super才会执行如weak表的清除,关联对象的释放等。
_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.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}

/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;

objc_destructInstance(obj);
free(obj);

return nil;
}

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is 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;
}

inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating(); //这里面也是清除weak,清除引用计数
}
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());
}

objc_destructInstance函数作用:

如果有自定义的C++析构方法,则调用C++析构函数。如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating方法清除对象的相关引用。

最终会调用clearDeallocating_slow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Slow path of clearDeallocating() 
// for objects with nonpointer isa
// that were ever weakly referenced
// or whose retain count ever overflowed to the side table.
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();
}

clearDeallocating_slow在获取到SideTable后会加锁,完成清除操作后,SideTable又会释放锁。

weak_clear_no_lock

核心函数weak_clear_no_lock:将所有弱引用该对象的指针变量的值置为 nil,并将该 object 的weak_entry_t从 weak 表中移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/** 
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
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);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}

// zero out references
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) {
*referrer = nil; //弱引用指针的值被置位nil.
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}

weak_entry_remove(weak_table, entry);
}

4.总结

SideTable示意图

让我们再来看一下下面的代码:

1
2
XQPerson *p = XQPerson.new;
__weak XQPerson *wp = p;

对于上面简单的一句 __weak XQPerson *wp = p; 背后执行的逻辑可不少。

首先会根据新旧对象的地址分别从SideTablesMap中获取到新旧对象的SideTable,

然后再根据旧对象的地址从旧对象的SideTable的weak 表中获取到weak entry,然后从weak entry的referrers哈希表中把指针变量移除,

最后再根据新对象的地址从新对象的SideTable的weak 表中获取到weak entry,然后将弱引用指针变量登记到weak entry的referrers哈希表中。

参考

细看objc-weak源码

iOS底层原理:weak的实现原理

Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t

哈希算法相关

Speed Hashing

扫盲,那些让人蛋疼的Hash概念

疑问

Q1:OC对象的引用计数是保存在哪的?(已完成)

分析了好几天的 weak 实现,突然想知道OC对象的引用计数到底是保存在哪的,于是便有了这个疑问。这个问题比较大,可能需要一整篇文章才能回答。

简单点讲就是存储在SideTable的RefcountMap(也是一个哈希表)里,不过大多数情况下是存储在对象的isa里。

参考:

苹果iOS系统源码思考:对象的引用计数存储在哪里?—从runtime源码得到的启示

Objective-C 引用计数原理

理解 ARC 实现原理

Q2:为什么 weak 是线程不安全的?(待解决)

源码里好些weak 相关的函数都注释着该函数是线程不安全的,如:objc_initWeak,objc_destroyWeak。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 
* Destroys the relationship between a weak pointer
* and the object it is referencing in the internal weak
* table. If the weak pointer is not referencing anything,
* there is no need to edit the weak table.
*
* This function IS NOT thread-safe with respect to concurrent
* modifications to the weak variable. (Concurrent weak clear is safe.)
*
* @param location The weak pointer address.
*/
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}

但本人没遇到过因为 weak 导致的崩溃,也没试出来,看源码发现该加锁的地方也都加锁了,实在看不出来到底是哪个地方会导致线程不安全。

参考

从一个crash理解weak的延迟释放 作者其他文章也还不错

不安全的weak变量 不安全的一个例子,没怎么看懂

在 dealloc 里使用 weak self 引起崩溃?

Weak properties are not thread safe when reading 不过系统已经解决了。

[runtime] Thread safety for weak references #1454

查遍了Google都是说因为objc_loadWeakRetained的问题导致的多线程下过度释放,但从资料上显示objc_loadWeakRetained函数已经修复了该问题。所以不太清楚到底还有哪些原因导致weak多线程不安全。

Q3:dealloc 的时候增加引用计数,会防止对象销毁吗?

不能。在dealloc 的时候调用retain,引用计数确实会增加,但不会阻止对象销毁。因为dealloc最终会调用super-dealloc,而super-dealloc从上面的实现可以看到最终会调用free释放内存。可以说dealloc是一个不可逆的行为。

retain只在对象还没销毁的时候才有用。

MRC:

1
2
3
4
5
6
7
8
9
10
11
- (void)dealloc {
//在[super dealloc];前写没有问题,虽然此时引用计数会增加,但对象已经在销毁了,super dealloc里最终会调用free。
// [self retain]; //引用计数会变为2.但没用。
// NSLog(@"retainCount:%lu", (unsigned long)[self retainCount]);

[super dealloc];

//在[super dealloc];后写会崩溃,因为对象的内存已经free了,这里会野指针崩溃。
[self retain];
NSLog(@"retainCount:%lu", (unsigned long)[self retainCount]);
}

ARC:

1
2
3
4
5
6
7
8
- (void)dealloc {
ARCPerson *obj = self; //这里没事,引用计数会变为2.
NSLog(@"obj:%@", obj);
NSLog(@"retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(self)));

__weak ARCPerson *wObj = self; //这里会崩溃。
NSLog(@"wObj:%@", wObj);
}

Q4:weak是否适用于MRC?

适用。weak在MRC下也是有效果的。只不过weak只能在>=iOS5上才能使用。

示例:

1
2
3
4
5
6
7
8
9
10
11
- (void)hello {
@autoreleasepool {
MRCCat *cat = [MRCCat new];
self.weakCat = cat;
NSLog(@"%@ 1", self.weakCat);
[cat release];
} //weak在MRC下也是有效果的。只不过weak只能在>=iOS5上才能使用。
NSLog(@"%@ 2", self.weakCat); //这里打印为:(null) 2。说明weakCat被置为nil了。
[self.weakCat hello];
NSLog(@"%@ hello", self);
}

其他

复习 C 语言里的指针和数组的一些语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int i = 3;
int *intPtr = &i; //可以指向一个int变量,也可以指向一个元素为int的数组

int intArr[10] = {0};
intArr[0] = i;

int *intPtrArr[10] = {&i};
*intPtrArr[0] = 8;

int *intPtrArr = intArr;
*(intPtrArr+1) = 4;

intPtr = intArr; //这里intPtr就指向了一个数组
*(intPtr + 2) = 5;

int **ptrD = &intPtr; //ptrD指针的指针
int **arr[10] = {NULL}; //arr是一个数组,元素是指针的指针
int ***ptrArr = arr; //ptrArr是一个指针,指向一个数组,数组的元素是指针的指针
arr[0] = ptrD;

int a = *intPtr;
觉得文章有帮助可以打赏一下哦!