理论知识总结一属性关键字

属性关键字:

  • atomic
  • nonatomic
  • strong
  • weak
  • copy
  • assign
  • retain
  • readwrite
  • readondy
  • unsafe_unretained

属性关键字与所有权关键字之间是对应关系:

  • assign 对应 __unsafe_unretained修饰符
  • copy 对应__strong修饰符
  • retain 对应 __strong修饰符
  • strong 对应 __strong修饰符
  • unsafe_unretained 对应 __unsafe_unretained修饰符
  • weak 对应 __weak修饰符

atomic:

多线程安全,默认关键字。

作用:多线程下将属性设置为atomic可以保证读取数据的一致性,因为它保证数据只被一个线程占用,也就是说一个线程对属性进行操作,会使用自旋锁锁住该属性,不被其他线程进行读取操作。

缺点:使用自旋锁的开销会比较大(因为使用atomic在访问被锁的资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源的锁被释放)。其实这也说明了atomic并不能真正的保证线程安全,因为自旋锁迟早就被释放。

在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成如下:

1
2
3
4
5
6
{lock} // 自旋锁
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}

为什么不能真正的保证线程安全:

例:
线程A 调用 getter
于此同时线程B 线程C 都调用了 setter

那线程A get 到的值就有三种情况:
1.可能是B/C线程set之前原始的值
2.可能是 B 线程 set 的值
3.可能是 C 线程 set 的值

因此atomic修饰的属性,系统在生成的getter/setter 会保证get/set的操作完整性,不受其他线程的影响。getter 最总还是能得到一个完好的对象(保证了数据的完整性,就是不存在get不到值的情况)。多个线程同时调用setter方法的情况,虽然不会出现某个线程执在未执行完setter全部语句之前,另一个线程就开始执行setter方法(确保操作完整性)。但是只是保证了在getter和setter方法上的线程安全,getter和setter以外的线程安全就无法保证了。


nonatomic:

在来理解nonatomic相对来说就比较轻松了,因为nonatomic理论上跟atomic是相对的。

即:非多线程、变量保护。因为没有自旋锁的存在,性能更优。但同时返回的对象或操作就有可能不是完整的。

  • 分析strong和weak的实现之前,很有必要看看对象的成员变量第怎么进行赋值的。因为对象的属性的setter本质就是对对象的成员变量进行复制,一般情况下每一个对象的属性就对应存在一个对象的成员变量

weak:

弱引用,引用计数不会+1,并在引用对象被释放后指针会被至为nil。 用于一些对象相互引用的时候,避免出现强强引用,对象不能释放所造成的内存泄漏问题。

理论知识:weak是通过Runtime维护的`weak表,而strong是通过runtime维护的一个自动计数表结构。

weak表:其实就是一个hash表(哈希表)。weak_table_t是一个全局weak引用的表,使用不定类型对象的地址作为key,使用weak_entry_t(保存了所有指向指定对象的weak指针数组)类型结构体对象作为value

weak实现原理步骤
初始化

runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 前提条件: 必须是一个没有被注册为__weak对象的有效指针。而value则可以是null或指向一个有效的对象。

id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效
// 无效对象直接导致指针释放

if (!newObj) {
*location = nil;
return nil;
}
// 有效object将被注册为一个指向value的__weak对象

return storeWeak<true/*old*/, false/*new*/, false/*crash*/> (location, (objc_object*)newObj);
}
添加引用时

添加引用时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
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];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} 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(_class_getNonMetaClass(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;
}

// 整个函数的作用大致区分传入的新旧weak指针值,然后添加到对应的SideTable内。
释放时调用

释放时调用objc_clear_deallocating函数.objc_clear_deallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设置为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

1
2
3
4
5
6
7
8
void 
objc_clear_deallocating(id obj)
{
assert(obj);

if (obj->isTaggedPointer()) return;
obj->clearDeallocating();
}

还调用了 clearDeallocating() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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());
}

clearDeallocating_slow()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}

最后在weak_clear_no_lock()函数中可以看出具体至nil的具体操作:

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
/**
* 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;
}
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);
}
SideTable

NSObject.mm文件中创建了一个结构体SideTable,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct SideTable {
spinlock_t slock;
// 自旋锁 首页是为了保护线程安全(atomic在setter方法中带有自旋锁)

RefcountMap refcnts;
// 引用技数表 RefcountMap以 对象的地址 作为 key,引用计数值 作为 value

weak_table_t weak_table;
// weak表
...
};

// SideTable:管理引用计数表和weak表(一个全局的Hsah表),并使用spinlock_lock(自旋锁)来防止操作表结构时可能的竞态条件(线程安全 保证了getter和setter的完整性)。
weak_table_t
1
2
3
4
5
The global weak references table. Stores object ids as keys,
// 全局弱引用表 将对象的值(结构体地址) 作为 key

and weak_entry_t structs as their values.
// 指向结构体的一组指针变量(是一个数组变量) 作为 value
1
2
3
4
5
6
struct weak_table_t {
weak_entry_t *weak_entries; // 保存所有指向指定对象的weak指针
size_t num_entries; // 存储空间 即`entries`的数目
uintptr_t mask; // 参与判断引用计数辅助量
uintptr_t mas_push_displacement; // hash key 最大偏移量
};

作用:就是在对象执行dealloc的时候将所有指向该对象的weak指针的值设为nil,避免空指针。 使用不定类型对象的地址的hash化后的数值作为key,用weak_entry_t类型的结构体对象作为value。

weak_entry_t

是存储在弱引用表中的一个内部结构体,负责维护和存储指向一个对象的所有弱引用hash表。存储了一个对象的引用计数的信息.

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
    #define WEAK_INLINE_COUNT 4
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
// 对 `obje_object *`指针及其一些操作进行的封装 目的就是为了让它给人看起来不会有内存泄漏的样子,其内容可以理解为存储了一个对象的引用计数的信息。

union {
struct {
weak_referrer_t *referrers;

uintptr_t out_of_line : 1;
// 成员为最低有效位 当标志位为0时 增加引用表指针纬度 当其为0的时候 `weak_referrer_t`(weak_referrer_t其实是obje_object的别名 定义:`typedef obje_object ** weak_referrer_t`) 成员将扩展为静态数组型的 hash table

uintptr_t num_refs : PTR_MINUS_1;
// 引用数值 这里记录弱引用表中引用有效数字 即里面元素的数量

uintptr_t mask;

uintptr_t mas_hash_displacement;
// hash 元素上限阀值
};
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};

// weak_entry_t 中的referrers有两种形式 当out_of_line为 0 时 referrers 就是一个静态数组类型的表 数组大小默认为 WEAK_INLINE_COUNT 当 out_of_line 不为 0 时 referrers 则是一个动态的数组 内容随之增加。

strong:

理解了weak再来理解strong就相对比较的简单了。因为weak多半是为了解决strong带来的一些问题而存在的。强引用,被strong修饰的属性一般不会自动释放。


assign:

既可以修饰对象类型的,也可以修饰基本数据类型。引用计数不 +1,当被修饰的对象被释放后指针不会被至为nil(跟weak相反)会产生野指针,因此向被释放的对象发送消息会导致崩溃。setter方法直接赋值,不进行任何retain操作,为了解决原类型与循环引用问题。


copy:

主要使用在两个方面:

1.block: 在MRC时代block内部的代码块是在栈区(由系统编译器自动管理,不需要程序员手动管理)的,使用copy关键字可以把它放到堆区。 ARC使用copy和strong效果是一样的。

  • 很有必要理解下为什么要把block把到堆区:

其实先大致理解下的意思。

:有系统编译器自动管理,不需要程序员手动管理。一般用于存放非OC对象

:释放由程序员手动管理,因此不及时回收很容易产生内存泄漏情况。一般用于存放OC对象

然而block就是一个对象,理论上是可以进行retain/release。但是block在创建的时候它的内存默认是分配在上的,所以它的作用域仅限在创建的时候的上下文函数或方法。所以当你在该作用域外调用该block程序就会崩溃。把block的内存推到堆区就可以避免这个问题。

2.用于某些父类指针可以指向子类的对象(多指存在不可变和可变类型的对象:NSString、NSArray、NSDictionary…),作用就是保护属性变量不会被修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@property (nonatomic,strong) NSString * strongedStr;
@property (nonatomic,copy) NSString * copyedStr;

NSMutableString *mutable_string = [[NSMutableString alloc] initWithString:@"可变类型"];

self.strongedStr = mutable_string;
self.copyedStr = mutable_string;

NSLog(@"mutable_string = %p",mutable_string);
NSLog(@"strongedStr = %p",self.strongedStr);
NSLog(@"copyedStr = %p",self.copyedStr);

// 内存地址
mutable_string = 0x283c14630
strongedStr = 0x283c14630
copyedStr = 0x283244420

// 值
mutable_string = 可变类型
strongedStr = 可变类型
copyedStr = 可变类型

可以很明显的看出使用strong修饰的不可变类型的地址跟可变类型的地址是相同的,
使用copy则是新地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 改变mutable_string 的值后看下内存地址变化
[mutable_string appendString:@"还是可变类型"];

NSLog(@"mutable_string = %p",mutable_string);
NSLog(@"strongedStr = %p",self.strongedStr);
NSLog(@"copyedStr = %p",self.copyedStr);

// 内存地址
mutable_string = 0x282508330
strongedStr = 0x282508330
copyedStr = 0x282b58940

// 值
mutable_string = 可变类型还是可变类型
strongedStr = 可变类型还是可变类型
copyedStr = 可变类型

可以很明显的看出copy修饰的没有改变,但是内存地址却发生了改变。strong修饰的则都随着改变。

  • 由此可以引出copy的深拷贝浅拷贝的概念和区别。
浅拷贝:拷贝后,并没有进行真正的复制,而是复制的对象和原对象指向同一个地址。

深拷贝:真正的复制,复制的对象指向新的地址。

> 真正理解深拷贝浅拷贝还需要了解容器类非容器类
>
> 容器类:可以包容其他类的基类。如:NSarray,NSDictionary
>
> 非容器类:则不可以可以包容其他类的基类。如:NSString
>
> 因为容器类非容器类所涉及的深拷贝浅拷贝也是有所区别的。


#### 非容器类:
不可变:
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
NSString *string = @"非容器不可变类型";
NSString *stringA = [string copy];
NSString *stringB = [string mutableCopy];

NSLog(@"string--值 = %@",string);
NSLog(@"stringA--值 = %@",stringA);
NSLog(@"stringB--值 = %@",stringB);

NSLog(@"string--地址 = %p",string);
NSLog(@"stringA--地址 = %p",stringA);
NSLog(@"stringB--地址 = %p",stringB);

NSLog(@"string--class = %@",string.class);
NSLog(@"stringA--class = %@",stringA.class);
NSLog(@"stringB--class = %@",stringB.class);

// NSLog:
string--值 = 非容器不可变类型
stringA--值 = 非容器不可变类型
stringB--值 = 非容器不可变类型

string--地址 = 0x104094290
stringA--地址 = 0x104094290 // copy 值相同 地址相同
stringB--地址 = 0x2818dc540 // mutableCopy 值相同 地址不同

string--class = __NSCFConstantString
stringA--class = __NSCFConstantString // 不可变对象
stringB--class = __NSCFString // 可变对象

结论:

1.非容器 -> 不可变对象 -> copy -> 浅拷贝:地址相同 不可变对象 2.非容器 -> 不可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象
可变:
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
NSMutableString *mustablestring = [[NSMutableString alloc] initWithString:@"非容器可变类型"];
NSString *stringA = [mustablestring copy];
NSString *stringB = [mustablestring mutableCopy];

NSLog(@"string--值 = %@",mustablestring);
NSLog(@"stringA--值 = %@",stringA);
NSLog(@"stringB--值 = %@",stringB);

NSLog(@"string--地址 = %p",mustablestring);
NSLog(@"stringA--地址 = %p",stringA);
NSLog(@"stringB--地址 = %p",stringB);

NSLog(@"string--class = %@",mustablestring.class);
NSLog(@"stringA--class = %@",stringA.class);
NSLog(@"stringB--class = %@",stringB.class);

//

string--值 = 非容器可变类型
stringA--值 = 非容器可变类型
stringB--值 = 非容器可变类型

string--地址 = 0x280c94810
stringA--地址 = 0x280c948d0
stringB--地址 = 0x280c94ab0

string--class = __NSCFString
stringA--class = __NSCFString
stringB--class = __NSCFString

结论:

1.非容器 -> 可变对象 -> copy -> 深拷贝:地址不同 可变对象 2.非容器 -> 可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象

容器类:

不可变:
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
NSArray *array = @[@"1",@"2",@"3"];
NSArray *arrayA = [array copy];
NSArray *arrayB = [array mutableCopy];

NSLog(@"array--值 = %@",array);
NSLog(@"arrayA--值 = %@",arrayA);
NSLog(@"arrayB--值 = %@",arrayB);

NSLog(@"array--地址 = %p",array);
NSLog(@"arrayA--地址 = %p",arrayA);
NSLog(@"arrayB--地址 = %p",arrayB);

NSLog(@"array--class = %@",array.class);
NSLog(@"arrayA--class = %@",arrayA.class);
NSLog(@"arrayB--class = %@",arrayB.class);

array--值 = (1,2,3)
arrayA--值 = (1,2,3)
arrayB--值 = (1,2,3)

array--地址 = 0x282e005d0
arrayA--地址 = 0x282e005d0
arrayB--地址 = 0x282e006c0

array--class = __NSArrayI
arrayA--class = __NSArrayI
arrayB--class = __NSArrayM

结论:

1.容器 -> 不可变对象 -> copy -> 浅拷贝:地址相同 不可变对象 2.容器 -> 不可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象
可变:
1
2
3
4
5
6
7
8
9
10
11
mutablearray--值 = (1,2,3)
mutablearrayA--值 = (1,2,3)
mutablearrayB--值 = (1,2,3)

mutablearray--地址 = 0x2831fc2d0
mutablearrayA--地址 = 0x2831fc300
mutablearrayB--地址 = 0x2831fc330

mutablearray--class = __NSArrayM
mutablearrayA--class = __NSArrayI
mutablearrayB--class = __NSArrayM

结论:

1.容器 -> 可变对象 -> copy -> 深拷贝:地址不同 不可变对象 2.容器 -> 可变对象 -> mutableCopy -> 深拷贝:地址不同 可变对象

总结:

非容器
方法类型 不可变类型 可变类型
copy 浅拷贝 深拷贝
mutableCopy 深拷贝 深拷贝
容器
copy 浅拷贝 深拷贝
mutableCopy 深拷贝 深拷贝

copy对于可变对象为深拷贝,对于不可变对象为浅拷贝

mutableCopy都是深拷贝


retain:

持有特效性,setter方法会将传入的参数先保留,在赋值。释放旧的对象,将旧对象的值赋予输入对象,在提高输入对象的引用计数值 +1。(release旧值,retain新值)

  • 声明属性时效果等同于strong,而在声明block时使用的strongcopy是等效的,但是retain却不等效于strong而等效于assign

readwrite readonly:

readwrite默认读写属性,生成setter和getter方法。readonly只读属性,只生成getter方法。


unsafe_unretained:

相当于ARC环境下的assign。对象被释放后不会至nil。容易导致野指针。