-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
170 lines (81 loc) · 287 KB
/
search.xml
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>一文读懂iOS内存管理之概念篇</title>
<link href="/2020/11/21/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82iOS%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A6%82%E5%BF%B5%E7%AF%87/"/>
<url>/2020/11/21/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82iOS%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B9%8B%E6%A6%82%E5%BF%B5%E7%AF%87/</url>
<content type="html"><![CDATA[<h1 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h1><blockquote><p>更多文章,参考<a href="https://www.yuque.com/focus-vtfvc/fgzn2g/vgfbd8" target="_blank" rel="noopener">个人雨雀站点</a></p></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>为什么需要内存管理?原因在于如果没有内存管理,程序在运行的过程中不但不能释放不再使用的内存,而且还会不停地分配内存,这样占用的内存就会越来越多,程序速度也会越来越慢,最后甚至会因为内存耗尽而崩溃,因此需要妥善管理内存。</p><blockquote><p>内存消耗: 当应用程序启动后,除了代码和系统数据会消耗一部分内存外,开发者在程序中创建的任何对象都会消耗内存。</p></blockquote><h2 id="内存分区"><a href="#内存分区" class="headerlink" title="内存分区"></a>内存分区</h2><p>在iOS程序中,内存通常被分为如下5个区域(内核区及保留区暂不探讨):</p><ul><li><p>栈区(stack):栈用于维护函数调用的上下文,离开了栈,函数调用就没法实现,栈保存了一个函数调用所需要维护的信息,这些信息通常被称为堆栈帧。由编译器自动分配释放,存放函数的参数值,局部变量的值等,操作方式类似于数据结构中的栈,属于线性结构,内存连续。栈通常在内存空间的最高地址处分配,通常有数兆字节的大小。</p></li><li><p>堆区(heap):堆是用来容纳应用动态程序动态分配的内存区域, 如<code>Objective-C</code>中<code>new</code> 、<code>alloc</code> 创建的对象, 由程序员分配释放,如果程序员不释放,在程序结束时由OS回收。属于链式结构,内存不连续,由于是动态分配,因此编译时不能提前确定。注意该堆区与数据结构中的堆是两码事。</p></li><li><p>BSS区(全局区、静态区):全局变量和静态变量的存储是放在一块的。<strong>初始化的</strong>全局变量和静态变量在一块区域,<strong>未初始化的</strong>全局变量和静态变量在相邻的另一块区域。</p></li><li><p>常量区:存储一些常量,如常量字符串等。</p></li><li><p>代码区:用于存放程序运行时的代码,代码会被编译成二进制存进内存的程序代码区。</p><p>以上5个内存分区中,除了堆区需要开发者手动进行内存管理外,其他区域都在程序结束后,由系统释放。</p><p>接下来通过代码进一步探索常用的数据存放的区域:</p><p><strong>栈区:</strong>通常存放一些指针及常见的数据类型,地址以<code>ox7</code>开始。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">// 栈区示例</span><br><span class="line">int a = 0;</span><br><span class="line">NSUInteger b = 1;</span><br><span class="line">NSNumber *c = @3;</span><br><span class="line">NSObject *obj = [[NSObject alloc] init];</span><br><span class="line">NSLog(@"a的地址:%p", &a);</span><br><span class="line">NSLog(@"b的地址:%p", &b);</span><br><span class="line">NSLog(@"c的地址:%p", &c);</span><br><span class="line">NSLog(@"obj的地址:%p", &obj);</span><br><span class="line">NSLog(@"a占用的内存:%lu",sizeof(a));</span><br><span class="line">NSLog(@"c占用的内存:%lu",sizeof(c));</span><br><span class="line">NSLog(@"obj占用的内存:%lu",sizeof(&obj));</span><br></pre></td></tr></table></figure><p>打印如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">2020-10-17 21:34:12.366339+0800 内存管理[18451:645349] a的地址:0x7ffee0b241dc</span><br><span class="line">2020-10-17 21:34:12.366620+0800 内存管理[18451:645349] b的地址:0x7ffee0b241d0</span><br><span class="line">2020-10-17 21:34:12.367156+0800 内存管理[18451:645349] c的地址:0x7ffee0b241c8</span><br><span class="line">2020-10-17 21:34:12.367313+0800 内存管理[18451:645349] obj的地址:0x7ffee0b241c0</span><br><span class="line">2020-10-17 21:34:12.367439+0800 内存管理[18451:645349] a占用的内存:4</span><br><span class="line">2020-10-17 21:34:12.367706+0800 内存管理[18451:645349] c占用的内存:8</span><br><span class="line">2020-10-17 21:34:12.367972+0800 内存管理[18451:645349] obj占用的内存:8</span><br></pre></td></tr></table></figure><p><strong>堆区:</strong>一般用于存放<code>alloc</code> 、<code>new</code>等创建的对象,地址以<code>0x6</code>开始</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">NSObject *obj1 = [NSObject new];</span><br><span class="line">NSObject *obj2 = [[NSObject alloc] init];</span><br><span class="line">Person *person1 = [[Person alloc] init];</span><br><span class="line">Person *person2 = [person1 copyWithZone: NULL];</span><br><span class="line">NSLog(@"obj1 = %@", obj1);</span><br><span class="line">NSLog(@"obj2 = %@", obj2);</span><br><span class="line">NSLog(@"person1 = %@", person1);</span><br><span class="line">NSLog(@"person2 = %@", person2);</span><br></pre></td></tr></table></figure><p>打印如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">2020-10-17 22:52:45.182268+0800 内存管理[19235:684688] obj1 = <NSObject: 0x600001edc3e0></span><br><span class="line">2020-10-17 22:52:45.182403+0800 内存管理[19235:684688] obj2 = <NSObject: 0x600001edc3f0></span><br><span class="line">2020-10-17 22:52:45.182559+0800 内存管理[19235:684688] person1 = <Person: 0x600001edc410></span><br><span class="line">2020-10-17 22:52:45.182868+0800 内存管理[19235:684688] person2 = <Person: 0x600001edc430></span><br></pre></td></tr></table></figure><p><strong>BSS区</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 未初始化</span><br><span class="line">int m;</span><br><span class="line">static int n;</span><br><span class="line">static NSString *str1;</span><br><span class="line">// 已初始化</span><br><span class="line">int p = 1;</span><br><span class="line">static int q = 2;</span><br><span class="line">static NSString *str2 = @"test";</span><br></pre></td></tr></table></figure><p>打印如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">2020-10-17 23:44:26.326518+0800 内存管理[19834:721060] m = 0x103e3b778</span><br><span class="line">2020-10-17 23:44:26.327054+0800 内存管理[19834:721060] n = 0x103e3b780</span><br><span class="line">2020-10-17 23:44:26.327252+0800 内存管理[19834:721060] str1 = 0x103e3b788</span><br><span class="line">2020-10-17 23:44:26.327944+0800 内存管理[19834:721060] p = 0x103e3b5e0</span><br><span class="line">2020-10-17 23:44:26.328244+0800 内存管理[19834:721060] q = 0x103e3b5f0</span><br><span class="line">2020-10-17 23:44:26.328675+0800 内存管理[19834:721060] str2 = 0x103e3b5e8</span><br></pre></td></tr></table></figure></li></ul><blockquote><p>当一个 app 启动后,全局区、常量区、代码区的大小就已经固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错)。</p></blockquote><h2 id="内存管理方案"><a href="#内存管理方案" class="headerlink" title="内存管理方案"></a>内存管理方案</h2><p>内存管理方案可以理解为内存优化方案,是Apple针对系统架构的演变而提出的一系列优化内存的解决方案。iOS的内存管理方案有三种:</p><h3 id="一、Tagged-Pointer"><a href="#一、Tagged-Pointer" class="headerlink" title="一、Tagged Pointer"></a>一、Tagged Pointer</h3><blockquote><p>在2013年9月,Apple发布了iPhone5S,而5S是首部采用64位架构的A7双核处理器,为了节省内存和提高执行效率,Apple提出了<code>Tagged Pointer</code>的概念,可以在<a href="https://developer.apple.com/videos/play/wwdc2013/404/" target="_blank" rel="noopener">WWDC2013 Advances in Objective-C Session 404</a>了解。</p></blockquote><h4 id="概述-1"><a href="#概述-1" class="headerlink" title="概述"></a>概述</h4><p>关于<code>Tagged Pointer</code>,Apple官方给出的解释如下图:<img src="./../images/taggedPointer.png" alt="taggedPointer"></p><p>业内大佬<a href="https://blog.devtang.com" target="_blank" rel="noopener">唐巧</a>有一篇文章<a href="https://blog.devtang.com/2014/05/30/understand-tagged-pointer/" target="_blank" rel="noopener">深入理解Tagged Pointer</a>有了详细的描述,有兴趣的可以去阅读下。这里可以概括下:Apple引入<code>Tagged Pointer</code>原因是从32位机器迁移到64位的机器上,虽然逻辑没有任何变化,但是像这种简单的数据类型如NSNumber、NSDate以及NSString类型(简短的字符串)一类对象所占的内存会翻倍。正常情况下,如果这个整数只是一个 NSInteger 的普通变量,那么它所占用的内存是与 CPU 的位数有关,在 32 位 CPU 下占 4 个字节,在 64 位 CPU 下是占 8 个字节的。而指针类型的大小通常也是与 CPU 位数相关,一个指针所占用的内存在 32 位 CPU 下为 4 个字节,在 64 位 CPU 下也是 8 个字节。通过以下两张图更直观的了解下:</p><p> <strong>未引入<code>Tagged Pointer</code>时:</strong></p><p><img src="./../images/tagged_pointer_before.jpg" alt="tagged_pointer_before"></p><p> <strong>引入<code>Tagged Pointer</code>后:</strong></p><p><img src="./../images/tagged_pointer_after.jpg" alt="tagged_pointer_after"></p><p>通过以上的初步了解可以知道:</p><ul><li><code>Tagged Pointer</code>是专门用来存储小的对象,例如NSNumber和NSDate;</li><li><code>Tagged Pointer</code>指针的值不再是地址了,而是真正的值。因此,实际上它不再是一个对象,可以称为<strong>伪指针</strong> ,它的内存并不存在堆中,因此不需要<code>malloc</code>和<code>free</code>。</li><li><code>NSNumber</code>等对象的值直接存储在了指针中,不必在堆上为其分配内存,节省了很多内存开销。在性能上,有着 3 倍空间效率的提升以及 106 倍创建和销毁速度的提升。</li></ul><blockquote><p>备注:需要在Xcode中target对应的Edit Scheme->Arguments->Environment Variables中关闭<code>Tagged Pointer</code>的数据混淆,即通过设置环境变量<code>OBJC_DISABLE_TAG_OBFUSCATION</code>为<code>YES</code>,以方便调试程序。可以在objc-env.h文件中找到更多环境变量。</p></blockquote><p>示例如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">NSNumber *num1 = @1;</span><br><span class="line">NSNumber *num2 = @2;</span><br><span class="line">NSNumber *num3 = @3;</span><br><span class="line">NSNumber *num4 = @(0xEFFFFFFFFFFFFFFF);</span><br></pre></td></tr></table></figure><p>打印:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 最后一位为标志位2</span><br><span class="line">2020-11-02 23:44:33.593252+0800 内存管理[21187:644448] 0xb000000000000012</span><br><span class="line">2020-11-02 23:44:33.593884+0800 内存管理[21187:644448] 0xb000000000000022</span><br><span class="line">2020-11-02 23:44:33.594003+0800 内存管理[21187:644448] 0xb000000000000032</span><br><span class="line">2020-11-02 23:44:33.594244+0800 内存管理[21187:644448] 0x600003260000</span><br></pre></td></tr></table></figure><p>通过以上实例可知,NSNumber在数值小的情况下,确实将值存在了指针中,当8字节可以承载用于表示的数值时,系统就会以Tagged Pointer的方式生成指针,如果8字节承载不了时,则又以以前的方式来生成普通的指针。</p><p>有了初步了解后,接下来进一步探究Tagged Pointer原理:</p><h4 id="Tagged-Pointer进一步探究"><a href="#Tagged-Pointer进一步探究" class="headerlink" title="Tagged Pointer进一步探究"></a>Tagged Pointer进一步探究</h4><h5 id="Tagged-Pointer的创建"><a href="#Tagged-Pointer的创建" class="headerlink" title="Tagged Pointer的创建"></a>Tagged Pointer的创建</h5><p>在runtime源码对应的头文件<code>objc-internal.h</code>中,有个<code>_objc_makeTaggedPointer</code>的函数,该函数说明了<code>Tagged Pointer</code>的创建:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">// 创建(代码经过整理)</span><br><span class="line">static inline void * _Nonnull</span><br><span class="line">_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)</span><br><span class="line">{</span><br><span class="line"> if (tag <= OBJC_TAG_Last60BitPayload) {</span><br><span class="line"> uintptr_t result =</span><br><span class="line"> (_OBJC_TAG_MASK | </span><br><span class="line"> ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | </span><br><span class="line"> ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));</span><br><span class="line"> return _objc_encodeTaggedPointer(result); // 编码</span><br><span class="line"> } else {</span><br><span class="line"> uintptr_t result =</span><br><span class="line"> (_OBJC_TAG_EXT_MASK |</span><br><span class="line"> ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |</span><br><span class="line"> ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));</span><br><span class="line"> return _objc_encodeTaggedPointer(result); //</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 编码</span><br><span class="line">static inline void * _Nonnull</span><br><span class="line">_objc_encodeTaggedPointer(uintptr_t ptr)</span><br><span class="line">{</span><br><span class="line"> return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);</span><br><span class="line">}</span><br><span class="line">// 解码</span><br><span class="line">static inline uintptr_t</span><br><span class="line">_objc_decodeTaggedPointer(const void * _Nullable ptr)</span><br><span class="line">{</span><br><span class="line"> return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过阅读源码发现,系统对<code>Tagged Pointer</code>进行了<code>_objc_encodeTaggedPointer</code>编码操作,该编码的实现就是对value进行了<code>objc_debug_taggedpointer_obfuscator</code>的异或操作,而在读取<code>Tagged Pointer</code>的时候,通过<code>_objc_decodeTaggedPointer</code>进行了解码,同样进行了<code>objc_debug_taggedpointer_obfuscator</code>的异或操作,这样进行了两次异或操作就还原了初始值。接下来通过代码进行验证:</p><blockquote><p>验证时,头文件中需要引入<code>\#import <objc/runtime.h></code>,以及将代码复制到文件中:</p><p><strong>extern</strong> uintptr_t objc_debug_taggedpointer_obfuscator;</p><p><strong>static</strong> <strong>inline</strong> uintptr_t</p><p>_objc_decodeTaggedPointer(<strong>const</strong> <strong>void</strong> * <strong>_Nullable</strong> ptr)</p><p>{</p><p><strong>return</strong> (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;</p><p>}</p></blockquote><p>代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">NSNumber *num1 = @(2);</span><br><span class="line"> NSNumber *num2 = @(10);</span><br><span class="line"> NSNumber *num3 = @(12);</span><br><span class="line"> NSNumber *num4 = @(15);</span><br><span class="line"> NSNumber *num5 = @(0xFFFFFFFFFFFFFFFF);</span><br><span class="line"> </span><br><span class="line"> NSLog(@"num1 = %@ - %p - 0x%lx", num1, &num1, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(num1)));</span><br><span class="line"> NSLog(@"num2 = %@ - %p - 0x%lx", num2, &num2, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(num2)));</span><br><span class="line"> NSLog(@"num3 = %@ - %p - 0x%lx", num3, &num3, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(num3)));</span><br><span class="line"> NSLog(@"num4 = %@ - %p - 0x%lx", num4, &num4, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(num4)));</span><br><span class="line"> NSLog(@"num5 = %@ - %p - 0x%lx", num5, &num5, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(num5)));</span><br></pre></td></tr></table></figure><p>打印如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">2020-11-04 23:38:26.898354+0800 内存管理[24395:828091] num1 = 2 - 0x7ffee79f91a8 - 0xb000000000000022</span><br><span class="line">2020-11-04 23:38:26.898519+0800 内存管理[24395:828091] num2 = 10 - 0x7ffee79f91a0 - 0xb0000000000000a2</span><br><span class="line">2020-11-04 23:38:26.898631+0800 内存管理[24395:828091] num3 = 12 - 0x7ffee79f9198 - 0xb0000000000000c2</span><br><span class="line">2020-11-04 23:38:26.898772+0800 内存管理[24395:828091] num4 = 15 - 0x7ffee79f9190 - 0xb0000000000000f2</span><br><span class="line">2020-11-04 23:38:26.898887+0800 内存管理[24395:828091] num5 = 18446744073709551615 - 0x7ffee79f9188 - 0x60000357efa0</span><br></pre></td></tr></table></figure><p>通过以上代码可知,<code>num1</code>~<code>num4</code>指针为<code>Tagged Pointer</code>类型,对象的值都存在了指针中,对应的倒数第二位即是对应的值。而<code>num5</code>由于数据过大,指针的<code>8</code>个字节不够存,因此在堆中分配了内存。最后一位用来表示数据类型。第一位<code>b</code>的二进制数值为<code>1011</code>,其中第一位为<code>Tagged Pointer</code>的标识位。根据<code>iOS</code>下<code>NSNumber</code>的<code>Tagged Pointer</code>位视图可更直观的了解:</p><p><img src="./../images/numberlocation.png" alt="numberlocation"></p><p>后面的<code>011</code>表示类标识位,对应的十进制数值为<code>3</code>,表示<code>NSNumber</code>类,在runtime源码对应的文件<code>nal.h</code>可找到对应的描述:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> // 60-bit payloads</span><br><span class="line"> OBJC_TAG_NSAtom = 0, </span><br><span class="line"> OBJC_TAG_1 = 1, </span><br><span class="line"> OBJC_TAG_NSString = 2, //NSString</span><br><span class="line"> OBJC_TAG_NSNumber = 3, //NSNumber</span><br><span class="line"> OBJC_TAG_NSIndexPath = 4, //NSIndexPath</span><br><span class="line"> OBJC_TAG_NSManagedObjectID = 5, </span><br><span class="line"> OBJC_TAG_NSDate = 6,//NSDate</span><br><span class="line"></span><br><span class="line"> // 60-bit reserved</span><br><span class="line"> OBJC_TAG_RESERVED_7 = 7, //保留位</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>同理,针对NSString:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">NSString * str1 = [NSString stringWithFormat:@"a"];</span><br><span class="line"> NSString * str2 = [NSString stringWithFormat:@"ab"];</span><br><span class="line"> NSString * str3 = [NSString stringWithFormat:@"abc"];</span><br><span class="line"> NSString * str4 = [NSString stringWithFormat:@"abcd"];</span><br><span class="line"> </span><br><span class="line"> NSLog(@"str1 = %@ - %p - 0x%lx",str1, &str1, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(str1)));</span><br><span class="line"> NSLog(@"str1 = %@ - %p - 0x%lx",str2, &str2, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(str2)));</span><br><span class="line"> NSLog(@"str1 = %@ - %p - 0x%lx",str3, &str3, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(str3)));</span><br><span class="line"> NSLog(@"str1 = %@ - %p - 0x%lx",str4, &str4, _objc_decodeTaggedPointer((__bridge const void * _Nullable)(str4)));</span><br></pre></td></tr></table></figure><p>打印如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">2020-11-04 23:38:26.899029+0800 内存管理[24395:828091] str1 = a - 0x7ffee79f9180 - 0xa000000000000611</span><br><span class="line">2020-11-04 23:38:26.899150+0800 内存管理[24395:828091] str1 = ab - 0x7ffee79f9178 - 0xa000000000062612</span><br><span class="line">2020-11-04 23:38:26.899272+0800 内存管理[24395:828091] str1 = abc - 0x7ffee79f9170 - 0xa000000006362613</span><br><span class="line">2020-11-04 23:38:26.899388+0800 内存管理[24395:828091] str1 = abcd - 0x7ffee79f9168 - 0xa000000646362614</span><br></pre></td></tr></table></figure><p>通过分析日志,可知字符串类型解压出来的值,最后一位代表的是字符串长度,而<code>61、62、63、64</code>对应的是ASCII的<code>a、b、c、d</code>。</p><p>在iOS中<code>NSString</code>的<code>TaggedPointer</code>的位视图如下:</p><p><img src="./../images/stringlocation.png" alt="stringlocation"></p><h5 id="WWDC2020-Tagged-Pointer的改变"><a href="#WWDC2020-Tagged-Pointer的改变" class="headerlink" title="WWDC2020 Tagged Pointer的改变"></a>WWDC2020 Tagged Pointer的改变</h5><blockquote><ul><li><p><code>MacOS</code>下采用 LSB(Least Significant Bit,即最低有效位)为<code>Tagged Pointer</code>标识位;</p></li><li><p><code>iOS</code>下则采用 MSB(Most Significant Bit,即最高有效位)为<code>Tagged Pointer</code>标识位;</p></li></ul></blockquote><p>在今年<a href="https://developer.apple.com/videos/play/wwdc2020/10163/" target="_blank" rel="noopener">WWDC20</a>(视频14:51开始)中,Apple官方对runtime进行了一些优化,其中包括对<code>Tagged Pointer</code> 格式的变化。接下来进一步介绍下:</p><p>在Intel处理器中,当我们查看对象指针时,在64位系统中,将一个16进制的地址如<code>0x00000001003041e0</code>转为二进制表示如下图:</p><p><img src="./../images/intel64.png" alt="intel64"></p><p>在64位操作系统中,可以使用64位表示一个对象指针,但是通常并没有真正使用所有这些位,由于内存对其要求的存在,低位始终为0,对象必须始终位于指针大小倍数的地址中,由于地址空间有限,高位也始终为0,实际上用不到2^64地址,我们只是使用了中间这一部位分的位:</p><p><img src="./../images/usemiddle.png" alt="usemiddle"></p><p>所以可以从这些始终为0的位中选择一位并把它设置为1,我们可以把最低位设置为 1,表示这个对象是一个 <code>Tagged Pointer</code> 对象,设置为 0 则表示为正常的对象:</p><p><img src="./../images/lastlocation.png" alt="lastlocation"></p><p>在最后一位设置为1表示为<code>TaggedPointer</code>对象后,在最低位的最后3位,我们给他赋予类型意义,由于只有 3 位,所以它可以表示 7 种数据类型:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 60-bit payloads</span><br><span class="line"> OBJC_TAG_NSAtom = 0, </span><br><span class="line"> OBJC_TAG_1 = 1, </span><br><span class="line"> OBJC_TAG_NSString = 2, //NSString</span><br><span class="line"> OBJC_TAG_NSNumber = 3, //NSNumber</span><br><span class="line"> OBJC_TAG_NSIndexPath = 4, //NSIndexPath</span><br><span class="line"> OBJC_TAG_NSManagedObjectID = 5, </span><br><span class="line"> OBJC_TAG_NSDate = 6,//NSDate</span><br><span class="line"></span><br><span class="line"> // 60-bit reserved</span><br><span class="line"> OBJC_TAG_RESERVED_7 = 7, //保留位</span><br></pre></td></tr></table></figure><p>在剩余的字段中,我们可以赋予他所包含的数据,在 Intel 中,我们 Tagged Pointer 对象的表示如下:</p><p><img src="./../images/tagpayload.png" alt="tagpayload"></p><p><code>OBJC_TAG_RESERVED_7</code>类型的是个例外,目前属于保留类型,它可以将接下来后 8 位作为它的扩展类型字段,基于此我们可以多支持 256 中类型的 Tagged Pointer,如 UIColors 或 NSIndexSets 之类的对象。</p><p><img src="./../images/tag7ext.png" alt="tag7ext"></p><p>在arm64上<code>Tagged Pointer</code>的格式有了些变化:</p><p><img src="./../images/arm64tag.png" alt="arm64tag"></p><p>我们使用最高位代表 Tagged Pointer 标识位,最低位 3 位标识 Tagged Pointer 的类型,接下去的位来表示包含的数据(可能包含扩展类型字段),为什么我们使用高位指示 ARM上 的 Tagged Pointer,而不是像 Intel 一样使用低位标记?</p><p>它实际是对 objc_msgSend 的微小优化。我们希望 msgSend 中最常用的路径尽可能快。最常用的路径表示普通对象指针。我们有两种不常见的情况:Tagged Pointer 指针和 nil。事实证明,当我们使用最高位时,可以通过一次比较来检查两者。与分别检查 nil 和 Tagged Pointer 指针相比,这会为 msgSend 中的节省了条件分支。</p><h4 id="如何判断Tagged-Pointer"><a href="#如何判断Tagged-Pointer" class="headerlink" title="如何判断Tagged Pointer"></a>如何判断Tagged Pointer</h4><p>在runtime源码中的<code>objc-internal.h</code>文件中,我们可以看到判断<code>TaggedPointer</code>指针的实现:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">static inline bool </span><br><span class="line">_objc_isTaggedPointer(const void * _Nullable ptr)</span><br><span class="line">{</span><br><span class="line"> return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上代码的含义是 将一个指针地址<code>ptr</code>和 _OBJC_TAG_MASK 常量做<code>&</code>运算:判断该指针的最高位或者最低位为 1,那么这个指针就是 Tagged Pointer。</p><p>上文备注中已经讲到,对于 iOS 系统而言,遵循 MSB 规则(高位优先),因此<code>_OBJC_TAG_MASK</code>的值为 <code>0x8000000000000000</code>:一个64 位的二进制,最左边一位是 1,其余位全是 0。</p><p>在 64 位系统中,使用指针很难将有限的 CPU 资源耗尽;因此 64 位还有很大的剩余!。苹果将64中的最左边一位(MSB 时)标记是 1 ,或者最右边一位(LSB 时,Mac系统)标记是 1 ,以此来表示这个指针是 <code>Tagged Pointer</code> 。通过源码中的宏定义可知:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#if OBJC_MSB_TAGGED_POINTERS</span><br><span class="line"># define _OBJC_TAG_MASK (1UL<<63)</span><br><span class="line">#else</span><br><span class="line"># define _OBJC_TAG_MASK 1UL</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><p>因此 <code>ptr & _OBJC_TAG_MASK)</code> 按位与 运算后可以判断它的标志位是否是 1,即是否是 <code>Tagged Pointer</code> 。</p><h4 id="Tagged-Pointer注意事项"><a href="#Tagged-Pointer注意事项" class="headerlink" title="Tagged Pointer注意事项"></a>Tagged Pointer注意事项</h4><ul><li><p>所有的OC对象都有<code>isa</code>指针,但是<code>Tagged Pointer</code>并不是真正的对象,因此没有<code>isa</code>指针。如果直接访问<code>Tagged Pointer</code>的<code>isa</code>成员,编译器会出现一些警告或错误,所以应当避免直接访问<code>Tagged Pointer</code>的<code>isa</code>,可以使用<code>isKindOfClass</code>和<code>object_getClass</code>等代替。</p><p>在通过LLDB打印<code>Tagged Pointer</code>的<code>isa</code>时,也会提示如下的错误:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(lldb) po str2->isa</span><br><span class="line">error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory</span><br></pre></td></tr></table></figure></li></ul><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><ul><li><a href="https://anyeler.top/2018/08/11/Objective-C对象的TaggedPointer特性/" target="_blank" rel="noopener">Tagged Pointer</a></li><li><a href="https://www.sweetloser.com/2019/11/28/tagged-Pointer/" target="_blank" rel="noopener">源码探究Tagged Pointer</a></li><li><a href="https://juejin.im/post/6844904132940136462#heading-10" target="_blank" rel="noopener">TaggedPointer分析</a></li><li><a href="https://developer.apple.com/videos/play/wwdc2020/10163/" target="_blank" rel="noopener">WWDC20-Advancements in the Objective-C runtime</a></li><li><a href="https://blog.devtang.com/2014/05/30/understand-tagged-pointer/" target="_blank" rel="noopener">唐巧-深入理解Tagged Pointer</a></li><li><a href="https://www.jianshu.com/p/3176e30c040b" target="_blank" rel="noopener">聊聊伪指针TaggedPointer</a></li><li><a href="https://juejin.im/post/6844904132940136462#heading-16" target="_blank" rel="noopener">Tagged Pointer探究</a></li><li><a href="https://juejin.im/post/6844904064820445191#heading-2" target="_blank" rel="noopener">iOS性能优化</a></li><li><a href="https://www.liebenschnee.com/2020/04/02/%E8%B0%83%E8%AF%95Runtime%E6%BA%90%E7%A0%81/" target="_blank" rel="noopener">调试Runtime源码</a></li><li><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011i" target="_blank" rel="noopener">About Memory Management</a></li></ul><h3 id="二、nonpointer-isa-非指针类型的isa"><a href="#二、nonpointer-isa-非指针类型的isa" class="headerlink" title="二、nonpointer_isa(非指针类型的isa)"></a>二、nonpointer_isa(非指针类型的isa)</h3><p> <code>nonpointer_isa</code>是Apple优化内存的方案之一。<code>isa</code>是个8字节(64)位的指针,如果只用来<code>isa</code>指向显然是一种浪费,因此可优化存储方案,用一部分额外空间存储其他内容,可以在runtime源码中验证这些信息: </p><p>在之前的版本中,我们看到的源码是这样的:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">typedef struct objc_object *id</span><br><span class="line">struct objc_object {</span><br><span class="line"> Class _Nonnull isa;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从当时的源码中可以知道实例对象的指针<code>isa</code>都是指向类对象。但是在现在的版本中,已经发生了改变:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">struct objc_object {</span><br><span class="line">private:</span><br><span class="line"> isa_t isa;</span><br><span class="line"></span><br><span class="line">public:</span><br><span class="line"></span><br><span class="line"> // ISA() assumes this is NOT a tagged pointer object</span><br><span class="line"> Class ISA();</span><br><span class="line"></span><br><span class="line"> // getIsa() allows this to be a tagged pointer object</span><br><span class="line"> Class getIsa();</span><br><span class="line"> ......</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>所以实例对象的<code>isa</code>都指向类对象的说法,目前来看是不对的。现在的实例对象的<code>isa</code>是一个<code>isa_t</code>类型的联合体,里面存放了很多其他的东西。在ARM64环境下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"> union isa_t {</span><br><span class="line"> isa_t() { }</span><br><span class="line"> isa_t(uintptr_t value) : bits(value) { }</span><br><span class="line"></span><br><span class="line"> Class cls;</span><br><span class="line"> uintptr_t bits;</span><br><span class="line">#if defined(ISA_BITFIELD)</span><br><span class="line"> struct {</span><br><span class="line"> ISA_BITFIELD; // defined in isa.h</span><br><span class="line"> };</span><br><span class="line">#endif</span><br><span class="line">};</span><br><span class="line"> 其中结构体ISA_BITFIELD包含:</span><br><span class="line"> uintptr_t nonpointer : 1; \</span><br><span class="line"> uintptr_t has_assoc : 1; \</span><br><span class="line"> uintptr_t has_cxx_dtor : 1; \</span><br><span class="line"> uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \</span><br><span class="line"> uintptr_t magic : 6; \</span><br><span class="line"> uintptr_t weakly_referenced : 1; \</span><br><span class="line"> uintptr_t deallocating : 1; \</span><br><span class="line"> uintptr_t has_sidetable_rc : 1; \</span><br><span class="line"> uintptr_t extra_rc : 19</span><br></pre></td></tr></table></figure><p>关于一些参数说明如下:</p><ul><li><code>nonpointer</code>: 表示是否对isa开启指针优化。0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等;</li><li><code>has_assoc</code>: 关联对象标志位,0没有,1存在;</li><li><code>has_cxx_dtor</code>: 该对象是否有C++或Objc的析构器,如果有析构函数,则需要做一些析构的逻辑处理,如果没有,则可以更快的释放对象;</li><li><code>shiftcls</code>: 存放着 Class、Meta-Class 对象的内存地址信息</li><li><code>magic</code>:用于在调试时分辨对象是否未完成初始化;</li><li><code>weakly_referenced</code>: 是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快;</li><li><code>deallocating</code>: 标志是否正在释放内存;</li><li><code>has_sidetable_rc</code>: 是否有辅助的引用计数散列表。当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位;</li><li><code>extra_rc</code>: 表示该对象的引⽤计数值,实际上是引⽤计数值减 1,例如,如果对象的引⽤计数为 20,那么 extra_rc 为 19。如果引⽤计数⼤于 10,则需要使⽤到下⾯的 has_sidetable_rc。</li></ul><p><code>isa</code>的赋值在<code>alloc</code>方法调用时,内部会进入<code>initIsa()</code>方法。</p><p>关于对象是否不启用<code>nonpointer-isa</code>目前有如下几个判断条件:</p><ul><li>包含<code>Swift</code>代码;</li><li>SDK版本低于<code>10.11</code>;</li><li>runtime读取image时发现这个image包含<code>__objc_rawisa</code>段;</li><li>调试时,在环境变量中添加了OBJC_DISABLE_NONPOINTER_ISA=YES;</li><li>某些不能使用non-pointer的类,如GCD等;</li><li>父类关闭。</li></ul><h3 id="三、SideTables(散列表)"><a href="#三、SideTables(散列表)" class="headerlink" title="三、SideTables(散列表)"></a>三、SideTables(散列表)</h3><blockquote><p>散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。即它通过一个关于键值得函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称作散列函数,存放记录的数组称作散列表。</p></blockquote><p>在runtime中,通过<code>SideTable</code>来管理对象的引用计数以及weak指针。如果该对象不是Tagged Pointer且关闭了Non-pointer,那该对象的引用计数就使用<code>SideTable</code>来存。Apple在系统中维护了一个全局的<code>SideTables</code>,里面的内容装的都是<code>SideTable</code>结构体。<code>SideTable</code>的结构如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">struct SideTable {</span><br><span class="line">// 自旋锁</span><br><span class="line"> spinlock_t slock;</span><br><span class="line"> // 强引用相关</span><br><span class="line"> RefcountMap refcnts;</span><br><span class="line"> // 弱引用相关</span><br><span class="line"> weak_table_t weak_table;</span><br><span class="line">// 以下为一些常见的操作:</span><br><span class="line"> SideTable() {</span><br><span class="line"> memset(&weak_table, 0, sizeof(weak_table));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ~SideTable() {</span><br><span class="line"> _objc_fatal("Do not delete SideTable.");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> void lock() { slock.lock(); }</span><br><span class="line"> void unlock() { slock.unlock(); }</span><br><span class="line"> void forceReset() { slock.forceReset(); }</span><br><span class="line"></span><br><span class="line"> // Address-ordered lock discipline for a pair of side tables.</span><br><span class="line"></span><br><span class="line"> template<HaveOld, HaveNew></span><br><span class="line"> static void lockTwo(SideTable *lock1, SideTable *lock2);</span><br><span class="line"> template<HaveOld, HaveNew></span><br><span class="line"> static void unlockTwo(SideTable *lock1, SideTable *lock2);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>每一张<code>SideTable</code>主要由三部分组成:自旋锁、引用计数表、弱引用表:</p><ul><li><p>spinlock_t:自旋锁,用于加锁/解锁散列表;</p><ul><li>自旋锁比较适用于锁的使用者保持锁时间比较短的情况,自旋锁的效率远高于互斥锁,而引用计数的操作又非常快,因此选择自旋锁非常有必要。</li><li>自旋锁适用于小型数据、耗时很少的操作,速度很快。</li></ul></li><li><p>RefcountMap:用来存储OC对象的引用计数的哈希表(只在未开启isa优化或在isa优化的情况下,isa的引用计数溢出时才会用到);</p></li><li><p>weak_table_t:存储对象弱应用指针的哈希表。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">struct weak_table_t {</span><br><span class="line"> weak_entry_t *weak_entries;</span><br><span class="line"> size_t num_entries;</span><br><span class="line"> uintptr_t mask;</span><br><span class="line"> uintptr_t max_hash_displacement;</span><br><span class="line">};</span><br></pre></td></tr></table></figure></li></ul><p> <strong>如何进行引用计数操作</strong></p><p> 当需要去查找一个对象对应的<code>SideTable</code>并进行引用计数或者弱引用计数的操作时,系统是如何实现的呢?</p><p> 当一个对象访问<code>SideTables</code>时:</p><ul><li>1、首先会取得对象的地址,将地址进行哈希运算,与<code>Sidetables</code>中<code>SideTable</code>的个数取余,最后得到的结果就是该对象所要访问的<code>SideTable</code>;</li><li>2、在取得的<code>SideTable</code>中的<code>RefcountMap</code>表中再进行一次哈希查找,找到该对象在引用计数表中对应的位置。</li><li>3、如果该位置存在对应的引用计数,则对其进行操作,如果没有对应的引用计数,则创建一个对应的<code>size_t</code>对象,实质就是一个<code>uint</code>类型的无符号整型。</li></ul><p>以上就是Apple为了更好的管理内存而使用的三种不同的内存管理方案,在内部采用不同的数据结构以达到更高效的内存检索。</p><blockquote><p>拓展:NSZone是什么呢?它是为防止内存碎片化而引入的结构。对内存分配的区域进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高内存管理的效率。目前Apple官方在现在的运行时系统简单地忽略了区域的概念。因为运行时系统中的内存管理本身以极具效率,使用区域来管理内存反而会引起内存使用效率低下以及源码复杂化等问题。</p><p>可能正是由于上述的内存管理策略,让Apple弱化了区域的概念,在runtime源码中,NSZone涉及到的值也几乎都是被忽略,如源码中经常看到的备注<code>ignores the zone parameter</code>。</p></blockquote><h2 id="引用计数"><a href="#引用计数" class="headerlink" title="引用计数"></a>引用计数</h2><blockquote><p>维基百科:<strong>引用计数</strong>是计算机<a href="https://zh.wikipedia.org/wiki/编程语言" target="_blank" rel="noopener">编程语言</a>中的一种<strong><a href="https://zh.wikipedia.org/wiki/内存管理" target="_blank" rel="noopener">内存管理</a>技术</strong>,是指将资源(可以是<a href="https://zh.wikipedia.org/wiki/对象" target="_blank" rel="noopener">对象</a>、<a href="https://zh.wikipedia.org/wiki/内存" target="_blank" rel="noopener">内存</a>或<a href="https://zh.wikipedia.org/wiki/磁碟" target="_blank" rel="noopener">磁盘</a>空间等等)的被<a href="https://zh.wikipedia.org/wiki/引用" target="_blank" rel="noopener">引用</a>次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的<a href="https://zh.wikipedia.org/wiki/垃圾回收" target="_blank" rel="noopener">垃圾回收</a>算法。</p><p>当创建一个对象的实例并在堆上申请内存时,对象的引用计数就为1,在其他对象中需要持有这个对象时,就需要把该对象的引用计数加1,需要释放一个对象时,就将该对象的引用计数减1,直至对象的引用计数为0,对象的内存会被立刻释放。 备注:</p><p>01、开启或关闭ARC<br>Build Settings -> Objective-C Automatic Reference Counting,设置Yes或No<br>02、ARC工程,将某个文件设置为MRC<br>Build Phases -> Compile Sources,找到需要设置的文件,修改Compiler Flags为 -fno-objc-arc<br>03、MRC工程,将某一文件设置为ARC<br>Build Phases -> Compile Sources,找到需要设置的文件,修改Compiler Flags为 -fobjc-arc</p></blockquote><blockquote><p>小技巧:ARC 环境下获取引用计数的两种方式</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> // 桥接字方式</span><br><span class="line">> NSLog(@"Retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)obj));</span><br><span class="line">> // KVC方式</span><br><span class="line">> NSLog(@"Retain count: %@", [obj valueForKey:@"retainCount"]);</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>引用计数是Objective-C提供的一种动态的内存管理方式,这种方式会跟踪每个对象被引用的次数,当引用计数为0时,系统就会释放这个对象所占有的内存。接下来通过MRC来进一步了解引用计数:</p><h2 id="MRC"><a href="#MRC" class="headerlink" title="MRC"></a>MRC</h2><p>MRC(Mannul Reference Counting)即手动引用计数,属于早期的时候,通过开发者在代码中插入<code>retain</code>和<code>release</code>对对象进行引用计数的控制。MRC环境下,可以使用<code>retain</code>、<code>release</code>、<code>autorelease</code>、<code>retainCount</code>对对象进行引用计数计算和控制。示例如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"> // MRC</span><br><span class="line">Person * person = [[Person alloc] init]; // 引用计数:1</span><br><span class="line"> [person retain]; // 引用计数 +1</span><br><span class="line"> NSLog(@"retainCount: %lu",[person retainCount]);// 引用计数:2</span><br><span class="line"> [person release]; // 引用计数 -1</span><br><span class="line"> NSLog(@"retainCount: %lu",[person retainCount]);// 引用计数:1</span><br><span class="line"> [person release]; // 引用计数:-1</span><br><span class="line"> NSLog(@"retainCount: %lu",[person retainCount]);// 引用计数:0,此时对象被释放,会调用dealloc,不能再使用,否则会造成崩溃</span><br><span class="line"></span><br><span class="line">Person * p1 = [[Person alloc] init]; //引用计数为1</span><br><span class="line"> Person * p2 = nil;</span><br><span class="line"> p2 = p1; //⚠️:直接赋值并不能使引用计数+1</span><br><span class="line"> [p2 retain]; //只有给对象发送retain时,引用计数才会+1</span><br></pre></td></tr></table></figure><h2 id="内存管理的思考方式"><a href="#内存管理的思考方式" class="headerlink" title="内存管理的思考方式"></a>内存管理的思考方式</h2><p>通过对引用计数有了一些了解后,我们需要对内存管理有进一步的思考,对于内存管理,不应该将注意力放到引用计数上,而是需要通过更加客观、正确的思考方式,即:</p><ul><li>自己生成的对象,自己所持有;</li><li>非自己生成的对象,自己也能持有;</li><li>不再需要自己持有的对象需要及时释放;</li><li>非自己持有的对象无法释放。</li></ul><p>按照这个思路,完全不必考虑引用计数。</p><p>针对生成、持有、释放等名词,以及加上Objective-C内存管理中的”废弃”,在程序中的对应关系如下:</p><table><thead><tr><th style="text-align:left">对象操作</th><th>Objective-C方法</th></tr></thead><tbody><tr><td style="text-align:left">生成并持有对象</td><td>alloc/new/copy/mutableCopy等方法</td></tr><tr><td style="text-align:left">持有对象</td><td>retain方法</td></tr><tr><td style="text-align:left">释放对象</td><td>release方法</td></tr><tr><td style="text-align:left">废弃对象</td><td>dealloc方法</td></tr></tbody></table><p>🤔接下来详细介绍下这四种思考方式:</p><h3 id="自己生成的对象,自己持有"><a href="#自己生成的对象,自己持有" class="headerlink" title="自己生成的对象,自己持有"></a>自己生成的对象,自己持有</h3><p>使用<code>alloc/new/copy/mutableCopy</code>方法开头的方法名,意味着自己生成的对象只能自己持有:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 自己生成并持有对象</span><br><span class="line">id obj1 = [[NSObject alloc] init];</span><br><span class="line">// 自己生成并持有对象</span><br><span class="line">id obj2 = [NSObject new];</span><br></pre></td></tr></table></figure><p>上述方法均能表示自己生成并持有对象,两者效果完全一致。</p><p>同样,使用<code>copy</code>和<code>mutableCopy</code>也能“自己生成的对象,自己持有”。<code>copy</code>方法是基于<code>NSCopying</code>方法约定,由各类实现的<code>copyWithZone:</code>方法生成并持有对象的副本,而<code>mutableCopy</code>方法利用基于<code>NSMutableCopying</code>方法约定,由各类实现的<code>mutableCopyWithZone:</code>方法生成并持有对象的副本。两者的区别在于<code>copy</code>方法生成不可变的对象,而<code>mutableCopy</code>方法生成可变更的对象。</p><h4 id="拓展:深拷贝和浅拷贝"><a href="#拓展:深拷贝和浅拷贝" class="headerlink" title="拓展:深拷贝和浅拷贝"></a>拓展:深拷贝和浅拷贝</h4><p> 浅拷贝和深拷贝是一个很常见的问题,无论是在平时的开发过程中,还是在面试时,几乎都会遇到,当被问到该问题时,大部分的人都会回答说浅拷贝是指针的拷贝,深拷贝是内容的拷贝,这样回答当然没错,但如果被进一步问到浅拷贝和深拷贝是如何实现的呢?对象中的属性是如何拷贝的?集合的拷贝以及集合中的对象如何拷贝呢?等等,如果对以上的问题有些许疑惑,接下来我们一起探索一下。</p><p> 首先,对象的拷贝涉及到两个方法<code>copy</code>和<code>mutableCopy</code>, 如果自定义的对象使用这个两个方法,首先需要遵守<code>NSCopying</code>、<code>NSMutableCopying</code>协议,并实现各自对应的方法<code>copyWithZone:</code>和<code>mutableCopyWithZone:</code>通过运行时的源码<code>NSObject.mm</code>中,可以了解到两者的实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">+ (id)copyWithZone:(struct _NSZone *)zone {</span><br><span class="line"> return (id)self;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (id)copy {</span><br><span class="line"> return [(id)self copyWithZone:nil];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (id)mutableCopyWithZone:(struct _NSZone *)zone {</span><br><span class="line"> return (id)self;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (id)mutableCopy {</span><br><span class="line"> return [(id)self mutableCopyWithZone:nil];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>copy</code>和<code>mutableCopy</code>两个方法只是简单的调用了<code>copyWithZone:</code>和<code>mutableCopyWithZone:</code>。两者的区别<code>copy</code>方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使对象本身是可修改的。例如,NSMutableString调用copy方法,将会返回不可修改的字符串对象。<code>mutableCopy</code>方法用于复制对象的可变副本。通常来说,<code>mutableCopy</code>方法总是返回对象可修改的副本,即使被复制的对象本身是不可修改的。</p><p><a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCopying.html" target="_blank" rel="noopener">Apple官方</a>针对浅拷贝和深拷贝的示意图如下:</p><p><img src="./../images/浅拷贝和深拷贝.png" alt="浅拷贝和深拷贝"></p><p>通过示意图可以初步了解到:浅拷贝的对象指向同一个地址,即指针的拷贝;深拷贝的对象指向不同的地址,即内容的拷贝。</p><p>Talk is cheap, show me the code.接下来通过具体的实践进一步了解分析<code>NSString</code>、<code>NSMutableString</code>以及自定义对象<code>TestModel</code>的拷贝:</p><h5 id="NSString"><a href="#NSString" class="headerlink" title="NSString"></a>NSString</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// NSString</span><br><span class="line">- (void)testStringCopy{</span><br><span class="line"> NSString *str = @"original value";</span><br><span class="line"> NSString *copyStr = [str copy];</span><br><span class="line"> NSMutableString *mutableCopyStr = [str mutableCopy];</span><br><span class="line"> NSLog(@"地址:%p 值:%@", str, str);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", copyStr, copyStr);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", mutableCopyStr, mutableCopyStr);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">2020-10-24 13:43:08.105253+0800 内存管理[33986:1618628] 地址:0x10cdcd160 值:original value</span><br><span class="line">2020-10-24 13:43:08.105371+0800 内存管理[33986:1618628] 地址:0x10cdcd160 值:original value</span><br><span class="line">2020-10-24 13:43:08.105490+0800 内存管理[33986:1618628] 地址:0x600000433e10 值:original value</span><br></pre></td></tr></table></figure><h5 id="NSMutableString"><a href="#NSMutableString" class="headerlink" title="NSMutableString"></a>NSMutableString</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">//NSMutableString</span><br><span class="line">- (void)testMutableCopy{</span><br><span class="line"> NSMutableString *str = [NSMutableString stringWithString:@"original value"];</span><br><span class="line"> NSMutableString *copyStr = [str copy];</span><br><span class="line"> NSMutableString *mutableCopyStr = [str mutableCopy];</span><br><span class="line"> NSLog(@"地址:%p 值:%@", str, str);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", copyStr, copyStr);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", mutableCopyStr, mutableCopyStr);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">2020-10-24 13:43:08.105712+0800 内存管理[33986:1618628] 地址:0x600000439fb0 值:original value</span><br><span class="line">2020-10-24 13:43:08.105815+0800 内存管理[33986:1618628] 地址:0x600000a30820 值:original value</span><br><span class="line">2020-10-24 13:43:08.105939+0800 内存管理[33986:1618628] 地址:0x60000043a2e0 值:original value</span><br></pre></td></tr></table></figure><p>通过以上结果分析可知:</p><ul><li>非可变字符串<code>NSString</code>通过<code>copy</code>对象后,生成的对象与原对象指向同一个地址,属于浅拷贝;通过<code>mutableCopy</code>生成的对象与原对象指向不同的地址,属于深拷贝。</li><li>可变字符串<code>NSMutableString</code>无论是通过<code>copy</code>还是<code>mutableCopy</code>,生成的对象均指向不同的地址,属于深拷贝。</li></ul><h5 id="TestModel对象的拷贝"><a href="#TestModel对象的拷贝" class="headerlink" title="TestModel对象的拷贝"></a>TestModel对象的拷贝</h5><p>针对<code>TestModel</code>为测试对象的拷贝,以及对象的拷贝对其属性的影响。源码如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">//TestModel.h</span><br><span class="line"></span><br><span class="line">#import <Foundation/Foundation.h></span><br><span class="line"></span><br><span class="line">NS_ASSUME_NONNULL_BEGIN</span><br><span class="line"></span><br><span class="line">@interface TestModel : NSObject</span><br><span class="line"></span><br><span class="line">@property (nonatomic, copy) NSString *title;</span><br><span class="line">@property (nonatomic, copy) NSMutableString *subTitle;</span><br><span class="line">@property (nonatomic, strong) NSArray *norArray;</span><br><span class="line">@property (nonatomic, strong) NSMutableArray *mutArray;</span><br><span class="line"></span><br><span class="line">- (instancetype)initWithTitle:(NSString *)title subTitle:(NSMutableString *)subTitle norArray:(NSArray *)array mutArrry:(NSMutableArray *)mutArray;</span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">NS_ASSUME_NONNULL_END</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line">// TestModel.m</span><br><span class="line">#import "TestModel.h"</span><br><span class="line"></span><br><span class="line">@interface TestModel()<NSCopying, NSMutableCopying></span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@implementation TestModel</span><br><span class="line"></span><br><span class="line">- (instancetype)initWithTitle:(NSString *)title subTitle:(NSMutableString *)subTitle norArray:(NSArray *)array mutArrry:(NSMutableArray *)mutArray{</span><br><span class="line"> if (self = [super init]) {</span><br><span class="line"> _title = title;</span><br><span class="line"> _subTitle = subTitle;</span><br><span class="line"> _norArray = array;</span><br><span class="line"> _mutArray = mutArray;</span><br><span class="line"> }</span><br><span class="line"> return self;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (id)copyWithZone:(nullable NSZone *)zone{</span><br><span class="line"> TestModel *model = [[[self class] allocWithZone:zone] init];</span><br><span class="line"> model.title = [self.title copyWithZone:zone]; //同[self.title copy];</span><br><span class="line"> model.subTitle = [self.subTitle copyWithZone:zone]; //同[self.subTitle copy];</span><br><span class="line"> model.norArray = [self.norArray copyWithZone:zone]; //同[self.norArray copy];</span><br><span class="line"> model.mutArray = [self.mutArray copyWithZone:zone]; //同[self.mutArray copy];</span><br><span class="line"> return model;</span><br><span class="line">}</span><br><span class="line">// 如果对象属性特别多的情况下,可以使用runtime实现,如下:</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line">- (id)copyWithZone:(NSZone * )zone{</span><br><span class="line"> id copyObject = [[[self class] allocWithZone:zone] init];</span><br><span class="line"> // 01:获取属性列表</span><br><span class="line"> unsigned int propertyCount = 0;</span><br><span class="line"> objc_property_t *propertyArray = class_copyPropertyList([self class], &propertyCount);</span><br><span class="line"> for (int i = 0; i< propertyCount; i++) {</span><br><span class="line"> objc_property_t property = propertyArray[i];</span><br><span class="line"> // 2.属性名字</span><br><span class="line"> const char * propertyName = property_getName(property);</span><br><span class="line"> NSString * key = [NSString stringWithUTF8String:propertyName];</span><br><span class="line"> // 3.通过属性名拿到属性值</span><br><span class="line"> id value=[self valueForKey:key];</span><br><span class="line"> NSLog(@"name:%s,value:%@",propertyName,value);</span><br><span class="line"> // 4.判断值对象是否响应copyWithZone</span><br><span class="line"> if ([value respondsToSelector:@selector(copyWithZone:)]) {</span><br><span class="line"> //5.设置属性值</span><br><span class="line"> [copyObject setValue:[value copy] forKey:key];</span><br><span class="line"> }else{</span><br><span class="line"> [copyObject setValue:value forKey:key];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> //mark:需要手动释放</span><br><span class="line"> free(propertyArray);</span><br><span class="line"> return copyObject;</span><br><span class="line">}</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">- (id)mutableCopyWithZone:(NSZone *)zone{</span><br><span class="line"> TestModel *model = [[[self class] allocWithZone:zone] init];</span><br><span class="line"> model.title = [self.title mutableCopyWithZone:zone]; // 同[self.title mutableCopy];</span><br><span class="line"> model.subTitle = [self.subTitle mutableCopyWithZone:zone]; // 同[self.subTitle mutableCopy];</span><br><span class="line"> model.norArray = [self.norArray mutableCopyWithZone:zone]; // 同[self.norArray mutableCopy];</span><br><span class="line"> model.mutArray = [self.mutArray mutableCopyWithZone:zone]; // 同[self.mutArray mutableCopy];</span><br><span class="line"> return model;</span><br><span class="line">}</span><br><span class="line">// 如果对象属性特别多的情况下,可以使用runtime实现,如下:</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line">- (id)mutableCopyWithZone:(NSZone *)zone{</span><br><span class="line"> id mutableCopyObj = [[[self class]allocWithZone:zone] init];</span><br><span class="line"> //1.获取属性列表</span><br><span class="line"> unsigned int count = 0;</span><br><span class="line"> objc_property_t* propertylist = class_copyPropertyList([self class], &count);</span><br><span class="line"> for (int i = 0; i < count ; i++) {</span><br><span class="line"> objc_property_t property = propertylist[i];</span><br><span class="line"> //2.获取属性名</span><br><span class="line"> const char * propertyName = property_getName(property);</span><br><span class="line"> NSString * key = [NSString stringWithUTF8String:propertyName];</span><br><span class="line"> //3.获取属性值</span><br><span class="line"> id value = [self valueForKey:key];</span><br><span class="line"> //4.判断属性值对象是否遵守NSMutableCopying协议</span><br><span class="line"> if ([value respondsToSelector:@selector(mutableCopyWithZone:)]) {</span><br><span class="line"> //5.设置对象属性值</span><br><span class="line"> [mutableCopyObj setValue:[value mutableCopy] forKey:key];</span><br><span class="line"> }else{</span><br><span class="line"> [mutableCopyObj setValue:value forKey:key];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> //mark:需要手动释放</span><br><span class="line"> free(propertylist);</span><br><span class="line"> return mutableCopyObj;</span><br><span class="line">}</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure><p>测试代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">- (void)testCustomObject{</span><br><span class="line"> NSMutableArray *mutableArray = [NSMutableArray array];</span><br><span class="line"> TestModel *model = [[TestModel alloc] initWithTitle:@"title" subTitle:[NSMutableString stringWithString:@"subTitle"] norArray:@[@"test1", @"test2"] mutArrry:mutableArray];</span><br><span class="line"> TestModel *copyModel = [model copy];</span><br><span class="line"> TestModel *mutableModel = [model mutableCopy];</span><br><span class="line"> // 测试对象的拷贝</span><br><span class="line"> NSLog(@"******TestModel内存地址******");</span><br><span class="line"> NSLog(@"原始地址:%p", model);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel);</span><br><span class="line"> // 测试对象拷贝对NSString类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性title(NSString)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.title);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.title);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.title);</span><br><span class="line"> // 测试对象拷贝对NSMutableString类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性subTitle(NSMutableString)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.subTitle);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.subTitle);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.subTitle);</span><br><span class="line"> // 测试对象拷贝对非可变集合类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性norArray(NSArray)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.norArray);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.norArray);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.norArray);</span><br><span class="line"> // 测试对象拷贝对可变几何类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性mutArrry(NSMutableArray)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.mutArray);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.mutArray);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.mutArray);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">2020-10-25 15:40:28.564704+0800 内存管理[39368:1919107] ******TestModel内存地址******</span><br><span class="line">2020-10-25 15:40:28.564882+0800 内存管理[39368:1919107] 原始地址:0x600000eaa400</span><br><span class="line">2020-10-25 15:40:28.564988+0800 内存管理[39368:1919107] copy地址:0x600000eaa370</span><br><span class="line">2020-10-25 15:40:28.565097+0800 内存管理[39368:1919107] mutableCopy地址:0x600000eaa100</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.565191+0800 内存管理[39368:1919107] ****** 属性title(NSString)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.565468+0800 内存管理[39368:1919107] 原始地址:0x10e8f5188</span><br><span class="line">2020-10-25 15:40:28.565923+0800 内存管理[39368:1919107] copy地址:0x10e8f5188</span><br><span class="line">2020-10-25 15:40:28.566376+0800 内存管理[39368:1919107] mutableCopy地址:0x8356f4dfe5d0308a</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.566881+0800 内存管理[39368:1919107] ****** 属性subTitle(NSMutableString)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.569415+0800 内存管理[39368:1919107] 原始地址:0x600000eaa430</span><br><span class="line">2020-10-25 15:40:28.578373+0800 内存管理[39368:1919107] copy地址:0x8355e20852d2afc7</span><br><span class="line">2020-10-25 15:40:28.578531+0800 内存管理[39368:1919107] mutableCopy地址:0x8355e20852d2afc7</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.578646+0800 内存管理[39368:1919107] ****** 属性norArray(NSArray)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.578771+0800 内存管理[39368:1919107] 原始地址:0x6000000a9780</span><br><span class="line">2020-10-25 15:40:28.579093+0800 内存管理[39368:1919107] copy地址:0x6000000a9780</span><br><span class="line">2020-10-25 15:40:28.579223+0800 内存管理[39368:1919107] mutableCopy地址:0x600000eaa310</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.579318+0800 内存管理[39368:1919107] ****** 属性mutArrry(NSMutableArray)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.579674+0800 内存管理[39368:1919107] 原始地址:0x600000eaa0d0</span><br><span class="line">2020-10-25 15:40:28.580027+0800 内存管理[39368:1919107] copy地址:0x7fff8062cc40</span><br><span class="line">2020-10-25 15:40:28.580466+0800 内存管理[39368:1919107] mutableCopy地址:0x600000eaa3d0</span><br></pre></td></tr></table></figure><p>通过以上测试可以发现:</p><ul><li>针对对象的拷贝,无论是<code>copy</code>还是<code>mutableCopy</code>都会产生新的对象,均为深拷贝。</li><li>对象中的属性,遵循可变类型的属性无论是<code>copy</code>还是<code>mutableCopy</code>都会产生新的对象,均为深拷贝;非可变类型的属性,<code>copy</code>时没有产生新的对象,为指针拷贝,即浅拷贝;<code>mutableCopy</code>时产生新的对象,为内容拷贝,即深拷贝。</li></ul><h5 id="集合的的拷贝"><a href="#集合的的拷贝" class="headerlink" title="集合的的拷贝"></a>集合的的拷贝</h5><p>针对集合的拷贝,<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html" target="_blank" rel="noopener">Apple官方</a>给的示意图如下:</p><p><img src="./../images/集合的深浅拷贝.png" alt="集合的深浅拷贝"></p><p>之所以将集合对象拿出来单独处理,原因在于集合中会包含很多的对象,这些对象也需要区分深拷贝与浅拷贝,更深一些,集合中也可能包含集合对象,如此一来,显得更加麻烦。接下来将以<code>NSArray</code>的深拷贝与浅拷贝,将集合的深浅拷贝分为四种情况进一步了解:</p><h6 id="1、浅拷贝"><a href="#1、浅拷贝" class="headerlink" title="1、浅拷贝"></a>1、浅拷贝</h6><p><strong>代码如下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">NSArray *oriArr = [NSArray arrayWithObjects:@"test", nil];</span><br><span class="line">NSArray *copyArr = [oriArr copy];</span><br><span class="line">NSLog(@"%p", oriArr);</span><br><span class="line">NSLog(@"%p", copyArr);</span><br></pre></td></tr></table></figure><p><strong>日志分析:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 通过日志分析可以确定为浅拷贝</span><br><span class="line">2020-10-25 16:59:33.093252+0800 内存管理[39941:1967202] 0x600002fafa60</span><br><span class="line">2020-10-25 16:59:33.093358+0800 内存管理[39941:1967202] 0x600002fafa60</span><br></pre></td></tr></table></figure><h6 id="2、单层深拷贝"><a href="#2、单层深拷贝" class="headerlink" title="2、单层深拷贝"></a>2、单层深拷贝</h6><p>单层深拷贝指的是对<code>NSArray</code>对象的深拷贝,并非对其内部的元素进行处理。</p><p><strong>代码如下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">NSArray *oriArr = [NSArray arrayWithObjects:@"test", nil];</span><br><span class="line">NSMutableArray *mutArr = [oriArr mutableCopy];</span><br><span class="line">NSLog(@"%p", oriArr);</span><br><span class="line">NSLog(@"%p", mutArr);</span><br><span class="line">//内部元素</span><br><span class="line">NSLog(@"%p", oriArr[0]);</span><br><span class="line">NSLog(@"%p", mutArr[0]);</span><br></pre></td></tr></table></figure><p><strong>日志分析:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 通过日志分析可以发现,NSArray对象通过mutableCopy进行了深拷贝,但是其内部元素并没有完全深拷贝,因此称为单层深拷贝</span><br><span class="line">2020-10-25 17:08:32.338871+0800 内存管理[40113:1978516] 0x60000223cb60</span><br><span class="line">2020-10-25 17:08:32.338960+0800 内存管理[40113:1978516] 0x600002c5d380</span><br><span class="line">2020-10-25 17:08:32.339046+0800 内存管理[40113:1978516] 0x10af9b4e8</span><br><span class="line">2020-10-25 17:08:32.339134+0800 内存管理[40113:1978516] 0x10af9b4e8</span><br></pre></td></tr></table></figure><h6 id="3、双层深拷贝"><a href="#3、双层深拷贝" class="headerlink" title="3、双层深拷贝"></a>3、双层深拷贝</h6><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">- (void)testCollectionCopy{ </span><br><span class="line">// 创建</span><br><span class="line"> NSMutableString *mutString1 = [NSMutableString stringWithString:@"test1"];</span><br><span class="line"> NSMutableString *mutString2 = [NSMutableString stringWithString:@"test2"];</span><br><span class="line"> NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutString2, nil];</span><br><span class="line"> NSArray *testArr = [NSArray arrayWithObjects:mutString1, mutableArr, nil];</span><br><span class="line"> //通过官方文档提供的方式进行创建copy</span><br><span class="line"> NSArray *testArrCopy = [[NSArray alloc] initWithArray:testArr copyItems:YES];</span><br><span class="line"> //testArr和testArrCopy进行对比</span><br><span class="line"> NSLog(@"===我是分割线01===");</span><br><span class="line"> NSLog(@"%p", testArr);</span><br><span class="line"> NSLog(@"%p", testArrCopy);</span><br><span class="line"> </span><br><span class="line"> //testArr和testArrCopy中元素指针对比</span><br><span class="line"> //mutableString对比</span><br><span class="line"> NSLog(@"===我是分割线02===");</span><br><span class="line"> NSLog(@"%p", testArr[0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[0]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr对比</span><br><span class="line"> NSLog(@"===我是分割线03===");</span><br><span class="line"> NSLog(@"%p", testArr[1]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr中元素对比,即mutString2进行对比</span><br><span class="line"> NSLog(@"===我是分割线04===");</span><br><span class="line"> NSLog(@"%p", testArr[1][0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1][0]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>日志分析:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">2020-10-25 17:35:01.731301+0800 内存管理[40436:1999803] ===我是分割线01===</span><br><span class="line">2020-10-25 17:35:01.734516+0800 内存管理[40436:1999803] 0x60000147a2c0</span><br><span class="line">2020-10-25 17:35:01.734661+0800 内存管理[40436:1999803] 0x60000147a2e0</span><br><span class="line">2020-10-25 17:35:01.734784+0800 内存管理[40436:1999803] ===我是分割线02===</span><br><span class="line">2020-10-25 17:35:01.734964+0800 内存管理[40436:1999803] 0x600001a528b0</span><br><span class="line">2020-10-25 17:35:01.735420+0800 内存管理[40436:1999803] 0x87c4312271f96ce5</span><br><span class="line">2020-10-25 17:35:01.735838+0800 内存管理[40436:1999803] ===我是分割线03===</span><br><span class="line">2020-10-25 17:35:01.736861+0800 内存管理[40436:1999803] 0x600001a52550</span><br><span class="line">2020-10-25 17:35:01.738048+0800 内存管理[40436:1999803] 0x600001627300</span><br><span class="line">2020-10-25 17:35:01.738733+0800 内存管理[40436:1999803] ===我是分割线04===</span><br><span class="line">2020-10-25 17:35:01.738939+0800 内存管理[40436:1999803] 0x600001a524c0</span><br><span class="line">2020-10-25 17:35:01.739575+0800 内存管理[40436:1999803] 0x600001a524c0</span><br></pre></td></tr></table></figure><p>通过以上日志可以发现:copy后,只有mutableArr中的mutalbeString2指针地址没有变化。而testArr的指针和testArr中的mutableArr、mutableString1的指针地址均发生变化,所以称之为双层深复制。</p><p><strong>限制</strong></p><p>initWithArray: copyItems:会使NSArray中元素均执行copy方法,这也是在testArr中放入NSMutableArray和NSMutableString的原因。如果放入的是NSArray或者NSString,执行copy后,只会发生指针复制;如果放入的是未实现NSCopying协议的对象,调用这个方法甚至会crash。</p><h6 id="4、完全深拷贝"><a href="#4、完全深拷贝" class="headerlink" title="4、完全深拷贝"></a>4、完全深拷贝</h6><p>如果想完美的解决NSArray嵌套NSArray这种情形,可以使用归档、解档的方式。</p><p><strong>代码如下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">- (void)testDeepCopyCollection{</span><br><span class="line"> NSMutableString *mutString1 = [NSMutableString stringWithString:@"test1"];</span><br><span class="line"> NSMutableString *mutString2 = [NSMutableString stringWithString:@"test1"];</span><br><span class="line"> NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutString2, nil];</span><br><span class="line"> NSArray *testArr = [NSArray arrayWithObjects:mutString1, mutableArr, nil];</span><br><span class="line"> //通过归档、解档的方式创建copy</span><br><span class="line"> NSArray *testArrCopy = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:testArr]];</span><br><span class="line"> //testArr和testArrCopy进行对比</span><br><span class="line"> NSLog(@"===我是分割线01===");</span><br><span class="line"> NSLog(@"%p", testArr);</span><br><span class="line"> NSLog(@"%p", testArrCopy);</span><br><span class="line"> </span><br><span class="line"> //testArr和testArrCopy中元素指针对比</span><br><span class="line"> //mutableString对比</span><br><span class="line"> NSLog(@"===我是分割线02===");</span><br><span class="line"> NSLog(@"%p", testArr[0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[0]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr对比</span><br><span class="line"> NSLog(@"===我是分割线03===");</span><br><span class="line"> NSLog(@"%p", testArr[1]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr中元素对比,即mutalbeString2进行对比</span><br><span class="line"> NSLog(@"===我是分割线04===");</span><br><span class="line"> NSLog(@"%p", testArr[1][0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1][0]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>日志:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">2020-10-25 21:14:30.738233+0800 内存管理[41176:2082596] ===我是分割线01===</span><br><span class="line">2020-10-25 21:14:30.738370+0800 内存管理[41176:2082596] 0x60000173a480</span><br><span class="line">2020-10-25 21:14:30.738475+0800 内存管理[41176:2082596] 0x60000173a660</span><br><span class="line">2020-10-25 21:14:30.738575+0800 内存管理[41176:2082596] ===我是分割线02===</span><br><span class="line">2020-10-25 21:14:30.738670+0800 内存管理[41176:2082596] 0x600001950780</span><br><span class="line">2020-10-25 21:14:30.738766+0800 内存管理[41176:2082596] 0x600001950990</span><br><span class="line">2020-10-25 21:14:30.738965+0800 内存管理[41176:2082596] ===我是分割线03===</span><br><span class="line">2020-10-25 21:14:30.745114+0800 内存管理[41176:2082596] 0x6000019507e0</span><br><span class="line">2020-10-25 21:14:30.745286+0800 内存管理[41176:2082596] 0x600001950a50</span><br><span class="line">2020-10-25 21:14:30.745426+0800 内存管理[41176:2082596] ===我是分割线04===</span><br><span class="line">2020-10-25 21:14:30.745631+0800 内存管理[41176:2082596] 0x6000019507b0</span><br><span class="line">2020-10-25 21:14:30.745943+0800 内存管理[41176:2082596] 0x600001950a80</span><br></pre></td></tr></table></figure><p>通过以上日志发现,<code>testArr</code>和<code>testArrCopy</code>中的元素以及集合中集合的指针完全不同,所以完成了深拷贝。</p><p><strong>限制</strong></p><p>归档和解档的前提是NSArray中所有的对象都实现了NSCoding协议。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>以上就是关于深拷贝和浅拷贝的一些探究,概括为浅拷贝为指针的复制,不会创建一个对象;深拷贝为内容的复制,会创建一个新的对象,集合的拷贝需要多加注意,以免引起一些问题。在平时的项目开发中,需要根据需要而决定使用深拷贝还是浅拷贝。</p><p> 参考文档:</p><ul><li><a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCopying.html" target="_blank" rel="noopener">对象的拷贝</a></li><li><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html" target="_blank" rel="noopener">集合的拷贝</a></li><li><a href="https://www.jianshu.com/p/ebbac2fec4c6" target="_blank" rel="noopener">iOS Copy</a></li></ul><h3 id="非自己生成的对象,自己也能持有"><a href="#非自己生成的对象,自己也能持有" class="headerlink" title="非自己生成的对象,自己也能持有"></a>非自己生成的对象,自己也能持有</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">id obj1 = [NSMutableArray array]; // 取得的对象存在,但是自己不持有对象</span><br><span class="line">[obj1 retain];// 自己持有对象</span><br></pre></td></tr></table></figure><p>通过<code>retain</code>方法,非自己生成的对象跟用<code>alloc/new/copy/mutableCopy</code>方法生成并持有的对象一样,成为了自己所持有的。</p><h3 id="不再需要自己持有的对象及时释放"><a href="#不再需要自己持有的对象及时释放" class="headerlink" title="不再需要自己持有的对象及时释放"></a>不再需要自己持有的对象及时释放</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">id obj1 = [NSMutableArray array]; // 取得的对象存在,但是自己不持有对象</span><br><span class="line">[obj1 retain];// 自己持有对象</span><br><span class="line">[obj1 release];// 释放对象,对象不可再访问</span><br></pre></td></tr></table></figure><p>使用<code>alloc</code>/<code>new</code>/<code>copy</code>/<code>mutableCopy</code>方法生成并持有的对象,或者使用<code>retain</code>方法持有的对象,一旦不再需要,务必需要使用<code>release</code>方法进行释放。内存释放实质上是通过dealloc方法,当对象的引用计数值达到0的时候,系统就知道这个对象不再需要了。此时,Objective-C会自动向对象发送一条dealloc的消息来释放内存。</p><p>但是针对如<code>NSMutableArray</code>类的<code>array</code>类方法等可以取得谁都不持有的对象,又通过什么方法释放呢?答案是<code>autorelease</code>方法。<code>autorelease</code>方法提供这样的功能,使对象在超出指定的生存范围时能够自动并正确地释放。</p><h4 id="NSAutoreleasePool"><a href="#NSAutoreleasePool" class="headerlink" title="NSAutoreleasePool"></a>NSAutoreleasePool</h4><p><a href="https://developer.apple.com/documentation/foundation/nsautoreleasepool" target="_blank" rel="noopener">NSAutoreleasePool</a>是 Cocoa 用来支持引用计数内存管理机制的类,是一种对象自动释放的机制,这种机制的基本思想是把所有需要发送release消息的对象都记录下来(放到自动释放池中),当一个AutoreleasePool(自动释放池)被销毁的时候,会给这些对象一起发送release消息,其中<code>NSAutoreleasePool</code>起到了记录的作用。在MRC环境下使用,ARC环境下使用<code>@autoreleasepool{ ** }</code>。</p><h4 id="release和autorelease的区别"><a href="#release和autorelease的区别" class="headerlink" title="release和autorelease的区别"></a>release和autorelease的区别</h4><p>release是在对象的引用计数为0是,立即释放对象(调用dealloc函数),而autorelease不立即释放对象,只是将对象注册到autoreleasepool中,当pool结束时,自动调用release方法。 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">id pool = [[NSAutoreleasePool alloc] init];</span><br><span class="line"> Person * pA = [[Person alloc] init];</span><br><span class="line"> [pA autorelease]; // 将对象放入自动释放池pool中,不会立即释放对象</span><br><span class="line"> Person * pB = pA;</span><br><span class="line"> [pB retain]; // 如果没有该步骤,引用计数为1</span><br><span class="line"> NSLog(@"pool release前:retainCount: %lu",[pA retainCount]); //pA引用计数为2</span><br><span class="line"> [pool release]; //销毁自动释放池,自动释放池中所有的对象也被销毁。此时pA引用计数减1</span><br><span class="line"> NSLog(@"pool release后:retainCount: %lu",[pA retainCount]); //pA引用计数为1</span><br></pre></td></tr></table></figure><h4 id="关于autorelease"><a href="#关于autorelease" class="headerlink" title="关于autorelease"></a>关于autorelease</h4><p><code>autorelease</code>顾名思义,就是自动释放。<code>autorelease</code>会像C语言的自动变量那样对待对象实例。当超出其作用域时,对象实例的<code>release</code>实例方法被调用。同C语言不同的是,编程人员可以设定变量的作用域。</p><p>在MRC环境下,autorelease的具体使用方法如下:</p><ul><li>1、生成并持有NSAutoreleasePool对象;</li><li>2、调用已分配对象的autorelease实例方法;</li><li>3、废弃NSAutoreleasePool对象。</li></ul><p>对于所有调用过<code>autorelease</code>实例方法的对象,在废弃<code>NSAutoreleasePool</code>对象时,都将调用release实例方法。在Cocoa框架中,相当于程序主循环的<code>NSRunLoop</code>或者在其他程序可运行的地方,对<code>NSAutoreleasePool</code>对象进行生成、持有和废弃。<code>NSRunLoop</code>每次循环过程中,<code>NSAutoreleasePool</code>对象被生成或废弃。</p><p>尽管如此,但在大量产生<code>autorelease</code>对象时,只要不废弃<code>NSAutoreleasePool</code>对象,那么生成的对象就不能被释放,因此有时会有产生内存不足的现象。例如在图像文件读入到<code>NSData</code>对象,并从中生成<code>UIImage</code>对象,改变该对象尺寸后生成新的<code>UIImage</code>对象时,就会大量产生<code>autorelease</code>对象。此时可以通过自主创建、持有或废弃<code>NSAutoreleasePool</code>对象。</p><p>另外,Cocoa框架中也有很多类方法用于返回<code>autorelease</code>的对象。比如<code>NSMutableArray</code>类的<code>arrayWithCapacity</code>类方法。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id array = [NSMutableArray arrayWithCapacity:1];</span><br></pre></td></tr></table></figure><p>此源代码同以下源代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id array = [[NSMutableArray arrayWithCapacity:1] autorelease];</span><br></pre></td></tr></table></figure><p><strong>autorelease的实现</strong></p><p>在ARC的环境下,<code>autoreleasepool</code>的写法如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">@autoreleasepool {</span><br><span class="line">....</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在runtime源码中,有段关于autorelease pool实现的介绍:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Autorelease pool implementation</span><br><span class="line"></span><br><span class="line">A thread's autorelease pool is a stack of pointers. </span><br><span class="line">Each pointer is either an object to release, or POOL_BOUNDARY which is </span><br><span class="line"> an autorelease pool boundary.</span><br><span class="line">A pool token is a pointer to the POOL_BOUNDARY for that pool. When </span><br><span class="line"> the pool is popped, every object hotter than the sentinel is released.</span><br><span class="line">The stack is divided into a doubly-linked list of pages. Pages are added </span><br><span class="line"> and deleted as necessary. </span><br><span class="line">Thread-local storage points to the hot page, where newly autoreleased </span><br><span class="line"> objects are stored.</span><br></pre></td></tr></table></figure><p>从以上信息大致可以了解到:</p><ul><li>自动释放池是一个以栈为节点的结构,拥有栈的特性,即先进后出;</li><li>自动释放池的节点可以是对象,即可以被释放;</li><li>自动释放池的数据结构是双向链表;</li><li>自动释放池跟线程相关。</li></ul><p>在main.m文件中,通过命令行<code>clang -S -fobjc-arc main.m -o main.s</code>,可以查看到对应的汇编代码,其中相关代码为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">callq_objc_autoreleasePoolPush</span><br><span class="line">movq%rax, %rdi</span><br><span class="line">callq_objc_autoreleasePoolPop</span><br></pre></td></tr></table></figure><p>通过在runtime源码中查找,可以初步了解到对应的实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">void *</span><br><span class="line">_objc_autoreleasePoolPush(void)</span><br><span class="line">{</span><br><span class="line"> return objc_autoreleasePoolPush();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">void</span><br><span class="line">_objc_autoreleasePoolPop(void *ctxt)</span><br><span class="line">{</span><br><span class="line"> objc_autoreleasePoolPop(ctxt);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这两个函数都是对<code>AutoreleasePoolPage</code>的简单封装,所以自动释放的核心就在于这个类。</p><p><code>AutoreleasePoolPage</code>是一个C++实现的类,大致结构如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">class AutoreleasePoolPage </span><br><span class="line">{</span><br><span class="line"># define EMPTY_POOL_PLACEHOLDER ((id*)1)</span><br><span class="line"></span><br><span class="line"># define POOL_BOUNDARY nil</span><br><span class="line"> static pthread_key_t const key = AUTORELEASE_POOL_KEY;</span><br><span class="line"> static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing</span><br><span class="line"> static size_t const SIZE = </span><br><span class="line">#if PROTECT_AUTORELEASEPOOL</span><br><span class="line"> PAGE_MAX_SIZE; // must be multiple of vm page size</span><br><span class="line">#else</span><br><span class="line"> PAGE_MAX_SIZE; // size and alignment, power of 2</span><br><span class="line">#endif</span><br><span class="line"> static size_t const COUNT = SIZE / sizeof(id);</span><br><span class="line"></span><br><span class="line"> magic_t const magic;</span><br><span class="line"> id *next;</span><br><span class="line"> pthread_t const thread;</span><br><span class="line"> AutoreleasePoolPage * const parent;</span><br><span class="line"> AutoreleasePoolPage *child;</span><br><span class="line"> uint32_t const depth;</span><br><span class="line"> uint32_t hiwat;</span><br><span class="line"></span><br><span class="line"> // SIZE-sizeof(*this) bytes of contents follow</span><br><span class="line"></span><br><span class="line"> static void * operator new(size_t size) {</span><br><span class="line"> return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);</span><br><span class="line"> }</span><br><span class="line"> static void operator delete(void * p) {</span><br><span class="line"> return free(p);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> AutoreleasePoolPage(AutoreleasePoolPage *newParent) </span><br><span class="line"> : magic(), next(begin()), thread(pthread_self()),</span><br><span class="line"> parent(newParent), child(nil), </span><br><span class="line"> depth(parent ? 1+parent->depth : 0), </span><br><span class="line"> hiwat(parent ? parent->hiwat : 0)</span><br><span class="line"> { </span><br><span class="line"> if (parent) {</span><br><span class="line"> parent->check();</span><br><span class="line"> assert(!parent->child);</span><br><span class="line"> parent->unprotect();</span><br><span class="line"> parent->child = this;</span><br><span class="line"> parent->protect();</span><br><span class="line"> }</span><br><span class="line"> protect();</span><br><span class="line"> }</span><br><span class="line"> ......</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>从以上源码可以了解到:</p><ul><li><code>magic</code>用来检验<code>AutoreleasePoolPage</code>的结构是否完整;</li><li><p>AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程);</p></li><li><p>AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以<strong>双向链表</strong>的形式组合而成,分别对应结构中的<code>parent</code>指针和<code>child</code>指针,<code>parent</code>指向父节点,第一个节点的parent值为nil, <code>child</code>指向子节点,最后一个节点的<code>child</code>值为nil;</p></li><li>AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址;</li><li>上面的<code>id *next</code>指针作为游标指向栈顶最新添加进来的autorelease对象的下一个位置,初始化时指向<code>begin()</code>;</li><li>一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入;</li></ul><p>所以,若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时内存如下图:<img src="./../images/autorelease01.jpg" alt="autorelease01"></p><p> (图片来自sunnyxx)</p><p>上图中,这一页再加入一个autorelease对象就要满了(即next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的<code>next</code>指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。</p><p>所以,向一个对象发送<code>autorelease</code>消息时,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置;</p><p><strong>哨兵对象</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># define POOL_BOUNDARY nil</span><br></pre></td></tr></table></figure><p>每档进行一次<code>objc_autoreleasePoolPush</code>调用时,runtime向当前的<code>AutoreleasePoolPage</code>中添加一个哨兵对象,值为0(即nil),这个page示例如下:</p><p><img src="./../images/autorelease.jpg" alt="autorelease"></p><p><code>objc_autoreleasePoolPush</code>的返回值正是这个哨兵对象的地址,被<code>objc_autoreleasePoolPop(哨兵对象)</code>作为入参,于是:</p><ul><li><p>1、可以根据传入的哨兵对象地址可以找到哨兵对象所处的page;</p></li><li><p>2、在当前page中,将晚于哨兵对象插入的所有<code>autorelease</code>对象都发送一次<code>release</code>消息,并向回移动<code>next</code>指针到正确的位置;</p></li><li><p>从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page;</p><p>执行完<code>objc_autoreleasePoolPop</code>后,最终变成如下的样子;</p></li></ul><p><img src="./../images/autoreleasepool3.jpg" alt="autoreleasepool3"></p><p>关于Autorelease进一步的了解,推荐阅读:</p><ul><li><p><a href="http://blog.sunnyxx.com/2014/10/15/behind-autorelease/" target="_blank" rel="noopener">黑幕背后的Autorelease</a></p></li><li><p><a href="https://draveness.me/autoreleasepool/" target="_blank" rel="noopener">自动释放池的前世今生</a></p></li><li><p><a href="https://www.jianshu.com/p/97dd0ae27108" target="_blank" rel="noopener">autorelease和AutoReleasePool</a></p></li></ul><h3 id="无法释放非自己持有的对象"><a href="#无法释放非自己持有的对象" class="headerlink" title="无法释放非自己持有的对象"></a>无法释放非自己持有的对象</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 自己生成并持有对象</span><br><span class="line">id obj1 = [[NSObject alloc] init];</span><br><span class="line">[obj1 release];// 对象已释放</span><br><span class="line">[obj1 release];// 针对已经释放即非自己持有的对象,会引起崩溃。</span><br></pre></td></tr></table></figure><p>以上例子说明释放非自己持有的对象会造成程序的崩溃,因此绝不要去释放非自己持有的对象。</p><h2 id="alloc-retain-release-dealloc实现"><a href="#alloc-retain-release-dealloc实现" class="headerlink" title="alloc/retain/release/dealloc实现"></a>alloc/retain/release/dealloc实现</h2><h3 id="alloc"><a href="#alloc" class="headerlink" title="alloc"></a>alloc</h3><p>关于<code>alloc</code>的实现,接下来通过<code>runtime</code>源码一步步分析下流程。</p><p><strong>第一步</strong>:alloc</p><p>首先在<code>NSObject.mm</code>文件可以找到对应的<code>alloc</code>方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">+ (id)alloc {</span><br><span class="line"> return _objc_rootAlloc(self);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>第二步</strong>:_objc_rootAlloc</p><p>通过跟踪<code>_objc_rootAlloc</code>方法,进入对应的实现:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">id</span><br><span class="line">_objc_rootAlloc(Class cls)</span><br><span class="line">{</span><br><span class="line"> return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>第三步:</strong>callAlloc</p><p>进入对应的callAlloc方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">static ALWAYS_INLINE id</span><br><span class="line">callAlloc(Class cls, bool checkNil, bool allocWithZone=false)</span><br><span class="line">{</span><br><span class="line"> if (slowpath(checkNil && !cls)) return nil;</span><br><span class="line"></span><br><span class="line">#if __OBJC2__</span><br><span class="line"> if (fastpath(!cls->ISA()->hasCustomAWZ())) {</span><br><span class="line"> // No alloc/allocWithZone implementation. Go straight to the allocator.</span><br><span class="line"> // fixme store hasCustomAWZ in the non-meta class and </span><br><span class="line"> // add it to canAllocFast's summary</span><br><span class="line"> if (fastpath(cls->canAllocFast())) {</span><br><span class="line"> // No ctors, raw isa, etc. Go straight to the metal.</span><br><span class="line"> bool dtor = cls->hasCxxDtor();</span><br><span class="line"> id obj = (id)calloc(1, cls->bits.fastInstanceSize());</span><br><span class="line"> if (slowpath(!obj)) return callBadAllocHandler(cls);</span><br><span class="line"> obj->initInstanceIsa(cls, dtor);</span><br><span class="line"> return obj;</span><br><span class="line"> }</span><br><span class="line"> else {</span><br><span class="line"> // Has ctor or raw isa or something. Use the slower path.</span><br><span class="line"> id obj = class_createInstance(cls, 0);</span><br><span class="line"> if (slowpath(!obj)) return callBadAllocHandler(cls);</span><br><span class="line"> return obj;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line"> // No shortcuts available.</span><br><span class="line"> if (allocWithZone) return [cls allocWithZone:nil];</span><br><span class="line"> return [cls alloc];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在该步骤中,需要注意这个if的判断条件<code>fastpath(!cls->ISA()->hasCustomAWZ())</code>,这个函数是什么含义呢?<code>fastpath</code>又是什么呢?在<code>objc-os.h</code>文件中发现对应的宏定义:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#define fastpath(x) (__builtin_expect(bool(x), 1))</span><br></pre></td></tr></table></figure><p>通过搜索发现<code>__builtin_expect</code>是gcc引入的,作用是允许程序员将最有可能执行的分析告诉编译器。指令的写法为:<code>__builtin_expect(EXP, N)</code>,意思是:EXP==N的概率很大。</p><p>而<code>!cls->ISA()->hasCustomAWZ()</code>明显是调用了<code>hasCustomAWZ</code>方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">bool hasDefaultAWZ() {</span><br><span class="line"> return data()->flags & RW_HAS_DEFAULT_AWZ;</span><br><span class="line">}</span><br><span class="line">#define RW_HAS_DEFAULT_AWZ (1<<16)</span><br></pre></td></tr></table></figure><p>通过注释能知晓一些,<code>RW_HAS_DEFAULT_AWZ</code>是用来表示当前的class是否有重写<code>allocWithZone</code>。如果<code>cls->ISA()->hasCustomAWZ()</code>返回YES意味着当前的class有重写<code>allocWithZone</code>方法,那么直接对class进行<code>allocWithZone</code>,申请内存空间:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">//step01</span><br><span class="line">if (allocWithZone) return [cls allocWithZone:nil];</span><br><span class="line"></span><br><span class="line">//step02</span><br><span class="line">+ (id)allocWithZone:(struct _NSZone *)zone {</span><br><span class="line"> return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//step03</span><br><span class="line">id</span><br><span class="line">_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)</span><br><span class="line">{</span><br><span class="line"> id obj;</span><br><span class="line"></span><br><span class="line">#if __OBJC2__</span><br><span class="line"> // allocWithZone under __OBJC2__ ignores the zone parameter</span><br><span class="line"> (void)zone;</span><br><span class="line"> obj = class_createInstance(cls, 0); //创建对象</span><br><span class="line">#else</span><br><span class="line"> if (!zone) {</span><br><span class="line"> obj = class_createInstance(cls, 0);</span><br><span class="line"> }</span><br><span class="line"> else {</span><br><span class="line"> obj = class_createInstanceFromZone(cls, 0, zone);</span><br><span class="line"> }</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line"> if (slowpath(!obj)) obj = callBadAllocHandler(cls);</span><br><span class="line"> return obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过上述分析发现,最终调用了函数<code>class_createInstance</code>,进一步探索,在<code>objc-runtime-new.mm</code>文件中周到对应的代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">id </span><br><span class="line">class_createInstance(Class cls, size_t extraBytes)</span><br><span class="line">{</span><br><span class="line"> return _class_createInstanceFromZone(cls, extraBytes, nil);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从函数命名上已经能看出接近真相了:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">static __attribute__((always_inline)) </span><br><span class="line">id</span><br><span class="line">_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, </span><br><span class="line"> bool cxxConstruct = true, </span><br><span class="line"> size_t *outAllocatedSize = nil)</span><br><span class="line">{</span><br><span class="line"> if (!cls) return nil;</span><br><span class="line"></span><br><span class="line"> assert(cls->isRealized());</span><br><span class="line"></span><br><span class="line"> // Read class's info bits all at once for performance</span><br><span class="line"> // 一次性读取类的信息位以提高性能</span><br><span class="line"> bool hasCxxCtor = cls->hasCxxCtor();</span><br><span class="line"> bool hasCxxDtor = cls->hasCxxDtor();</span><br><span class="line"> bool fast = cls->canAllocNonpointer();</span><br><span class="line">// </span><br><span class="line"> size_t size = cls->instanceSize(extraBytes);</span><br><span class="line"> if (outAllocatedSize) *outAllocatedSize = size;</span><br><span class="line"></span><br><span class="line"> id obj;</span><br><span class="line"> if (!zone && fast) {</span><br><span class="line"> obj = (id)calloc(1, size);</span><br><span class="line"> if (!obj) return nil;</span><br><span class="line"> obj->initInstanceIsa(cls, hasCxxDtor);</span><br><span class="line"> } </span><br><span class="line"> else {</span><br><span class="line"> if (zone) {</span><br><span class="line"> obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);</span><br><span class="line"> } else {</span><br><span class="line"> obj = (id)calloc(1, size);</span><br><span class="line"> }</span><br><span class="line"> if (!obj) return nil;</span><br><span class="line"></span><br><span class="line"> // Use raw pointer isa on the assumption that they might be </span><br><span class="line"> // doing something weird with the zone or RR.</span><br><span class="line"> obj->initIsa(cls);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (cxxConstruct && hasCxxCtor) {</span><br><span class="line"> obj = _objc_constructOrFree(obj, cls);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>针对上述代码逐步分析:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"> // Class's ivar size rounded up to a pointer-size boundary.</span><br><span class="line"> // 类的ivar大小舍入到指针大小边界。</span><br><span class="line"> uint32_t alignedInstanceSize() {</span><br><span class="line"> return word_align(unalignedInstanceSize());</span><br><span class="line"> }</span><br><span class="line">// 分配对象的空间,最小为16个字节</span><br><span class="line"> size_t instanceSize(size_t extraBytes) {</span><br><span class="line"> size_t size = alignedInstanceSize() + extraBytes;</span><br><span class="line"> //CF要求所有对象至少为16字节。</span><br><span class="line"> // CF requires all objects be at least 16 bytes.</span><br><span class="line"> if (size < 16) size = 16;</span><br><span class="line"> return size;</span><br><span class="line"> }</span><br><span class="line"> // May be unaligned depending on class's ivars.</span><br><span class="line"> //读取当前的类的属性数据大小</span><br><span class="line"> uint32_t unalignedInstanceSize() {</span><br><span class="line"> assert(isRealized());</span><br><span class="line"> return data()->ro->instanceSize;</span><br><span class="line"> }</span><br><span class="line"> //内存对其</span><br><span class="line"> static inline uint32_t word_align(uint32_t x) {</span><br><span class="line"> return (x + WORD_MASK) & ~WORD_MASK;</span><br><span class="line">}</span><br><span class="line"># define WORD_MASK 7UL</span><br></pre></td></tr></table></figure><p>通过以上分析可知,创建对象的时,系统会为对象分配不少于16字节的内存,读取类中的信息,然后进行执行<code>initInstanceIsa</code>初始化<code>isa</code>等操作。关于内存对其及<code>isa</code>后续单独分析。</p><p>以上分析的是<code>hasDefaultAWZ()</code>返回YES的情况,如果返回NO呢?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">// No alloc/allocWithZone implementation. Go straight to the allocator.</span><br><span class="line">// fixme store hasCustomAWZ in the non-meta class and </span><br><span class="line">// add it to canAllocFast's summary</span><br><span class="line">if (fastpath(cls->canAllocFast())) {</span><br><span class="line"> // No ctors, raw isa, etc. Go straight to the metal.</span><br><span class="line"> bool dtor = cls->hasCxxDtor();</span><br><span class="line"> id obj = (id)calloc(1, cls->bits.fastInstanceSize());</span><br><span class="line"> if (slowpath(!obj)) return callBadAllocHandler(cls);</span><br><span class="line"> obj->initInstanceIsa(cls, dtor);</span><br><span class="line"> return obj;</span><br><span class="line">}</span><br><span class="line">else {</span><br><span class="line"> // Has ctor or raw isa or something. Use the slower path.</span><br><span class="line"> id obj = class_createInstance(cls, 0);</span><br><span class="line"> if (slowpath(!obj)) return callBadAllocHandler(cls);</span><br><span class="line"> return obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过上述代码分析可知,首先取判断当前类是否支持快速<code>alloc</code>,如果支持,直接调用<code>calloc</code>方法,并申请一块大小为<code>bits.fastInstanceSize()</code>的内存空间,然后初始化<code>isa</code>指针,否则直接调用<code>class_createInstance</code>方法,流程同上述分析。</p><p><strong>拓展:</strong></p><p><strong>init的实现</strong></p><p><code>alloc</code>通常都会搭配<code>init</code>使用,通过源码可知,<code>init</code>的实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">- (id)init {</span><br><span class="line"> return _objc_rootInit(self);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">id</span><br><span class="line">_objc_rootInit(id obj)</span><br><span class="line">{</span><br><span class="line"> // In practice, it will be hard to rely on this function.</span><br><span class="line"> // Many classes do not properly chain -init calls.</span><br><span class="line"> return obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>令人意外的是,<code>init</code>啥也没做,只是将对象原封不动的返回了。这点也纠正了我之前的错误认知,一直以为<code>init</code>时,才会去初始化一些类的信息。如果啥也不做,那要<code>init</code>方法啥用呢?其实<code>init</code>就是一个工厂类,方便开发者重写自定义罢了。</p><p><strong>new的实现</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">+ (id)new {</span><br><span class="line"> return [callAlloc(self, false/*checkNil*/) init];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从以上源码可知,<code>new</code>实质上就是<code>alloc + init</code>的综合体。</p><h3 id="retain"><a href="#retain" class="headerlink" title="retain"></a>retain</h3><p>关于<code>retain</code>,在ARC下不能直接调用,而在MRC下可以直接使用。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (id)retain {</span><br><span class="line"> return ((id)self)->rootRetain();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">ALWAYS_INLINE id </span><br><span class="line">objc_object::rootRetain()</span><br><span class="line">{</span><br><span class="line"> return rootRetain(false, false);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">ALWAYS_INLINE id </span><br><span class="line">objc_object::rootRetain(bool tryRetain, bool handleOverflow)</span><br><span class="line">{</span><br><span class="line"> if (isTaggedPointer()) return (id)this;</span><br><span class="line"></span><br><span class="line"> bool sideTableLocked = false;</span><br><span class="line"> bool transcribeToSideTable = false;</span><br><span class="line"></span><br><span class="line"> isa_t oldisa;</span><br><span class="line"> isa_t newisa;</span><br><span class="line"></span><br><span class="line"> do {</span><br><span class="line"> transcribeToSideTable = false;</span><br><span class="line"> oldisa = LoadExclusive(&isa.bits);</span><br><span class="line"> newisa = oldisa;</span><br><span class="line"> if (slowpath(!newisa.nonpointer)) {</span><br><span class="line"> ClearExclusive(&isa.bits);</span><br><span class="line"> if (!tryRetain && sideTableLocked) sidetable_unlock();</span><br><span class="line"> if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;</span><br><span class="line"> else return sidetable_retain();</span><br><span class="line"> }</span><br><span class="line"> // don't check newisa.fast_rr; we already called any RR overrides</span><br><span class="line"> if (slowpath(tryRetain && newisa.deallocating)) {</span><br><span class="line"> ClearExclusive(&isa.bits);</span><br><span class="line"> if (!tryRetain && sideTableLocked) sidetable_unlock();</span><br><span class="line"> return nil;</span><br><span class="line"> }</span><br><span class="line"> uintptr_t carry;</span><br><span class="line"> newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++</span><br><span class="line"></span><br><span class="line"> if (slowpath(carry)) {</span><br><span class="line"> // newisa.extra_rc++ overflowed</span><br><span class="line"> if (!handleOverflow) {</span><br><span class="line"> ClearExclusive(&isa.bits);</span><br><span class="line"> return rootRetain_overflow(tryRetain);</span><br><span class="line"> }</span><br><span class="line"> // Leave half of the retain counts inline and </span><br><span class="line"> // prepare to copy the other half to the side table.</span><br><span class="line"> if (!tryRetain && !sideTableLocked) sidetable_lock();</span><br><span class="line"> sideTableLocked = true;</span><br><span class="line"> transcribeToSideTable = true;</span><br><span class="line"> newisa.extra_rc = RC_HALF;</span><br><span class="line"> newisa.has_sidetable_rc = true;</span><br><span class="line"> }</span><br><span class="line"> } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));</span><br><span class="line"></span><br><span class="line"> if (slowpath(transcribeToSideTable)) {</span><br><span class="line"> // Copy the other half of the retain counts to the side table.</span><br><span class="line"> sidetable_addExtraRC_nolock(RC_HALF);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();</span><br><span class="line"> return (id)this;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 不要被上述这么长的源码给吓到,自己分析下可知,实际上是调用了<code>sidetable_retain()</code>方法,而该方法对应的实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">id</span><br><span class="line">objc_object::sidetable_retain()</span><br><span class="line">{</span><br><span class="line">#if SUPPORT_NONPOINTER_ISA</span><br><span class="line"> assert(!isa.nonpointer);</span><br><span class="line">#endif</span><br><span class="line"> //获取引用计数表</span><br><span class="line"> SideTable& table = SideTables()[this];</span><br><span class="line"> //加锁</span><br><span class="line"> table.lock();</span><br><span class="line"> size_t& refcntStorage = table.refcnts[this];</span><br><span class="line"> if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {</span><br><span class="line"> refcntStorage += SIDE_TABLE_RC_ONE;</span><br><span class="line"> }</span><br><span class="line"> table.unlock();</span><br><span class="line"></span><br><span class="line"> return (id)this;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//其中</span><br><span class="line">#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit</span><br><span class="line">代表左移两位,即为:0b00000100</span><br></pre></td></tr></table></figure><p>通过以上源码分析可知:<code>retain</code>的实现机制就是通过第一层<code>hash</code>算法,找到<code>指针变量</code>所对应的<code>sideTable</code>,然后再通过一层<code>hash</code>算法,找到存储引用计数的<code>size_t</code>,然后对其进行增加操作。</p><h3 id="release"><a href="#release" class="headerlink" title="release"></a>release</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">- (oneway void)release {</span><br><span class="line"> ((id)self)->rootRelease();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">objc_object::rootRelease()</span><br><span class="line">{</span><br><span class="line"> return rootRelease(true, false);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">ALWAYS_INLINE bool </span><br><span class="line">objc_object::rootRelease(bool performDealloc, bool handleUnderflow)</span><br><span class="line">{</span><br><span class="line"> if (isTaggedPointer()) return false;</span><br><span class="line"></span><br><span class="line"> bool sideTableLocked = false;</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"> sidetable_release(true);</span><br><span class="line"> ...</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>同retain源码类似,在<code>release</code>方法中实质上调用了<code>sidetable_release</code>.实现的原理也几乎一致,只是对引用计数进行相减,即<code>release</code>的实现机制就是通过第一层<code>hash</code>算法,找到<code>指针变量</code>所对应的<code>sideTable</code>,然后再通过一层<code>hash</code>算法,找到存储引用计数的<code>size_t</code>,然后对其进行相减操作。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">uintptr_t</span><br><span class="line">objc_object::sidetable_release(bool performDealloc)</span><br><span class="line">{</span><br><span class="line">#if SUPPORT_NONPOINTER_ISA</span><br><span class="line"> assert(!isa.nonpointer);</span><br><span class="line">#endif</span><br><span class="line"> SideTable& table = SideTables()[this];</span><br><span class="line"></span><br><span class="line"> bool do_dealloc = false;</span><br><span class="line"></span><br><span class="line"> table.lock();</span><br><span class="line"> RefcountMap::iterator it = table.refcnts.find(this);</span><br><span class="line"> if (it == table.refcnts.end()) {</span><br><span class="line"> do_dealloc = true; // 引用计数小于阀值,最后执行dealloc</span><br><span class="line"> table.refcnts[this] = SIDE_TABLE_DEALLOCATING;</span><br><span class="line"> } else if (it->second < SIDE_TABLE_DEALLOCATING) {</span><br><span class="line"> // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.</span><br><span class="line"> do_dealloc = true;</span><br><span class="line"> it->second |= SIDE_TABLE_DEALLOCATING;</span><br><span class="line"> } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {</span><br><span class="line"> it->second -= SIDE_TABLE_RC_ONE; 引用计数相减</span><br><span class="line"> }</span><br><span class="line"> table.unlock();</span><br><span class="line"> if (do_dealloc && performDealloc) {</span><br><span class="line"> ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);</span><br><span class="line"> }</span><br><span class="line"> return do_dealloc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>release过程:查找map,对引用计数减1,注意减的不是1,而是<code>SIDE_TABLE_RC_ONE</code>,即一个值为4的偏移量,如果引用计数小于阈值,则调用SEL_dealloc。</p><h3 id="dealloc"><a href="#dealloc" class="headerlink" title="dealloc"></a>dealloc</h3><p>当对象的引用计数为0时,即对象的所有者都不持有该对象,该对象被废弃时,无论是否开启<code>ARC</code>,都会调用对象的<code>dealloc</code>方法,对对象进行释放。通过runtime源码进一步分析<code>dealloc</code>的流程。</p><p>在<code>NSObject.mm</code>文件中,可以知道,对应的实现:</p><p><strong>第一步:dealloc</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- (void)dealloc {</span><br><span class="line"> _objc_rootDealloc(self);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>第二步:_objc_rootDealloc</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">_objc_rootDealloc(id obj)</span><br><span class="line">{</span><br><span class="line"> assert(obj);</span><br><span class="line"></span><br><span class="line"> obj->rootDealloc();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>第三步: rootDealloc</strong></p><p>此时会判断对象是否可以被释放,TaggedPointer不用释放,其他的判断依据主要有五个:</p><ul><li><code>nonpointer</code>: 是否是非指针类型isa</li><li><code>weakly_referenced</code>: 是有有弱引用</li><li><code>has_assoc</code>: 是否有关联对象</li><li><code>has_cxx_dtor</code>: 是否有C++相关内容</li><li><code>has_sidetable_rc</code>: 是否有辅助的引用计数散列表</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">//03</span><br><span class="line">inline void</span><br><span class="line">objc_object::rootDealloc()</span><br><span class="line">{</span><br><span class="line"> if (isTaggedPointer()) return; // fixme necessary?</span><br><span class="line"></span><br><span class="line"> if (fastpath(isa.nonpointer && </span><br><span class="line"> !isa.weakly_referenced && </span><br><span class="line"> !isa.has_assoc && </span><br><span class="line"> !isa.has_cxx_dtor && </span><br><span class="line"> !isa.has_sidetable_rc))</span><br><span class="line"> {</span><br><span class="line"> assert(!sidetable_present());</span><br><span class="line"> free(this);</span><br><span class="line"> } </span><br><span class="line"> else {</span><br><span class="line"> object_dispose((id)this);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果满足以上以上的条件则,直接使用<code>free</code>函数释放。否则执行<code>object_dispose</code>:</p><p><strong>第四步:objc_dispose</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">id </span><br><span class="line">object_dispose(id obj)</span><br><span class="line">{</span><br><span class="line"> if (!obj) return nil;</span><br><span class="line"></span><br><span class="line"> objc_destructInstance(obj); </span><br><span class="line"> free(obj);</span><br><span class="line"></span><br><span class="line"> return nil;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>直接调用<code>objc_destructInstance</code>方法,之后通过<code>free</code>函数释放。</p><p><code>objc_destructInstance</code>函数的实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">void *objc_destructInstance(id obj) </span><br><span class="line">{</span><br><span class="line"> if (obj) {</span><br><span class="line"> // Read all of the flags at once for performance.</span><br><span class="line"> bool cxx = obj->hasCxxDtor();</span><br><span class="line"> bool assoc = obj->hasAssociatedObjects();</span><br><span class="line"></span><br><span class="line"> // This order is important.</span><br><span class="line"> if (cxx) object_cxxDestruct(obj);</span><br><span class="line"> if (assoc) _object_remove_assocations(obj);</span><br><span class="line"> obj->clearDeallocating();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>流程:</p><ul><li><p>先判断 <code>hasCxxDtor</code>,如果有 <code>c++</code> 相关内容,要调用 <code>object_cxxDestruct()</code>,销毁 c++ 相关内容;</p></li><li><p>再判断 <code>hasAssociatedObjects</code>,如果有关联对象,要调用 <code>object_remove_associations()</code>,销毁关联对象的一系列操作;</p></li><li><p>然后调用 <code>clearDeallocating()</code></p></li></ul><p><code>clearDeallocating</code>函数实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">inline void </span><br><span class="line">objc_object::clearDeallocating()</span><br><span class="line">{</span><br><span class="line"> if (slowpath(!isa.nonpointer)) {</span><br><span class="line"> // Slow path for raw pointer isa.</span><br><span class="line"> sidetable_clearDeallocating();</span><br><span class="line"> }</span><br><span class="line"> else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {</span><br><span class="line"> // Slow path for non-pointer isa with weak refs and/or side table data.</span><br><span class="line"> clearDeallocating_slow();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> assert(!sidetable_present());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>流程:</p><ul><li><p>先执行 <code>sideTable_clearDeallocating()</code>;</p></li><li><p>再执行 <code>waek_clear_no_lock</code>,将指向该对象的弱引用指针置为 <code>nil</code>;</p></li><li><p>接下来执行 <code>table.refcnts.eraser()</code>,从引用计数表中擦除该对象的引用计数;</p></li><li><p>至此为此,<code>dealloc</code> 的执行流程结束。</p></li></ul><h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><ul><li><a href="https://juejin.im/post/6857758064658153486#heading-15" target="_blank" rel="noopener">参考iOS内存管理探索篇</a></li></ul><h2 id="ARC"><a href="#ARC" class="headerlink" title="ARC"></a>ARC</h2><p>ARC(Auto Reference Counting)自动引用计数,是<strong>编译器的特性</strong>,通过在编译期间添加合适的retain/release/autorelease等函数,来帮助开发者维护引用计数。因此,就ARC本身而言,它并不负责管理内存,它只是在编译层面帮助开发者维护引用计数的一种编译选项,具体如何实现的呢,通过以下代码进行示例:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char * argv[]) {</span><br><span class="line"> NSObject *obj = [[NSObject alloc] init];</span><br><span class="line"> NSLog(@"%@", obj);</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>编译后的汇编代码如下:</p><blockquote><p>开启汇编代码方式:打断点后,Xcode->Debug->Debug workflow-> Always show Disassembly</p></blockquote><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">内存管理`main:</span><br><span class="line"> 0x105547060 <+0>: pushq %rbp</span><br><span class="line"> 0x105547061 <+1>: movq %rsp, %rbp</span><br><span class="line"> 0x105547064 <+4>: subq $0x20, %rsp</span><br><span class="line"> 0x105547068 <+8>: movl $0x0, -0x4(%rbp)</span><br><span class="line"> 0x10554706f <+15>: movl %edi, -0x8(%rbp)</span><br><span class="line"> 0x105547072 <+18>: movq %rsi, -0x10(%rbp)</span><br><span class="line"> 0x105547076 <+22>: movq 0x43c3(%rip), %rax ; (void *)0x00007fff89be1d00: NSObject</span><br><span class="line"> 0x10554707d <+29>: movq %rax, %rdi</span><br><span class="line"> 0x105547080 <+32>: callq 0x105547350 ; symbol stub for: objc_alloc_init</span><br><span class="line"> 0x105547085 <+37>: leaq 0x2214(%rip), %rcx ; @"%@"</span><br><span class="line"> 0x10554708c <+44>: movq %rax, -0x18(%rbp)</span><br><span class="line"> 0x105547090 <+48>: movq -0x18(%rbp), %rsi</span><br><span class="line"> 0x105547094 <+52>: movq %rcx, %rdi</span><br><span class="line"> 0x105547097 <+55>: movb $0x0, %al</span><br><span class="line"> 0x105547099 <+57>: callq 0x105547344 ; symbol stub for: NSLog</span><br><span class="line"> 0x10554709e <+62>: xorl %edx, %edx</span><br><span class="line"> 0x1055470a0 <+64>: movl %edx, %esi</span><br><span class="line">-> 0x1055470a2 <+66>: movl $0x0, -0x4(%rbp)</span><br><span class="line"> 0x1055470a9 <+73>: leaq -0x18(%rbp), %rcx</span><br><span class="line"> 0x1055470ad <+77>: movq %rcx, %rdi</span><br><span class="line"> 0x1055470b0 <+80>: callq 0x105547374 ; symbol stub for: objc_storeStrong</span><br><span class="line"> 0x1055470b5 <+85>: movl -0x4(%rbp), %eax</span><br><span class="line"> 0x1055470b8 <+88>: addq $0x20, %rsp</span><br><span class="line"> 0x1055470bc <+92>: popq %rbp</span><br><span class="line"> 0x1055470bd <+93>: retq</span><br></pre></td></tr></table></figure><p>通过以上汇编代码发现,在代码退出作用域之前,执行了<code>objc_storeStrong(id *object, id value)</code>函数,而在<a href="https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-storeweak" target="_blank" rel="noopener">LLVM官方</a>的ARC文档中,可以知道函数<code></code>objc_storeStrong`实现如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">objc_storeStrong</span><span class="params">(id *object, id value)</span> </span>{</span><br><span class="line"> id oldValue = *object;</span><br><span class="line"> value = [value retain];</span><br><span class="line"> *object = value;</span><br><span class="line"> [oldValue release];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>官方释义: <code>object</code> is a valid pointer to a <code>__strong</code> object which is adequately aligned for a pointer. <code>value</code> is null or a pointer to a valid object。</p></blockquote><p>分析代码,可以知道该函数实际的操作为:对<code>value</code>进行了<code>retain</code>,而对<code>oldValue</code>进行了<code>release</code>,即释放了旧值。因此,可以总结,在<code>__strong</code>类型的变量的作用域结束时,编译器自动添加了<code>release</code>函数释放对象。</p><h4 id="ARC环境下的注意事项:"><a href="#ARC环境下的注意事项:" class="headerlink" title="ARC环境下的注意事项:"></a>ARC环境下的注意事项:</h4><ul><li>不能使用<code>retain</code>、<code>release</code>、<code>autorelease</code>、<code>retainCount</code>;</li><li>禁止使用<code>NSAutoreleasePool</code>,而是使用<code>@autoreleasepool{ ** }</code>;</li><li>ARC中未初始化的变量都会被初始化为nil;</li><li>方法命名必须遵守命名规则,不能随便定义以<code>alloc</code>/<code>init</code>/<code>new</code>/<code>copy</code>/<code>mutableCopy</code>开头且和所有权操作无关的方法;</li><li>不用在dealloc中释放实例变量(但是可以在dealloc中释放资源),也不需要调用[super dealloc];</li><li>编译代码时使用编译器clang,并加上编译选项<code>-fobjc-arc</code>;</li></ul><h4 id="ARC环境下的内存管理问题:"><a href="#ARC环境下的内存管理问题:" class="headerlink" title="ARC环境下的内存管理问题:"></a>ARC环境下的内存管理问题:</h4><ul><li>循环引用</li></ul><p>推荐阅读:</p><ul><li>书籍《Objective-C高级编程》</li><li><a href="https://mp.weixin.qq.com/s/W2QIS_dY21P4Rtz6JPVQZw" target="_blank" rel="noopener">谈谈iOS内存管理</a></li><li><a href="http://blog.devtang.com/2016/07/30/ios-memory-management/" target="_blank" rel="noopener">理解iOS的内存管理</a></li></ul><h3 id="所有权修饰符"><a href="#所有权修饰符" class="headerlink" title="所有权修饰符"></a>所有权修饰符</h3><p>在ARC有效的情况下,对象类型前面必须添加所有权修饰符,所有权修饰符一共有4种,Apple官方文档<a href="https://developer.apple.com/library/archive/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html" target="_blank" rel="noopener">Transitioning to ARC Release Notes</a>对其的描述为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">__strong is the default. An object remains “alive” as long as there is a strong pointer to it. </span><br><span class="line">__weak specifies a reference that does not keep the referenced object alive. A weak reference is set to nil when there are no strong references to the object.</span><br><span class="line">__unsafe_unretained specifies a reference that does not keep the referenced object alive and is not set to nil when there are no strong references to the object. If the object it references is deallocated, the pointer is left dangling.</span><br><span class="line">__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.</span><br></pre></td></tr></table></figure><p>简单翻译下为:</p><ul><li><code>__strong</code>:是默认的,只要有一个强指针指向对象,该对象就会保持“活的”(不被释放)。</li><li><code>__weak</code>:指定一个不保持被引用对象为活动的引用。当没有对对象的强引用时,弱引用被设置为nil。</li><li><code>__unsafe_unretained</code>:指定一个引用,该引用不会使被引用的对象保持活动状态,并且当没有对该对象的强引用时不会将其设置为nil。如果它引用的对象被释放,指针就会悬空。</li><li><code>__autoreleasing</code>:用来表示通过引用(id *)传递的参数,并在返回时自动释放。</li></ul><p>接下来分别介绍这四种修饰符:</p><h4 id="strong修饰符"><a href="#strong修饰符" class="headerlink" title="__strong修饰符"></a>__strong修饰符</h4><p><strong>概念</strong></p><p><code>__strong</code>修饰符是id类型和对象类型默认的所有权修饰符,如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id obj = [[NSObject alloc] init];</span><br></pre></td></tr></table></figure><p>实际上,上面的源码同下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id __strong obj = [[NSObject alloc] init];</span><br></pre></td></tr></table></figure><p><code>__strong</code>修饰符表示对对象的“强引用”,持有强引用的变量在超出其作用域时被废弃,随着强引用的实效,引用的对象会随之释放。</p><h4 id="weak修饰符"><a href="#weak修饰符" class="headerlink" title="__weak修饰符"></a>__weak修饰符</h4><p><strong>概念</strong></p><p><code>__weak</code>修饰符通常用来解决循环引用的问题,当两个强引用对象相互持有时,很容易造成循环引用。此时如果使用<code>__weak</code>修饰符,则可以保证修饰的变量不持有对象,因此在超出其变量作用域时,对象即被释放,如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line">//自己生成并持有对象,强引用</span><br><span class="line">id __strong obj1 = [[NSObject alloc] init];</span><br><span class="line">// obj2持有生成对象的弱引用</span><br><span class="line">id __weak obj2 = obj1;</span><br><span class="line">}</span><br><span class="line">// obj1超出作用域时,强引用失效,所以自动释放自己持有的对象。由于对象的所有者不存在,所以废弃该对象。</span><br></pre></td></tr></table></figure><h4 id="unsafe-unretained修饰符"><a href="#unsafe-unretained修饰符" class="headerlink" title="__unsafe_unretained修饰符"></a>__unsafe_unretained修饰符</h4><p><strong>概念</strong></p><p><code>__unsafe_unretained</code>修饰符是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有<code>__unsafe_unretained</code>修饰符的变量<strong>不属于编译器的内存管理对象</strong>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id unsafe_unretained obj = [[NSObject alloc] init];</span><br></pre></td></tr></table></figure><p>以上代码,编译器会报如下警告:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Assigning retained object to unsafe_unretained variable; object will be released after assignment</span><br><span class="line">//将retain对象赋值给unsafe_unretained变量;对象将在赋值后被释放.</span><br></pre></td></tr></table></figure><p>附有<code>__unsafe_unretained</code>修饰符的变量同附有<code>__weak</code>修饰符的变量一样,因为自己生成的对象不能继续为自己所有,所以生成的对象会立即被释放。到这里,<code>__unsafe_unretained</code>和<code>__weak</code>修饰符是一样的,进一步了解:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">id unsafe_unretained obj1 = [[NSObject alloc] init];</span><br><span class="line">{</span><br><span class="line">//自己生成并持有对象</span><br><span class="line">id __strong obj0 = [[NSObject alloc] init];</span><br><span class="line">//虽然obj0变量被赋值给obj1,但是obj1既不持有对象的强引用,也不持有对象的弱引用</span><br><span class="line">obj1 = obj0;</span><br><span class="line">//输出obj1对象</span><br><span class="line">NSLog("A : %@", obj1);</span><br><span class="line">}</span><br><span class="line">//obj0变量超出其作用域,强引用失效,所以自动释放自己所持有的对象,因为对象无持有者,所以废弃该对象。</span><br><span class="line"></span><br><span class="line">//输出obj1变量表示的对象,实际上对象已被释放(垂悬指针),错误访问!</span><br><span class="line">NSLog("B : %@", obj1);</span><br><span class="line">//mark:如果obj1被__weak修饰,那么obj1此时为nil.</span><br></pre></td></tr></table></figure><p>在使用<code>__unsafe_unretained</code>修饰符时,赋值给附有<code>__strong</code>修饰符的变量时有必要确保被赋值的对象确实存在,否则会造成崩溃。</p><h4 id="autoreleasing修饰符"><a href="#autoreleasing修饰符" class="headerlink" title="__autoreleasing修饰符"></a>__autoreleasing修饰符</h4><p>在ARC有效时,要通过将对象赋值给增加了<code>__autoreleasing</code>修饰符的变量来代替调用<code>autorelease</code>方法。对象赋值给赋有<code>__autoreleasing</code>修饰符的变量等价于在ARC无效时的<code>autorelease</code>方法,即使对象被注册到<code>autoreleasepool</code>。通过以下示例进行说明:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> // 01 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];</span><br><span class="line"> // 02 [obj autorelease];</span><br><span class="line"> // 03 [pool drain];</span><br><span class="line"> </span><br><span class="line"> // 04 @autoreleasepool {</span><br><span class="line"> // 05 id __autoreleasing obj2;</span><br><span class="line"> // 06 obj2 = obj;</span><br><span class="line"> // 07 }</span><br><span class="line"> </span><br><span class="line">01行代码等同04;</span><br><span class="line">02行代码等同05,06</span><br><span class="line">03行代码等同07</span><br></pre></td></tr></table></figure><p>接下来通过一些示例进一步深入了解。</p><p><strong>非显式使用<code>__autoreleasing</code>示例一:</strong></p><p>在前文中已经知道,可以使用<code>alloc</code>/<code>new</code>/<code>copy</code>/<code>mutableCopy</code>生成并持有对象,这四种持有对象的方式,可以通过引用计数的形式管理内存。如果通过其他方式取得非自己生成并持有的对象时,该如何处理呢?如下示例:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@autoreleasepool {</span><br><span class="line"> id __strong obj = [NSMutableArray array];</span><br><span class="line"> NSLog(@"Retain count: %@", [obj valueForKey:@"retainCount"]); // 无引用计数</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当看到<code>@autoreleasepool</code>时,便知道了答案,就是通过<code>@autoreleasepool</code>去管理。当生成对象的时候,编译器会检查方法名是否以<code>alloc</code>/<code>new</code>/<code>copy</code>/<code>mutableCopy</code>开始,如果不是,则自动将返回值的对象注册到<code>autoreleasepool</code>.进一步分析如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">@autoreleasepool {</span><br><span class="line">// 取得非自己生成并持有的对象</span><br><span class="line"> id __strong obj = [NSMutableArray array];</span><br><span class="line"> //因为变量obj是强引用,所以自己持有对象,并且对象由编译器判断其方法名称后,自动注册到autoreleasepool</span><br><span class="line">}</span><br><span class="line">// 此时变量obj超出其作用域,强引用失效,所以自动释放自己所持有的对象。</span><br><span class="line">//</span><br><span class="line">// 同时,随着@autoreleasepool块的解释,注册到autoreleasepool中的所有对象被自动释放。</span><br><span class="line"></span><br><span class="line">// 因为对象的所有者不存在,所以废弃对象</span><br></pre></td></tr></table></figure><p>以上例子为不使用<code>__autoreleasing</code>修饰符也能使对象注册到<code>autoreleasepool</code>。以下为取得非自己生成并持有对象时被调用方法的源代码示例:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">+ (id)array</span><br><span class="line">{</span><br><span class="line">return [[NSMutableArray alloc] init];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>也可以写成另一种形式:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">+ (id)array</span><br><span class="line">{</span><br><span class="line">id obj = [[NSMutableArray alloc] init];</span><br><span class="line">return obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为没有显式地指定所有权修饰符,所以<code>id obj</code>同附有<code>__strong</code>修饰符的<code>id __strong obj</code>完全一样。由于return使得变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但是该对象作为返回值,编译器会自动将其注册到<code>autoreleasepool</code>。</p><p><strong>非显式使用<code>__autoreleasing</code>示例二:</strong></p><p>接下来通过<code>__weak</code>修饰符对应的示例进一步认识<code>__autoreleasing</code> .</p><p>虽然<code>__weak</code>修饰符是为了避免循环引用而使用的,但是在访问<code>__weak</code>修饰符的变量时,实际上必须要访问被注册到<code>autoreleasepool</code>的对象。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">id __weak obj1 = obj0;</span><br><span class="line">NSLog(@"class = %@", [obj1 class]);</span><br></pre></td></tr></table></figure><p>以下源代码于此相同:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">id __weak obj1 = obj0;</span><br><span class="line">id __autoreleasing tmp = obj1;</span><br><span class="line">NSLog(@"class = %@", [tmp class]);</span><br></pre></td></tr></table></figure><p>为什么在访问附有<code>__weak</code>修饰符的变量时必须访问注册到<code>autoreleasepool</code>的对象呢?这是因为<code>__weak</code>修饰符只持有对象的弱引用,而在访问的过程中,该对象有可能被废弃。如果要把访问的对象注册到<code>autoreleasepool</code>,那么在<code>@autoreleasepool{}</code>介绍之前都能确保该对象存在。因此,在使用附有<code>__weak</code>修饰符的变量时就必定要使用注册到<code>autoreleasepool</code>中的对象。</p><p><strong>非显式使用<code>__autoreleasing</code>示例三:</strong></p><p>在上述官方对<code>__autoreleasing</code>的释意中,提到:用来表示通过引用(id <em>)传递的参数,结合前文中的<code>id obj</code>和<code>id __strong obj</code>完全一样。那么<code>id</code>的指针`id </em>obj<code>又是如何呢?可以由</code>id <strong>strong obj<code>的例子类推出</code>id </strong>strong <em>obj<code>吗?实际上推出来的是</code>id __autoreleasing </em>obj<code>。同样地,对象的指针</code>NSObject *<em>obj<code>便成了</code>NSObject </em>__autoreleasing *obj`。</p><p>可以归纳为:<code>id</code>的指针或对象的指针在没有显式指定时会被附加上<code>__autoreleasing</code>修饰符。</p><p>例如常见的,为了得到详细的错误信息,经常会在方法的参数中传递<code>NSError</code>对象的指针,而不是函数返回值。在<code>Cocoa</code>框架中,大多数使用这种方式,比如<code>NSString</code>的一些方法:</p><p>调用时:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[NSString stringWithContentsOfFile:<#(nonnull NSString *)#> encoding:<#(NSStringEncoding)#> error:<#(NSError *__autoreleasing _Nullable * _Nullable)#>];</span><br></pre></td></tr></table></figure><p>方法声明时:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">+ (nullable instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;</span><br></pre></td></tr></table></figure><p>从上述Apple源码中其实已经可以看出,<strong><code>id</code>的指针或对象的指针会默认附加上<code>__autoreleasing</code>修饰符</strong>,所以,在我们平时写代码时,如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">NSError *error = nil;</span><br><span class="line"> BOOL result = [self performOperationWithError:&error];</span><br><span class="line">//方法声明</span><br><span class="line">- (BOOL)performOperationWithError:(NSError **)error;</span><br></pre></td></tr></table></figure><p>实际上方法声明同下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;</span><br></pre></td></tr></table></figure><p>参数中持有<code>NSError</code>对象指针的方法,虽然为响应其执行结果,需要生成<code>NSError</code>类对象,但也必须符合内存管理的思考方式。</p><p>实现的方式如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">NSError *error = nil;</span><br><span class="line">BOOL result = [self performOperationWithError:&error];</span><br></pre></td></tr></table></figure><p>当发生错误时,方法内部处理如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error{</span><br><span class="line"> //出错了</span><br><span class="line"> *error = [NSError errorWithDomain:@"error test" code:1 userInfo:nil];</span><br><span class="line"> return NO;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通常ARC情况下,使用<code>alloc/new/copy/mutableCopy</code>方法返回值取得的对象是自己生成并持有的,其他情况下,便是取得非自己生成并持有的对象。因此,使用附有<code>__autoreleasing</code>修饰符的变量作为对象取得参数,与除<code>alloc/new/copy/mutableCopy</code>外其他方法的返回值取得对象完全一样,都会注册到<code>autoreleasepool</code>,并取得非自己生成并持有的对象。</p><p><strong>__autoreleasing注意事项</strong></p><p>赋值给对象指针时,所有权修饰符必须一致:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">//编译错误</span><br><span class="line">NSError *error = nil;</span><br><span class="line">NSError **pError = &error;</span><br><span class="line">//编译正确</span><br><span class="line">NSError *error = nil; // 默认修饰符是 __strong</span><br><span class="line">NSError *__strong *pError = &error;</span><br><span class="line">//编译正确</span><br><span class="line">NSError __weak *error = nil; </span><br><span class="line">NSError *__weak *pError = &error;</span><br><span class="line">//编译正确</span><br><span class="line">NSError __unsafe_unretain *error = nil; </span><br><span class="line">NSError *__unsafe_unretain *pError = &error;</span><br></pre></td></tr></table></figure><p>如果按照这个标准,以下代码为什么能编译通过呢?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">NSError *error = nil; // 默认修饰符为__strong</span><br><span class="line">BOOL result = [self performOperationWithError:&error];</span><br><span class="line">// 而函数参数的修饰符为__autoreleasing</span><br><span class="line">- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>原因在于,针对这种<strong>通过引用方式传入的对象</strong>,编译器会重写代码,即本地变量声明<code>__strong</code>和参数<code>__autoreleasing</code>之间的区别导致编译器创建临时变量,实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">NSError *error = nil;</span><br><span class="line">NSError __autoreleasing *tmp = error; //对象赋值</span><br><span class="line">BOOL result = [obj performOperationWithError:&tmp];//传参时的动作才是对象指针赋值,需要保证所有权修饰符一致</span><br><span class="line">error = tmp; //对象赋值</span><br></pre></td></tr></table></figure><p><strong>经典面试题</strong></p><p>在网上看到如下面试题:指出如下代码的错误之处。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">- (BOOL)validateDictionary:(NSDictionary *)dict usingChecker:(Checker *)checker error:(NSError **)error {</span><br><span class="line"> __block BOOL isValid = YES;</span><br><span class="line"> [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {</span><br><span class="line"> if([checker checkObject:obj forKey:key]) return;</span><br><span class="line"> *stop = YES;isValid = NO;</span><br><span class="line"> if(error) *error = [NSError errorWithDomain:...];</span><br><span class="line"> }];</span><br><span class="line"> return isValid;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过上述关于<code>__autoreleasing</code>的介绍可以找到一个错误是参数<code>error</code>的类型应该是<code>error:(NSError *__autoreleasing *)error</code>。第二个错误不易发现,其实是<code>NSDictionary</code>中的<code>enumerateKeysAndObjectsUsingBlock</code>方法隐式地使用了<code>autoreleasepool</code>;因此上述代码可以被翻译为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"> - (BOOL)validateDictionary:(NSDictionary *)dict usingChecker:(Checker *)checker error:(NSError **)error {</span><br><span class="line"> __block BOOL isValid = YES;</span><br><span class="line"> [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {</span><br><span class="line"> @autoreleasepool{</span><br><span class="line"> if([checker checkObject:obj forKey:key]) return;</span><br><span class="line"> *stop = YES;isValid = NO;</span><br><span class="line"> if(error) *error = [NSError errorWithDomain:...];</span><br><span class="line"> }];</span><br><span class="line"> return isValid;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于<code>error</code>在<code>@autoreleasepool{}</code>中,因此在<code>autoreleasepool</code>释放的时候,<code>error</code>会被释放掉,因此在外部访问的时候就会报错。解决的方式是在字典遍历前新声明一个<code>NSError</code>对象,遍历完再对传入的<code>error</code>赋值即可。</p><p><strong>推荐阅读</strong></p><ul><li><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html" target="_blank" rel="noopener">Advanced Memory Management Programming Guide</a></li></ul><h3 id="属性修饰符"><a href="#属性修饰符" class="headerlink" title="属性修饰符"></a>属性修饰符</h3><p>属性修饰符跟所有权修饰符类似,只是属性修饰符是在使用<code>@property</code>声明时使用,两者的对应关系如下:</p><table><thead><tr><th>属性声明的属性</th><th>所有权修饰符</th><th>备注</th></tr></thead><tbody><tr><td>assign</td><td>__unsafe_unretained</td><td>直接赋值,和引用计数无关,用来修饰简单数据类型的属性,如int</td></tr><tr><td>copy</td><td>__strong(但是赋值的是被复制的对象)</td><td>跟strong类似,但是设置方法并不保留新值,而是copy一份。修饰NSString,NSMutableString,Block等</td></tr><tr><td>retain</td><td>__strong</td><td>对旧值进行释放,并强引用新的对象,使其引用计数+1,用在MRC中型</td></tr><tr><td>strong</td><td>__strong</td><td>对新对象进行强引用,释放旧对象,使其引用计数+1。为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。型</td></tr><tr><td>unsafe_unretained</td><td>__unsafe_unretained</td><td>不安全的弱引用,与weak不同的是,如果引用的对象不可用,则当前指针不会被置为nil,会产生野指针。到</td></tr><tr><td>weak</td><td>__weak</td><td>弱引用,不对对象所赋值的对象进行持有,但是是安全的。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。在属性所指的对象被销毁时,属性值会清空,即置为nil。</td></tr></tbody></table><p>上述属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量中。需要注意的是<code>copy</code>属性不是简单的赋值,它的赋值是通过<code>NSCopying</code>接口的<code>copyWithZone:</code>方法复制赋值源所产生的对象。</p><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>以上是关于iOS内存管理整理的一些概念性的知识点,文中通过一些博客以及官方文档,对内存管理的知识进行了汇总以及源码分析实践。有错误的地方欢迎提出,有些遗漏或不足的地方后续会持续更新。</p><h2 id="补充概念"><a href="#补充概念" class="headerlink" title="补充概念"></a>补充概念</h2><ul><li><p>垂悬指针: 当所指向的对象被释放或者收回时,但是对该指针没有作任何修改,以至于该指针仍旧指向已经回收的内存地址(指针指向的内存以及被回收了,但是指针还在),此时该指针被称为垂悬指针或迷途指针。</p><ul><li><a href="https://zh.wikipedia.org/wiki/迷途指针" target="_blank" rel="noopener">维基百科</a></li></ul></li><li><p>野指针:没有被初始化的指针,该类指针即为野指针。</p></li></ul><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><ul><li><p>1、为什么Android设备用的时间久了会卡顿,而iOS的却流畅很多?</p></li><li><p>2、内存泄漏如何定位?</p></li></ul><h2 id="推荐阅读"><a href="#推荐阅读" class="headerlink" title="推荐阅读"></a>推荐阅读</h2><p><a href="https://juejin.im/post/6844903713832697864#heading-13" target="_blank" rel="noopener">iOS内存管理探究</a></p><p><a href="https://clang.llvm.org/docs/index.html" target="_blank" rel="noopener">LLVM官网</a></p><p><a href="https://opensource.apple.com" target="_blank" rel="noopener">Apple Open Source</a></p><p><a href="https://developer.apple.com/library/archive/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html" target="_blank" rel="noopener">Transitioning to ARC Release Notes</a></p><p><a href="http://gnustep.org" target="_blank" rel="noopener">GNUstep</a></p><p><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MmoryMgmt.html" target="_blank" rel="noopener">内存管理官方文档</a></p><p><a href="https://opensource.apple.com/tarballs/objc4/" target="_blank" rel="noopener">RunTime</a></p><p><a href="http://www.devzhang.cn" target="_blank" rel="noopener">做点有意思的事情</a></p><p><a href="https://www.jianshu.com/p/f8cd3a48bc78" target="_blank" rel="noopener">WWDC2020笔记</a></p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 内存管理 </tag>
</tags>
</entry>
<entry>
<title>iOS内存管理之深拷贝与浅拷贝</title>
<link href="/2020/10/25/iOS%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B9%8B%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D/"/>
<url>/2020/10/25/iOS%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B9%8B%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D/</url>
<content type="html"><![CDATA[<h1 id="iOS内存管理之深拷贝和浅拷贝"><a href="#iOS内存管理之深拷贝和浅拷贝" class="headerlink" title="iOS内存管理之深拷贝和浅拷贝"></a>iOS内存管理之深拷贝和浅拷贝</h1><p> 浅拷贝和深拷贝是一个很常见的问题,无论是在平时的开发过程中,还是在面试时,几乎都会遇到,当被问到该问题时,大部分的人都会回答说浅拷贝是指针的拷贝,深拷贝是内容的拷贝,这样回答当然没错,但如果被进一步问到浅拷贝和深拷贝是如何实现的呢?对象中的属性是如何拷贝的?集合的拷贝以及集合中的对象如何拷贝呢?等等,如果对以上的问题有些许疑惑,接下来我们一起探索一下。</p><p> 首先,对象的拷贝涉及到两个方法<code>copy</code>和<code>mutableCopy</code>, 如果自定义的对象使用这个两个方法,首先需要遵守<code>NSCopying</code>、<code>NSMutableCopying</code>协议,并实现各自对应的方法<code>copyWithZone:</code>和<code>mutableCopyWithZone:</code>通过运行时的源码<code>NSObject.mm</code>中,可以了解到两者的实现如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">+ (id)copyWithZone:(struct _NSZone *)zone {</span><br><span class="line"> return (id)self;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (id)copy {</span><br><span class="line"> return [(id)self copyWithZone:nil];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">+ (id)mutableCopyWithZone:(struct _NSZone *)zone {</span><br><span class="line"> return (id)self;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (id)mutableCopy {</span><br><span class="line"> return [(id)self mutableCopyWithZone:nil];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>copy</code>和<code>mutableCopy</code>两个方法只是简单的调用了<code>copyWithZone:</code>和<code>mutableCopyWithZone:</code>。两者的区别<code>copy</code>方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使对象本身是可修改的。例如,NSMutableString调用copy方法,将会返回不可修改的字符串对象。<code>mutableCopy</code>方法用于复制对象的可变副本。通常来说,<code>mutableCopy</code>方法总是返回对象可修改的副本,即使被复制的对象本身是不可修改的。</p><p><a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCopying.html" target="_blank" rel="noopener">Apple官方</a>针对浅拷贝和深拷贝的示意图如下:</p><p><img src="./../images/浅拷贝和深拷贝.png" alt="浅拷贝和深拷贝"></p><p>通过示意图可以初步了解到:浅拷贝的对象指向同一个地址,即指针的拷贝;深拷贝的对象指向不同的地址,即内容的拷贝。</p><p>Talk is cheap, show me the code.接下来通过具体的实践进一步了解分析<code>NSString</code>、<code>NSMutableString</code>以及自定义对象<code>TestModel</code>的拷贝:</p><h2 id="NSString"><a href="#NSString" class="headerlink" title="NSString"></a>NSString</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// NSString</span><br><span class="line">- (void)testStringCopy{</span><br><span class="line"> NSString *str = @"original value";</span><br><span class="line"> NSString *copyStr = [str copy];</span><br><span class="line"> NSMutableString *mutableCopyStr = [str mutableCopy];</span><br><span class="line"> NSLog(@"地址:%p 值:%@", str, str);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", copyStr, copyStr);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", mutableCopyStr, mutableCopyStr);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">2020-10-24 13:43:08.105253+0800 内存管理[33986:1618628] 地址:0x10cdcd160 值:original value</span><br><span class="line">2020-10-24 13:43:08.105371+0800 内存管理[33986:1618628] 地址:0x10cdcd160 值:original value</span><br><span class="line">2020-10-24 13:43:08.105490+0800 内存管理[33986:1618628] 地址:0x600000433e10 值:original value</span><br></pre></td></tr></table></figure><h2 id="NSMutableString"><a href="#NSMutableString" class="headerlink" title="NSMutableString"></a>NSMutableString</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">//NSMutableString</span><br><span class="line">- (void)testMutableCopy{</span><br><span class="line"> NSMutableString *str = [NSMutableString stringWithString:@"original value"];</span><br><span class="line"> NSMutableString *copyStr = [str copy];</span><br><span class="line"> NSMutableString *mutableCopyStr = [str mutableCopy];</span><br><span class="line"> NSLog(@"地址:%p 值:%@", str, str);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", copyStr, copyStr);</span><br><span class="line"> NSLog(@"地址:%p 值:%@", mutableCopyStr, mutableCopyStr);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">2020-10-24 13:43:08.105712+0800 内存管理[33986:1618628] 地址:0x600000439fb0 值:original value</span><br><span class="line">2020-10-24 13:43:08.105815+0800 内存管理[33986:1618628] 地址:0x600000a30820 值:original value</span><br><span class="line">2020-10-24 13:43:08.105939+0800 内存管理[33986:1618628] 地址:0x60000043a2e0 值:original value</span><br></pre></td></tr></table></figure><p>通过以上结果分析可知:</p><ul><li>非可变字符串<code>NSString</code>通过<code>copy</code>对象后,生成的对象与原对象指向同一个地址,属于浅拷贝;通过<code>mutableCopy</code>生成的对象与原对象指向不同的地址,属于深拷贝。</li><li>可变字符串<code>NSMutableString</code>无论是通过<code>copy</code>还是<code>mutableCopy</code>,生成的对象均指向不同的地址,属于深拷贝。</li></ul><h2 id="TestModel对象的拷贝"><a href="#TestModel对象的拷贝" class="headerlink" title="TestModel对象的拷贝"></a>TestModel对象的拷贝</h2><p>针对<code>TestModel</code>为测试对象的拷贝,以及对象的拷贝对其属性的影响。源码如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">//TestModel.h</span><br><span class="line"></span><br><span class="line">#import <Foundation/Foundation.h></span><br><span class="line"></span><br><span class="line">NS_ASSUME_NONNULL_BEGIN</span><br><span class="line"></span><br><span class="line">@interface TestModel : NSObject</span><br><span class="line"></span><br><span class="line">@property (nonatomic, copy) NSString *title;</span><br><span class="line">@property (nonatomic, copy) NSMutableString *subTitle;</span><br><span class="line">@property (nonatomic, strong) NSArray *norArray;</span><br><span class="line">@property (nonatomic, strong) NSMutableArray *mutArray;</span><br><span class="line"></span><br><span class="line">- (instancetype)initWithTitle:(NSString *)title subTitle:(NSMutableString *)subTitle norArray:(NSArray *)array mutArrry:(NSMutableArray *)mutArray;</span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">NS_ASSUME_NONNULL_END</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line">// TestModel.m</span><br><span class="line">#import "TestModel.h"</span><br><span class="line"></span><br><span class="line">@interface TestModel()<NSCopying, NSMutableCopying></span><br><span class="line"></span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@implementation TestModel</span><br><span class="line"></span><br><span class="line">- (instancetype)initWithTitle:(NSString *)title subTitle:(NSMutableString *)subTitle norArray:(NSArray *)array mutArrry:(NSMutableArray *)mutArray{</span><br><span class="line"> if (self = [super init]) {</span><br><span class="line"> _title = title;</span><br><span class="line"> _subTitle = subTitle;</span><br><span class="line"> _norArray = array;</span><br><span class="line"> _mutArray = mutArray;</span><br><span class="line"> }</span><br><span class="line"> return self;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- (id)copyWithZone:(nullable NSZone *)zone{</span><br><span class="line"> TestModel *model = [[[self class] allocWithZone:zone] init];</span><br><span class="line"> model.title = [self.title copyWithZone:zone]; //同[self.title copy];</span><br><span class="line"> model.subTitle = [self.subTitle copyWithZone:zone]; //同[self.subTitle copy];</span><br><span class="line"> model.norArray = [self.norArray copyWithZone:zone]; //同[self.norArray copy];</span><br><span class="line"> model.mutArray = [self.mutArray copyWithZone:zone]; //同[self.mutArray copy];</span><br><span class="line"> return model;</span><br><span class="line">}</span><br><span class="line">// 如果对象属性特别多的情况下,可以使用runtime实现,如下:</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line">- (id)copyWithZone:(NSZone * )zone{</span><br><span class="line"> id copyObject = [[[self class] allocWithZone:zone] init];</span><br><span class="line"> // 01:获取属性列表</span><br><span class="line"> unsigned int propertyCount = 0;</span><br><span class="line"> objc_property_t *propertyArray = class_copyPropertyList([self class], &propertyCount);</span><br><span class="line"> for (int i = 0; i< propertyCount; i++) {</span><br><span class="line"> objc_property_t property = propertyArray[i];</span><br><span class="line"> // 2.属性名字</span><br><span class="line"> const char * propertyName = property_getName(property);</span><br><span class="line"> NSString * key = [NSString stringWithUTF8String:propertyName];</span><br><span class="line"> // 3.通过属性名拿到属性值</span><br><span class="line"> id value=[self valueForKey:key];</span><br><span class="line"> NSLog(@"name:%s,value:%@",propertyName,value);</span><br><span class="line"> // 4.判断值对象是否响应copyWithZone</span><br><span class="line"> if ([value respondsToSelector:@selector(copyWithZone:)]) {</span><br><span class="line"> //5.设置属性值</span><br><span class="line"> [copyObject setValue:[value copy] forKey:key];</span><br><span class="line"> }else{</span><br><span class="line"> [copyObject setValue:value forKey:key];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> //mark:需要手动释放</span><br><span class="line"> free(propertyArray);</span><br><span class="line"> return copyObject;</span><br><span class="line">}</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">- (id)mutableCopyWithZone:(NSZone *)zone{</span><br><span class="line"> TestModel *model = [[[self class] allocWithZone:zone] init];</span><br><span class="line"> model.title = [self.title mutableCopyWithZone:zone]; // 同[self.title mutableCopy];</span><br><span class="line"> model.subTitle = [self.subTitle mutableCopyWithZone:zone]; // 同[self.subTitle mutableCopy];</span><br><span class="line"> model.norArray = [self.norArray mutableCopyWithZone:zone]; // 同[self.norArray mutableCopy];</span><br><span class="line"> model.mutArray = [self.mutArray mutableCopyWithZone:zone]; // 同[self.mutArray mutableCopy];</span><br><span class="line"> return model;</span><br><span class="line">}</span><br><span class="line">// 如果对象属性特别多的情况下,可以使用runtime实现,如下:</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line">- (id)mutableCopyWithZone:(NSZone *)zone{</span><br><span class="line"> id mutableCopyObj = [[[self class]allocWithZone:zone] init];</span><br><span class="line"> //1.获取属性列表</span><br><span class="line"> unsigned int count = 0;</span><br><span class="line"> objc_property_t* propertylist = class_copyPropertyList([self class], &count);</span><br><span class="line"> for (int i = 0; i < count ; i++) {</span><br><span class="line"> objc_property_t property = propertylist[i];</span><br><span class="line"> //2.获取属性名</span><br><span class="line"> const char * propertyName = property_getName(property);</span><br><span class="line"> NSString * key = [NSString stringWithUTF8String:propertyName];</span><br><span class="line"> //3.获取属性值</span><br><span class="line"> id value = [self valueForKey:key];</span><br><span class="line"> //4.判断属性值对象是否遵守NSMutableCopying协议</span><br><span class="line"> if ([value respondsToSelector:@selector(mutableCopyWithZone:)]) {</span><br><span class="line"> //5.设置对象属性值</span><br><span class="line"> [mutableCopyObj setValue:[value mutableCopy] forKey:key];</span><br><span class="line"> }else{</span><br><span class="line"> [mutableCopyObj setValue:value forKey:key];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> //mark:需要手动释放</span><br><span class="line"> free(propertylist);</span><br><span class="line"> return mutableCopyObj;</span><br><span class="line">}</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">@end</span><br></pre></td></tr></table></figure><p>测试代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">- (void)testCustomObject{</span><br><span class="line"> NSMutableArray *mutableArray = [NSMutableArray array];</span><br><span class="line"> TestModel *model = [[TestModel alloc] initWithTitle:@"title" subTitle:[NSMutableString stringWithString:@"subTitle"] norArray:@[@"test1", @"test2"] mutArrry:mutableArray];</span><br><span class="line"> TestModel *copyModel = [model copy];</span><br><span class="line"> TestModel *mutableModel = [model mutableCopy];</span><br><span class="line"> // 测试对象的拷贝</span><br><span class="line"> NSLog(@"******TestModel内存地址******");</span><br><span class="line"> NSLog(@"原始地址:%p", model);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel);</span><br><span class="line"> // 测试对象拷贝对NSString类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性title(NSString)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.title);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.title);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.title);</span><br><span class="line"> // 测试对象拷贝对NSMutableString类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性subTitle(NSMutableString)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.subTitle);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.subTitle);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.subTitle);</span><br><span class="line"> // 测试对象拷贝对非可变集合类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性norArray(NSArray)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.norArray);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.norArray);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.norArray);</span><br><span class="line"> // 测试对象拷贝对可变几何类型属性的影响</span><br><span class="line"> NSLog(@"****** 属性mutArrry(NSMutableArray)内存地址 ******");</span><br><span class="line"> NSLog(@"原始地址:%p", model.mutArray);</span><br><span class="line"> NSLog(@"copy地址:%p", copyModel.mutArray);</span><br><span class="line"> NSLog(@"mutableCopy地址:%p", mutableModel.mutArray);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">2020-10-25 15:40:28.564704+0800 内存管理[39368:1919107] ******TestModel内存地址******</span><br><span class="line">2020-10-25 15:40:28.564882+0800 内存管理[39368:1919107] 原始地址:0x600000eaa400</span><br><span class="line">2020-10-25 15:40:28.564988+0800 内存管理[39368:1919107] copy地址:0x600000eaa370</span><br><span class="line">2020-10-25 15:40:28.565097+0800 内存管理[39368:1919107] mutableCopy地址:0x600000eaa100</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.565191+0800 内存管理[39368:1919107] ****** 属性title(NSString)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.565468+0800 内存管理[39368:1919107] 原始地址:0x10e8f5188</span><br><span class="line">2020-10-25 15:40:28.565923+0800 内存管理[39368:1919107] copy地址:0x10e8f5188</span><br><span class="line">2020-10-25 15:40:28.566376+0800 内存管理[39368:1919107] mutableCopy地址:0x8356f4dfe5d0308a</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.566881+0800 内存管理[39368:1919107] ****** 属性subTitle(NSMutableString)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.569415+0800 内存管理[39368:1919107] 原始地址:0x600000eaa430</span><br><span class="line">2020-10-25 15:40:28.578373+0800 内存管理[39368:1919107] copy地址:0x8355e20852d2afc7</span><br><span class="line">2020-10-25 15:40:28.578531+0800 内存管理[39368:1919107] mutableCopy地址:0x8355e20852d2afc7</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.578646+0800 内存管理[39368:1919107] ****** 属性norArray(NSArray)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.578771+0800 内存管理[39368:1919107] 原始地址:0x6000000a9780</span><br><span class="line">2020-10-25 15:40:28.579093+0800 内存管理[39368:1919107] copy地址:0x6000000a9780</span><br><span class="line">2020-10-25 15:40:28.579223+0800 内存管理[39368:1919107] mutableCopy地址:0x600000eaa310</span><br><span class="line"></span><br><span class="line">2020-10-25 15:40:28.579318+0800 内存管理[39368:1919107] ****** 属性mutArrry(NSMutableArray)内存地址 ******</span><br><span class="line">2020-10-25 15:40:28.579674+0800 内存管理[39368:1919107] 原始地址:0x600000eaa0d0</span><br><span class="line">2020-10-25 15:40:28.580027+0800 内存管理[39368:1919107] copy地址:0x7fff8062cc40</span><br><span class="line">2020-10-25 15:40:28.580466+0800 内存管理[39368:1919107] mutableCopy地址:0x600000eaa3d0</span><br></pre></td></tr></table></figure><p>通过以上测试可以发现:</p><ul><li>针对对象的拷贝,无论是<code>copy</code>还是<code>mutableCopy</code>都会产生新的对象,均为深拷贝。</li><li>对象中的属性,遵循可变类型的属性无论是<code>copy</code>还是<code>mutableCopy</code>都会产生新的对象,均为深拷贝;非可变类型的属性,<code>copy</code>时没有产生新的对象,为指针拷贝,即浅拷贝;<code>mutableCopy</code>时产生新的对象,为内容拷贝,即深拷贝。</li></ul><h2 id="集合的的拷贝"><a href="#集合的的拷贝" class="headerlink" title="集合的的拷贝"></a>集合的的拷贝</h2><p>针对集合的拷贝,<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html" target="_blank" rel="noopener">Apple官方</a>给的示意图如下:</p><p><img src="./../images/集合的深浅拷贝.png" alt="集合的深浅拷贝"></p><p>之所以将集合对象拿出来单独处理,原因在于集合中会包含很多的对象,这些对象也需要区分深拷贝与浅拷贝,更深一些,集合中也可能包含集合对象,如此一来,显得更加麻烦。接下来将以<code>NSArray</code>的深拷贝与浅拷贝,将集合的深浅拷贝分为四种情况进一步了解:</p><h3 id="1、浅拷贝"><a href="#1、浅拷贝" class="headerlink" title="1、浅拷贝"></a>1、浅拷贝</h3><p><strong>代码如下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">NSArray *oriArr = [NSArray arrayWithObjects:@"test", nil];</span><br><span class="line">NSArray *copyArr = [oriArr copy];</span><br><span class="line">NSLog(@"%p", oriArr);</span><br><span class="line">NSLog(@"%p", copyArr);</span><br></pre></td></tr></table></figure><p><strong>日志分析:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 通过日志分析可以确定为浅拷贝</span><br><span class="line">2020-10-25 16:59:33.093252+0800 内存管理[39941:1967202] 0x600002fafa60</span><br><span class="line">2020-10-25 16:59:33.093358+0800 内存管理[39941:1967202] 0x600002fafa60</span><br></pre></td></tr></table></figure><h3 id="2、单层深拷贝"><a href="#2、单层深拷贝" class="headerlink" title="2、单层深拷贝"></a>2、单层深拷贝</h3><p>单层深拷贝指的是对<code>NSArray</code>对象的深拷贝,并非对其内部的元素进行处理。</p><p><strong>代码如下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">NSArray *oriArr = [NSArray arrayWithObjects:@"test", nil];</span><br><span class="line">NSMutableArray *mutArr = [oriArr mutableCopy];</span><br><span class="line">NSLog(@"%p", oriArr);</span><br><span class="line">NSLog(@"%p", mutArr);</span><br><span class="line">//内部元素</span><br><span class="line">NSLog(@"%p", oriArr[0]);</span><br><span class="line">NSLog(@"%p", mutArr[0]);</span><br></pre></td></tr></table></figure><p><strong>日志分析:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 通过日志分析可以发现,NSArray对象通过mutableCopy进行了深拷贝,但是其内部元素并没有完全深拷贝,因此称为单层深拷贝</span><br><span class="line">2020-10-25 17:08:32.338871+0800 内存管理[40113:1978516] 0x60000223cb60</span><br><span class="line">2020-10-25 17:08:32.338960+0800 内存管理[40113:1978516] 0x600002c5d380</span><br><span class="line">2020-10-25 17:08:32.339046+0800 内存管理[40113:1978516] 0x10af9b4e8</span><br><span class="line">2020-10-25 17:08:32.339134+0800 内存管理[40113:1978516] 0x10af9b4e8</span><br></pre></td></tr></table></figure><h3 id="3、双层深拷贝"><a href="#3、双层深拷贝" class="headerlink" title="3、双层深拷贝"></a>3、双层深拷贝</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">- (void)testCollectionCopy{ </span><br><span class="line">// 创建</span><br><span class="line"> NSMutableString *mutString1 = [NSMutableString stringWithString:@"test1"];</span><br><span class="line"> NSMutableString *mutString2 = [NSMutableString stringWithString:@"test2"];</span><br><span class="line"> NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutString2, nil];</span><br><span class="line"> NSArray *testArr = [NSArray arrayWithObjects:mutString1, mutableArr, nil];</span><br><span class="line"> //通过官方文档提供的方式进行创建copy</span><br><span class="line"> NSArray *testArrCopy = [[NSArray alloc] initWithArray:testArr copyItems:YES];</span><br><span class="line"> //testArr和testArrCopy进行对比</span><br><span class="line"> NSLog(@"===我是分割线01===");</span><br><span class="line"> NSLog(@"%p", testArr);</span><br><span class="line"> NSLog(@"%p", testArrCopy);</span><br><span class="line"> </span><br><span class="line"> //testArr和testArrCopy中元素指针对比</span><br><span class="line"> //mutableString对比</span><br><span class="line"> NSLog(@"===我是分割线02===");</span><br><span class="line"> NSLog(@"%p", testArr[0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[0]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr对比</span><br><span class="line"> NSLog(@"===我是分割线03===");</span><br><span class="line"> NSLog(@"%p", testArr[1]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr中元素对比,即mutString2进行对比</span><br><span class="line"> NSLog(@"===我是分割线04===");</span><br><span class="line"> NSLog(@"%p", testArr[1][0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1][0]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>日志分析:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">2020-10-25 17:35:01.731301+0800 内存管理[40436:1999803] ===我是分割线01===</span><br><span class="line">2020-10-25 17:35:01.734516+0800 内存管理[40436:1999803] 0x60000147a2c0</span><br><span class="line">2020-10-25 17:35:01.734661+0800 内存管理[40436:1999803] 0x60000147a2e0</span><br><span class="line">2020-10-25 17:35:01.734784+0800 内存管理[40436:1999803] ===我是分割线02===</span><br><span class="line">2020-10-25 17:35:01.734964+0800 内存管理[40436:1999803] 0x600001a528b0</span><br><span class="line">2020-10-25 17:35:01.735420+0800 内存管理[40436:1999803] 0x87c4312271f96ce5</span><br><span class="line">2020-10-25 17:35:01.735838+0800 内存管理[40436:1999803] ===我是分割线03===</span><br><span class="line">2020-10-25 17:35:01.736861+0800 内存管理[40436:1999803] 0x600001a52550</span><br><span class="line">2020-10-25 17:35:01.738048+0800 内存管理[40436:1999803] 0x600001627300</span><br><span class="line">2020-10-25 17:35:01.738733+0800 内存管理[40436:1999803] ===我是分割线04===</span><br><span class="line">2020-10-25 17:35:01.738939+0800 内存管理[40436:1999803] 0x600001a524c0</span><br><span class="line">2020-10-25 17:35:01.739575+0800 内存管理[40436:1999803] 0x600001a524c0</span><br></pre></td></tr></table></figure><p>通过以上日志可以发现:copy后,只有mutableArr中的mutalbeString2指针地址没有变化。而testArr的指针和testArr中的mutableArr、mutableString1的指针地址均发生变化,所以称之为双层深复制。</p><p><strong>限制</strong></p><p>initWithArray: copyItems:会使NSArray中元素均执行copy方法,这也是在testArr中放入NSMutableArray和NSMutableString的原因。如果放入的是NSArray或者NSString,执行copy后,只会发生指针复制;如果放入的是未实现NSCopying协议的对象,调用这个方法甚至会crash。</p><h3 id="4、完全深拷贝"><a href="#4、完全深拷贝" class="headerlink" title="4、完全深拷贝"></a>4、完全深拷贝</h3><p>如果想完美的解决NSArray嵌套NSArray这种情形,可以使用归档、解档的方式。</p><p><strong>代码如下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">- (void)testDeepCopyCollection{</span><br><span class="line"> NSMutableString *mutString1 = [NSMutableString stringWithString:@"test1"];</span><br><span class="line"> NSMutableString *mutString2 = [NSMutableString stringWithString:@"test1"];</span><br><span class="line"> NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutString2, nil];</span><br><span class="line"> NSArray *testArr = [NSArray arrayWithObjects:mutString1, mutableArr, nil];</span><br><span class="line"> //通过归档、解档的方式创建copy</span><br><span class="line"> NSArray *testArrCopy = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:testArr]];</span><br><span class="line"> //testArr和testArrCopy进行对比</span><br><span class="line"> NSLog(@"===我是分割线01===");</span><br><span class="line"> NSLog(@"%p", testArr);</span><br><span class="line"> NSLog(@"%p", testArrCopy);</span><br><span class="line"> </span><br><span class="line"> //testArr和testArrCopy中元素指针对比</span><br><span class="line"> //mutableString对比</span><br><span class="line"> NSLog(@"===我是分割线02===");</span><br><span class="line"> NSLog(@"%p", testArr[0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[0]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr对比</span><br><span class="line"> NSLog(@"===我是分割线03===");</span><br><span class="line"> NSLog(@"%p", testArr[1]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1]);</span><br><span class="line"> </span><br><span class="line"> //mutableArr中元素对比,即mutalbeString2进行对比</span><br><span class="line"> NSLog(@"===我是分割线04===");</span><br><span class="line"> NSLog(@"%p", testArr[1][0]);</span><br><span class="line"> NSLog(@"%p", testArrCopy[1][0]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>日志:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">2020-10-25 21:14:30.738233+0800 内存管理[41176:2082596] ===我是分割线01===</span><br><span class="line">2020-10-25 21:14:30.738370+0800 内存管理[41176:2082596] 0x60000173a480</span><br><span class="line">2020-10-25 21:14:30.738475+0800 内存管理[41176:2082596] 0x60000173a660</span><br><span class="line">2020-10-25 21:14:30.738575+0800 内存管理[41176:2082596] ===我是分割线02===</span><br><span class="line">2020-10-25 21:14:30.738670+0800 内存管理[41176:2082596] 0x600001950780</span><br><span class="line">2020-10-25 21:14:30.738766+0800 内存管理[41176:2082596] 0x600001950990</span><br><span class="line">2020-10-25 21:14:30.738965+0800 内存管理[41176:2082596] ===我是分割线03===</span><br><span class="line">2020-10-25 21:14:30.745114+0800 内存管理[41176:2082596] 0x6000019507e0</span><br><span class="line">2020-10-25 21:14:30.745286+0800 内存管理[41176:2082596] 0x600001950a50</span><br><span class="line">2020-10-25 21:14:30.745426+0800 内存管理[41176:2082596] ===我是分割线04===</span><br><span class="line">2020-10-25 21:14:30.745631+0800 内存管理[41176:2082596] 0x6000019507b0</span><br><span class="line">2020-10-25 21:14:30.745943+0800 内存管理[41176:2082596] 0x600001950a80</span><br></pre></td></tr></table></figure><p>通过以上日志发现,<code>testArr</code>和<code>testArrCopy</code>中的元素以及集合中集合的指针完全不同,所以完成了深拷贝。</p><p><strong>限制</strong></p><p>归档和解档的前提是NSArray中所有的对象都实现了NSCoding协议。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是关于深拷贝和浅拷贝的一些探究,概括为浅拷贝为指针的复制,不会创建一个对象;深拷贝为内容的复制,会创建一个新的对象,集合的拷贝需要多加注意,以免引起一些问题。在平时的项目开发中,需要根据需要而决定使用深拷贝还是浅拷贝。</p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档:"></a>参考文档:</h2><ul><li><a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCopying.html" target="_blank" rel="noopener">对象的拷贝</a></li><li><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html" target="_blank" rel="noopener">集合的拷贝</a></li><li><a href="https://www.jianshu.com/p/ebbac2fec4c6" target="_blank" rel="noopener">iOS Copy</a></li></ul>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 内存管理 </tag>
</tags>
</entry>
<entry>
<title>Xcode小技巧之Snippet</title>
<link href="/2020/09/20/Xcode%E5%B0%8F%E6%8A%80%E5%B7%A7%E4%B9%8BSnippet/"/>
<url>/2020/09/20/Xcode%E5%B0%8F%E6%8A%80%E5%B7%A7%E4%B9%8BSnippet/</url>
<content type="html"><![CDATA[<p>Xcode中有许多小功能,可能经常被使用到,但是却没被发觉,如<code>Xcode Snippets</code>。</p><p>在敲代码时,经常会遇到刚敲几个字母就会有相应的提示,点击<code>enter</code>选中时,代码段立即显示出来,再根据对应的提示进行代码编写,极大地提升了代码的编程效率,如:</p><p><img src="./../images/snippet01.png" alt="snippet"></p><p>带有<code>{}</code>图标的均代表有该操作,只需输入几个单词就能完成如下代码的编写。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">static dispatch_once_t onceToken;</span><br><span class="line">dispatch_once(&onceToken, ^{</span><br><span class="line"> <#code to be executed once#></span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>以上实例就是<code>Xcode Snippets</code>的应用,这是Xcode自带的一些<code>Code Snippet</code>,我们也可以自定义接下来将通过一些实例自定义一些常见的<code>Code Snippet</code>。</p><h2 id="查找Code-Snippet"><a href="#查找Code-Snippet" class="headerlink" title="查找Code Snippet"></a>查找<code>Code Snippet</code></h2><p>Xcode中默认自带一些常见的<code>Code Snippet</code>,通过以下方式可以快速打开<code>Code Snippet</code>:</p><ul><li>1、快捷键<code>cmd + shift + L</code></li><li><p>2、点击Xcode右上角的工具栏中的<code>+</code>,如下图所示:</p><p> <img src="./../images/snippet02.png" alt="snippet"></p></li></ul><p>按照上述其中之一操作后,即可显示如下界面:</p><p><img src="./../images/snippet03.png" alt="snippet"></p><p>通过输入对应的字段即可查找相关的<code>Code Snippet</code>.</p><h2 id="新增自定义Code-Snippet"><a href="#新增自定义Code-Snippet" class="headerlink" title="新增自定义Code Snippet"></a>新增自定义<code>Code Snippet</code></h2><p>在Xcode中,通过在源文件中右键,并点击<code>Create Code Snippet</code>,即可快速创建代码段,如下图所示:</p><p><img src="./../images/snippet04.png" alt="snippet"></p><p>打开面板后,通过输入对应的name、Completion,即可完成代码段的新建,如下图所示:</p><p><img src="./../images/snippet05.png" alt="snippet"></p><p>其中<code>Completion</code>代表代码段的简写,通过在编码时快速输入该简写,达到展示代码段的目的,如下所示:</p><p><img src="./../images/snippet06.png" alt="snippet"></p><h2 id="自定义常见的Code-Snippet"><a href="#自定义常见的Code-Snippet" class="headerlink" title="自定义常见的Code Snippet"></a>自定义常见的<code>Code Snippet</code></h2><p>可以将平时用经常用到,但又都是大量重读性的代码自定义成代码段,提高编写效率。如常见的属性声明、固定值等</p><p><strong>01、属性声明:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">//Strong</span><br><span class="line">@property (nonatomic, strong) <#Class#> *<#object#>;</span><br><span class="line">//Weak</span><br><span class="line">@property (nonatomic, weak) <#Class#> *<#object#>;</span><br><span class="line">//assign</span><br><span class="line">@property (nonatomic, assign) <#Class#> <#property#>;</span><br><span class="line">//copy</span><br><span class="line">@property (nonatomic, copy) NSString *<#string#>;</span><br><span class="line">//delegate</span><br><span class="line">@property (nonatomic, weak) id<<#protocol#>> <#delegate#>;</span><br><span class="line">//Block声明</span><br><span class="line">@property (nonatomic, copy) <#returnType#>(^<#blockName#>)(<#arguments#>);</span><br></pre></td></tr></table></figure><p><strong>02、固定值</strong><br>罗列部分,根据需要增加</p><table><thead><tr><th>含义</th><th>Completion</th><th>代码段</th></tr></thead><tbody><tr><td>屏幕宽度</td><td>ksw</td><td>[UIScreen mainScreen].bounds.size.width;</td></tr><tr><td>屏幕高度</td><td>ksh</td><td>[UIScreen mainScreen].bounds.size.height;</td></tr></tbody></table><p><strong>03、单例</strong></p><h2 id="共享代码段"><a href="#共享代码段" class="headerlink" title="共享代码段"></a>共享代码段</h2><p>针对自定义的一些代码段会被存放在<code>~/Library/Developer/Xcode /UserData/CodeSnippets/</code>目录下,可以备份对应的文件,以便切换电脑时复用。</p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> Xcode </tag>
</tags>
</entry>
<entry>
<title>Mac上使用Hexo搭建博客</title>
<link href="/2020/09/16/hello-world/"/>
<url>/2020/09/16/hello-world/</url>
<content type="html"><![CDATA[<h1 id="Mac上使用Hexo搭建博客"><a href="#Mac上使用Hexo搭建博客" class="headerlink" title="Mac上使用Hexo搭建博客"></a>Mac上使用Hexo搭建博客</h1><h2 id="前提条件"><a href="#前提条件" class="headerlink" title="前提条件"></a>前提条件</h2><ul><li>安装<code>node.js</code></li><li>安装<code>Git</code><h2 id="安装Hexo"><a href="#安装Hexo" class="headerlink" title="安装Hexo"></a>安装Hexo</h2>终端中执行如下命令进行安装:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g hexo-cli</span><br></pre></td></tr></table></figure><p>安装完成后,执行如下命令,Hexo将会在指定的文件夹下创建所需的文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">hexo init <folder></span><br><span class="line">cd <folder></span><br><span class="line">npm install</span><br></pre></td></tr></table></figure><h2 id="创建主题"><a href="#创建主题" class="headerlink" title="创建主题"></a>创建主题</h2><p>使用如下命令下载主题,如next主题<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cd themes/</span><br><span class="line">git init</span><br><span class="line">git clone https://github.com/iissnan/hexo-theme-next.git</span><br></pre></td></tr></table></figure></p><h2 id="常见操作"><a href="#常见操作" class="headerlink" title="常见操作"></a>常见操作</h2><ul><li><p>启动服务</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo server</span><br></pre></td></tr></table></figure></li><li><p>创建新文章</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hexo new "My New Post"</span><br><span class="line">执行完后,Hexo\source_posts中多了一个文件博名.md,也可直接进入Hexo\source_posts中创建.md文件</span><br></pre></td></tr></table></figure></li><li><p>生成静态资源</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo generate</span><br></pre></td></tr></table></figure></li><li><p>发布博文</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo deploy</span><br></pre></td></tr></table></figure></li><li><p>清理</p><pre><code>hexo clean</code></pre></li><li>快捷指令<ul><li>hexo g == hexo generate</li><li>hexo d == hexo deploy</li><li>hexo s == hexo server</li><li>hexo n == hexo new </li></ul></li></ul>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> Hexo </tag>
</tags>
</entry>
<entry>
<title>数据结构与算法基础</title>
<link href="/2020/08/30/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"/>
<url>/2020/08/30/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/</url>
<content type="html"><![CDATA[<p>数据结构是指同一数据对象中各数据元素间存在的关系。算法是解决某一特定类型问题的有限运算序列,算法的实现必须借助程序设计语言中提供的数据类型及其运算。一个算法的效率往往与数据的表现形式有关,因此数据结构的选择对数据处理的效率起着至关重要的作用。</p><h1 id="基础入门篇"><a href="#基础入门篇" class="headerlink" title="基础入门篇"></a>基础入门篇</h1><p>作为一名iOSer,综合考虑了下,决定还是选择Swift作为学习算法的主要语言。一方面有Swift语言基础,能够减少算法学习的前期准备成本,另一方面,通过算法的练习,提升对Swift语言的熟练度。</p><h2 id="Swift语言入门"><a href="#Swift语言入门" class="headerlink" title="Swift语言入门"></a>Swift语言入门</h2><p><strong>基础语法</strong></p><p><a href="https://swiftgg.gitbook.io/swift/" target="_blank" rel="noopener">SwiftGG-The Swift Programming Language</a></p><h1 id="数据结构篇"><a href="#数据结构篇" class="headerlink" title="数据结构篇"></a>数据结构篇</h1><h2 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h2><p>数组是最基本的数据结构,可以在内存中连续存储多个相同元素,在内存中的分配也是连续的,数组中的元素通过下标进行访问,下标从0开始。在Swift中,将OC时代的NSMutableArray和NSArray统一归为Array。虽然看上去只有一种数据结构,但是它的实现其实有三种:</p><ul><li><p><strong>ContiguousArray<element></element></strong>:是一个比较特殊的数组,会将对应的数组存储在连续的内存区域中。如果存储的元素是结构体或枚举(值类型,栈上操作),那么<code>Array</code>和<code>ContiguousArray</code>的效率相似;如果数组中存储的元素是对象(<code>class</code>或<code>@objc</code>),而且不需要将数组桥接到Array或者传递给<code>Objective-C</code>的<code>API</code>时,那么<code>ContiguousArray</code>比<code>Array</code>更高效,而且拥有更多可预测的性能。</p></li><li><p><strong>Array<element></element></strong>:当存储值类型时,性能和<code>ContiguousArray</code>无差别,而且内存是连续的。但是如果存储的是对象(<code>class</code>或<code>@objc</code>),则会自动桥接到<code>Objective-C</code>中的<code>NSArray</code>。</p></li><li><p><strong>ArraySlice<element></element></strong>:不是数组,而是数组片段,与原数组享用同一区域,不创建新的存储空间。但是,修改ArraySlice中的元素时,不会影响到原数组。</p></li></ul><h3 id="数组中的count和capacity"><a href="#数组中的count和capacity" class="headerlink" title="数组中的count和capacity"></a>数组中的count和capacity</h3><p>Swift中Array的count属性用于描述数组中元素的个数,而capacity用于描述数组的容量,即不分配新存储空间的数组可以包含的元素总数。如果要向数组中增加元素,并且超过其容量,则该数组必须增加其容量。为了提高数组的扩容效率,数组的容量的大小都是成倍增长,如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">var cArr = ContiguousArray<Any>(repeating: 3, count: 2)</span><br><span class="line">print(cArr.count) // 2</span><br><span class="line">print(cArr.capacity) // 2</span><br><span class="line">cArr.append(1)</span><br><span class="line">print(cArr.capacity) // 4</span><br><span class="line">cArr.append(2)</span><br><span class="line">print(cArr.capacity) // 4</span><br><span class="line">cArr.append(3)</span><br><span class="line">print(cArr.capacity) // 8 </span><br><span class="line">// 当调用removeAll时,数组的所有空间被释放</span><br><span class="line">cArr.removeAll()</span><br><span class="line">print(cArr.count) // 0</span><br><span class="line">print(cArr.capacity) // 0</span><br></pre></td></tr></table></figure><p>由于Swift数组将其元素连续存储在内存中,因此必须通过通过重新分配其内部存储,并且将所有元素从原先的存储中复制到新的存储中。如果事先知道需要向数组汇总添加多少个元素,则可使用<code>reserveCapacity</code>方法来预设数组中的容量,以减少数组<code>capacity</code>变化带来的影响。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var cArr = ContiguousArray<Any>(repeating: 3, count: 2)</span><br><span class="line">cArr.reserveCapacity(10) // 10</span><br></pre></td></tr></table></figure><p>通常无需担心数组容量的问题,重新分配很少影响性能问题,Swift使用有效的重新分配方式,以便重新分配的数组在最终计数中为对数。但是如果数组非常大(例如,Mac上为千兆字节或iOS设备上为数十兆字节),或者对性能敏感的数据存入数组(音频缓冲区,需要几秒内播放),则需要考虑使用reserveCapacity避免重新分配。</p><h4 id="ArraySlice注意点"><a href="#ArraySlice注意点" class="headerlink" title="ArraySlice注意点"></a>ArraySlice注意点</h4><p>ArraySlice是Array,ContiguousArray的一个切片或ArraySlice实例。ArraySlice并没有将切片的元素拷贝到新的内存中。修改ArraySlice中的值不会引起原数组的改变,同样,原数组的值修改,也不会引起ArraySlice的变化。如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">let testArr = Array(1...7)</span><br><span class="line">let middle = testArr.count / 2</span><br><span class="line">var firstHalf = testArr[..<middle]</span><br><span class="line">let secondHalf = testArr[middle...]</span><br><span class="line">firstHalf[0] = 10</span><br><span class="line">print(secondHalf.startIndex) // 3</span><br><span class="line">print(testArr) // [1, 2, 3, 4, 5, 6, 7]</span><br><span class="line">print(firstHalf) // [10, 2, 3]</span><br><span class="line">testArr[0] = 20</span><br><span class="line">print(firstHalf) // [10, 2, 3]</span><br><span class="line">print(testArr) // [20, 2, 3, 4, 5, 6, 7]</span><br></pre></td></tr></table></figure><p>ArraySlice的索引并总是从0开始,而是按照原数据中的索引。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">print(secondHalf.startIndex) // 3</span><br><span class="line">print(secondHalf[0]) // Fatal error: Index out of bounds</span><br></pre></td></tr></table></figure><blockquote><p>Apple官方文档中指出不建议长期存储实例。因为即使原始数组的生命周期结束后,切片也会保留对较大数组整个存储的引用,而不仅仅是对其呈现的部分的引用。因此,切片的长期存储可能会延长原本无法访问的元素的寿命,这些元素似乎是内存和对象泄漏。</p></blockquote><h3 id="数组中的常见操作"><a href="#数组中的常见操作" class="headerlink" title="数组中的常见操作"></a>数组中的常见操作</h3><ul><li><p>数组的声明</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">// 声明一个可变数组</span><br><span class="line">var arr1 = Array<Int>()</span><br><span class="line">var arr2 = [Int]() //推荐使用</span><br><span class="line">// 声明一个不可修改的数组</span><br><span class="line">let arr3 = [1,2,3]</span><br><span class="line">let arr4 = [Int](repeating: 0, count: 3)</span><br></pre></td></tr></table></figure></li><li><p>增加一个元素</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">arr1.append(1)</span><br><span class="line">或</span><br><span class="line">arr1 += [1]</span><br></pre></td></tr></table></figure></li><li><p>删除一个元素</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arr1.remove(at: 0)</span><br></pre></td></tr></table></figure></li><li><p>修改一个元素</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arr1[1] = 6</span><br></pre></td></tr></table></figure></li><li><p>取出一个元素</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var value = arr1[0]</span><br></pre></td></tr></table></figure></li><li><p>遍历</p></li></ul><p>常用方式一:<strong>forEach</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">arr1.forEach { (value) in</span><br><span class="line"> print(value)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>常用方式二:<strong>for…in</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line">for i in 0..<arr1.count - 1 {</span><br><span class="line"> print(i)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 枚举遍历</span><br><span class="line">for (index, value) in arr1.enumerated() {</span><br><span class="line"> print("\(index): '\(value)'")</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>区别:</p><h2 id="字典与集合"><a href="#字典与集合" class="headerlink" title="字典与集合"></a>字典与集合</h2><h2 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h2><h2 id="栈和队列"><a href="#栈和队列" class="headerlink" title="栈和队列"></a>栈和队列</h2><h2 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h2><h2 id="二叉树"><a href="#二叉树" class="headerlink" title="二叉树"></a>二叉树</h2><h1 id="基础算法篇"><a href="#基础算法篇" class="headerlink" title="基础算法篇"></a>基础算法篇</h1><p>二分查找,排序算法, 动态规划</p><h1 id="算法思维篇"><a href="#算法思维篇" class="headerlink" title="算法思维篇"></a>算法思维篇</h1><p>数据结构和算法思维的目的都是为了降低时间复杂度。数据结构是从数据组织形式的角度去达成这个目标,而算法思维则是从数据处理的思路上去达成这个目标。如果数据处理的逻辑上出现缺陷,即使通过高效的数据处理解决了问题,但是会产生很多无效的计算,造成时间浪费。</p><h2 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h2><blockquote><p>排序是指让一组无序数据变成有序的过程,一般默认有序为从小到大的排列顺序。</p></blockquote><p><strong>衡量排序算法优劣的方式</strong></p><ul><li>时间复杂度<ul><li>包括最好时间复杂度、最坏时间复杂度和平均时间复杂度。</li></ul></li><li>空间复杂度<ul><li>如果空间复杂度为1,也称为原地排序。</li></ul></li><li>稳定性<ul><li>指相等的数据对象,在排序后,顺序是否保持不变。</li></ul></li></ul><h3 id="冒泡排序"><a href="#冒泡排序" class="headerlink" title="冒泡排序"></a>冒泡排序</h3><p><strong>原理</strong><br>从第一个数据开始,依次比较相邻元素的大小,如果前者大于后者,这交换两个元素的位置,将大的元素排到后面,通过多轮迭代,直到不用交换为止。</p><p><strong>性能</strong></p><ul><li><strong>时间复杂度</strong><ul><li>最好时间复杂度:<strong>O(n)</strong><ul><li>即当数据刚好是顺序排序时,只要挨个比较一般即可。</li></ul></li><li>最坏时间复杂度:<strong>O(n*n)</strong><ul><li>即当数据刚好逆序时,每轮排序都需要比较n次,并且重复n次。</li></ul></li><li>平均时间复杂度: <strong>O(n*n)</strong><ul><li>即数据杂乱无章时</li></ul></li></ul></li><li><strong>空间复杂度</strong><ul><li><strong>O(1)</strong>:不占用额外的空间</li></ul></li></ul><p><strong>代码实现</strong><br> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">// 冒泡排序(Swift)</span><br><span class="line">func bubbleSort() {</span><br><span class="line"> var list = [-1,2,4,1,0,9,6,7]</span><br><span class="line"> for i in 1..<list.count {</span><br><span class="line"> for j in 0..<(i - 1) {</span><br><span class="line"> if list[j] > list[j + 1] {</span><br><span class="line"> let temp = list[j]</span><br><span class="line"> list[j] = list[j + 1]</span><br><span class="line"> list[j+1] = temp</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> print(list)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="插入排序"><a href="#插入排序" class="headerlink" title="插入排序"></a>插入排序</h3><p><strong>原理</strong><br>选取未排序的数据插入到已排序区间的合适位置,直到未排序区间为空。</p><p><strong>性能</strong></p><ul><li><strong>时间复杂度</strong><ul><li>最好时间复杂度:<strong>O(n)</strong><ul><li>即当数据刚好是顺序排序时,每次只用比较一次就能找到正确的位置。</li></ul></li><li>最坏时间复杂度:<strong>O(n*n)</strong><ul><li>即当数据刚好完全逆序时,每轮排序都需要比较n次才能找到正确的位置区间。</li></ul></li><li>平均时间复杂度: <strong>O(n*n)</strong><ul><li>因为往数组中插入一个元素的平均时间复杂度为O(n),而插入排序可以理解为重复n次的数组插入操作</li></ul></li></ul></li><li><strong>空间复杂度</strong><ul><li><strong>O(1)</strong>:不占用额外的空间。</li></ul></li></ul><h2 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h2><h3 id="二分查找"><a href="#二分查找" class="headerlink" title="二分查找"></a>二分查找</h3><ul><li>二分查找的时间复杂度是O(logn)。</li><li>二分查找的循环次数并不确定。一般是达到某个条件就跳出循环。因此编码的时候,多数采用while循环加break跳出代码结构。</li><li>二分查找处理的原问题必须是有序的。</li></ul><h3 id="二叉树搜索"><a href="#二叉树搜索" class="headerlink" title="二叉树搜索"></a>二叉树搜索</h3><h2 id="递归思维"><a href="#递归思维" class="headerlink" title="递归思维"></a>递归思维</h2><blockquote><p>递归是指在函数的定义中使用函数自身的方法,即自己调用自己。递归的基本思想就是把规模大的问题转化为规模小的相同的子问题来解决。递归的实现包含两部分,一是递归主体,而是终止条件。</p></blockquote><p>递归的数学模型是<a href="https://zh.wikipedia.org/wiki/%E6%95%B0%E5%AD%A6%E5%BD%92%E7%BA%B3%E6%B3%95" target="_blank" rel="noopener">数学归纳法</a>。当一个问题满足如下的两个条件时,就可以使用递归的方法求解:</p><ul><li>1、可以拆解为除了数据规模之外,求解思路完全相同的方法求解;</li><li>2、存在终止条件。</li></ul><p>递归的核心思想是把规模大的问题转化为规模小的相似的子问题来解决。<br>例子:通过递归求解汉诺塔问题:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">func hannio(n: Int, x: String, y: String, z: String) {</span><br><span class="line"> if n < 1 {</span><br><span class="line"> print("汉诺塔的层数不能小于1")</span><br><span class="line"> } else if (n == 1) {</span><br><span class="line"> print("移动:" + x + "->" + z)</span><br><span class="line"> }</span><br><span class="line"> else {</span><br><span class="line"> hannio(n: n - 1, x: x, y: z, z: y)</span><br><span class="line"> print("移动:" + x + "->" + z)</span><br><span class="line"> hannio(n: n - 1, x: y, y: x, z: z)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="分治法"><a href="#分治法" class="headerlink" title="分治法"></a>分治法</h2><blockquote><p>分治法的核心思想是“分而治之”,就是把一个规模大、高难度的问题,分解为若干个小规模、低难度的小问题,再针对小的问题,逐一击破。</p></blockquote><h3 id="分治法的使用方法"><a href="#分治法的使用方法" class="headerlink" title="分治法的使用方法"></a>分治法的使用方法</h3><p>当使用分治法解决问题时,一般原问题都需要具备以下几个特征:</p><ul><li><strong>1、难度在降低</strong>:即原问题的解决难度,随着数据的规模的缩小而降低。</li><li><strong>2、问题可分</strong>:原问题可以分解为若干个规模较小的同类型问题。</li><li><strong>3、解可合并</strong>:利用所有子问题的解,可合并出原问题的解。</li><li><strong>4、相互独立</strong>:各个子问题之间相互独立,某个子问题的求解不会影响到另一个子问题。如果子问题之间不独立,则分治法需要重复地解决公共的子问题,造成效率低下的结果。</li></ul><p>分治法的常见实例为二分查找。二分查找的步骤如下:</p><ul><li>1、选择一个标志i将集合L分为二个子集,一般为中位数;</li><li>2、判断标志L(i)是否能够与要查找的值des相等,相等则直接返回结果;</li><li>3、如果不相等,需要判断L(i)与des的大小;</li><li>4、基于判断的结果决定下不步是向左查找还是向右查找。如果向某个方向查找的空间为0,则返回结果未找到。</li><li>5、回到步骤1。<br>二分查找最差的情况是找到最后一个数字才完成,那么此时的最大的复杂度为O(logn)。<br>分治法的例子:在有序数组[1,2,3,4,5,6,7,8,9]中查找8有没有出现过:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">func findValue(_ list:[Int], _ targrt:Int) -> Bool {</span><br><span class="line"> var middle = 0, low = 0, high = list.count - 1</span><br><span class="line"> var isFind = false</span><br><span class="line"> while (low < high) {</span><br><span class="line"> middle = (high + low) / 2</span><br><span class="line"> if list[middle] == targrt {</span><br><span class="line"> print(String(format: "😄找到了,哈哈哈,index:%@", String(middle)))</span><br><span class="line"> isFind = true</span><br><span class="line"> break</span><br><span class="line"> } else if (list[middle] > targrt) {</span><br><span class="line"> // 说明在左侧 low -> middle - 1之间</span><br><span class="line"> high = middle - 1</span><br><span class="line"> } else {</span><br><span class="line"> // 说明在右侧 middle -> high 之间</span><br><span class="line"> low = middle + 1</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if !isFind {</span><br><span class="line"> print("😠没找到,呜呜呜呜")</span><br><span class="line"> }</span><br><span class="line"> return isFind</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>规律</strong></p><ul><li>二分法查找的时间复杂度是O(logn),这也是分治法普遍具备的特征。当约束了时间复杂度为O(logn)或O(nlogn)时,可以考虑分治法是否可行。</li><li>二分查找的循环次数并不确定。一般是达到某个条件就跳出循环。通常采用while循环加break的代码结构</li><li>二分查找处理的原问题必须是有序的。</li></ul><h2 id="动态规划"><a href="#动态规划" class="headerlink" title="动态规划"></a>动态规划</h2><blockquote><p>动态规划问题之所以难,是因为动态规划的解题方法并没有那么标准化,需要因题而异,仔细分析问题并寻找解决方案。</p></blockquote><h2 id="感谢与推荐"><a href="#感谢与推荐" class="headerlink" title="感谢与推荐"></a>感谢与推荐</h2><ul><li><a href="https://kaiwu.lagou.com/course/courseInfo.htm?courseId=185#/content" target="_blank" rel="noopener">重学数据结构与算法</a></li><li><a href="https://greyireland.gitbook.io/algorithm-pattern/" target="_blank" rel="noopener">algorithm-pattern</a></li><li><a href="https://books.halfrost.com/leetcode/" target="_blank" rel="noopener">冰霜LeetCodeCookBook</a></li><li><a href="https://github.com/MisterBooo/LeetCodeAnimation" target="_blank" rel="noopener">LeetCodeAnimation</a></li></ul>]]></content>
<categories>
<category> 数据结构与算法 </category>
</categories>
<tags>
<tag> 数据结构与算法 </tag>
</tags>
</entry>
<entry>
<title>时间复杂度</title>
<link href="/2020/08/23/%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6/"/>
<url>/2020/08/23/%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6/</url>
<content type="html"><![CDATA[<h3 id="时间复杂度"><a href="#时间复杂度" class="headerlink" title="时间复杂度"></a>时间复杂度</h3><p>算法的时间复杂度,用来度量算法的运行时间,以算法中频度最大的语句来度量,记作: T(n) = O(f(n)),它表示随着输入大小n的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。</p><p><strong>时间复杂度的计算</strong><br>如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n)),通常称为大O推导法。时间复杂度计算的一般法则如下:</p><ul><li>一个顺序结构的代码,时间复杂度是O(1),对于一个for循环,假设循环次数为n, 循环体的时间复杂度为O(m),那么该循环的时间复杂度为O(n * m)。如下:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">func testFunc(_ n: Int) {</span><br><span class="line"> for i in 0..<n { // 循环次数:n</span><br><span class="line"> print("index = \(i)") // 循环体时间复杂度O(1)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><pre><code>上述时间时间复杂度为O(n * 1),即O(n).</code></pre><ul><li>嵌套循环时,假设循环体的时间复杂度为O(n),各个循环的循环次数为a、b、c…,那么该循环侧时间复杂度为O(n <em> a </em> b <em> c </em> …),分析时可由内向外分析:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">func testFunc(_ n: Int) {</span><br><span class="line"> for i in 0..<n { // 循环次数 n</span><br><span class="line"> for j in 0..<n{ // 循环次数 n</span><br><span class="line"> print("i = \(i) -->j = \(j)") // 循环体时间复杂度O(1)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>此时时间复杂度为O(n * n x 1),即O(n^2)。</p><ul><li>对于顺序执行的语句或算法,总的时间复杂度为其中最大的时间复杂度。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">func testFunc(_ n: Int) {</span><br><span class="line"> // 第一部分时间复杂度为O(n^2)</span><br><span class="line"> for i in 0..<n { // 循环次数 n</span><br><span class="line"> for j in 0..<n{ // 循环次数 n</span><br><span class="line"> print("i = \(i) -->j = \(j)") // 循环体时间复杂度O(1)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // 第二部分时间复杂度为O(n)</span><br><span class="line"> for i in 0..<n { // 循环次数:n</span><br><span class="line"> print("index = \(i)") // 循环体时间复杂度O(1)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>此时时间复杂度为max(O(n^2),O(n)),即O(n^2).</p><ul><li>对于条件判断语句,总的时间复杂度等于其中<strong>时间复杂度最大的路径</strong>的时间复杂度。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">func testFunc(_ n: Int) {</span><br><span class="line"> if (n > 3) {</span><br><span class="line"> // 第一条路径复杂度为O(n^2)</span><br><span class="line"> for i in 0..<n { // 循环次数 n</span><br><span class="line"> for j in 0..<n{ // 循环次数 n</span><br><span class="line"> print("i = \(i) -->j = \(j)") // 循环体时间复杂度O(1)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> // 第二条路径时间复杂度为O(n)</span><br><span class="line"> for i in 0..<n { // 循环次数:n</span><br><span class="line"> print("index = \(i)") // 循环体时间复杂度O(1)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>此时时间复杂度为max(O(n^2),O(n)),即O(n^2)。</p><ul><li>二分查找的时间复杂度是O(logn)。</li></ul><h2 id="时间复杂度的增长率"><a href="#时间复杂度的增长率" class="headerlink" title="时间复杂度的增长率"></a>时间复杂度的增长率</h2><p><img src="./../images/timg.png" alt="timg"></p><h3 id="空间复杂度"><a href="#空间复杂度" class="headerlink" title="空间复杂度"></a>空间复杂度</h3><p>算法的空间复杂度是指在算法中所需要的辅助空间单元,而不包括问题的原始数据占用的空间。计算公式为:S(n)=O(f(n)),其中n为问题的规模,f(n)为语句关于n所占存储空间的函数。</p><h2 id="推荐阅读"><a href="#推荐阅读" class="headerlink" title="推荐阅读"></a>推荐阅读</h2><ul><li><a href="https://www.jianshu.com/p/f4cca5ce055a" target="_blank" rel="noopener">十分钟搞定时间复杂度</a></li></ul>]]></content>
<categories>
<category> 数据结构与算法 </category>
</categories>
<tags>
<tag> 数据结构与算法 </tag>
</tags>
</entry>
<entry>
<title>focus</title>
<link href="/2019/05/09/focus/"/>
<url>/2019/05/09/focus/</url>
<content type="html"><![CDATA[<h2 id="没有记录-就没有发生"><a href="#没有记录-就没有发生" class="headerlink" title="没有记录 就没有发生"></a>没有记录 就没有发生</h2>]]></content>
</entry>
</search>