-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1885 lines (1584 loc) · 312 KB
/
index.html
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 3.8.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
<link rel="stylesheet" href="/lib/pace/pace-theme-minimal.min.css">
<script src="/lib/pace/pace.min.js"></script>
<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = {"hostname":"yoursite.com","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","Pisces | Gemini":240,"display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
</script>
<meta name="description" content="starryKey">
<meta property="og:type" content="website">
<meta property="og:title" content="Focus's blog">
<meta property="og:url" content="http://yoursite.com/index.html">
<meta property="og:site_name" content="Focus's blog">
<meta property="og:description" content="starryKey">
<meta property="og:locale" content="zh-CN">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Focus's blog">
<meta name="twitter:description" content="starryKey">
<link rel="canonical" href="http://yoursite.com/">
<script id="page-configurations">
// https://hexo.io/docs/variables.html
CONFIG.page = {
sidebar: "",
isHome : true,
isPost : false,
lang : 'zh-CN'
};
</script>
<title>Focus's blog</title>
<noscript>
<style>
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-header { opacity: initial; }
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="container use-motion">
<div class="headband"></div>
<header class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="切换导航栏">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<h1 class="site-title">Focus's blog</h1>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger">
</div>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="main-menu menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>
</li>
<li class="menu-item menu-item-about">
<a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>
</li>
</ul>
</nav>
</div>
</header>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
<main class="main">
<div class="main-inner">
<div class="content-wrap">
<div class="content index posts-expand">
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2020/11/21/一文读懂iOS内存管理之概念篇/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.JPG">
<meta itemprop="name" content="Focus">
<meta itemprop="description" content="starryKey">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Focus's blog">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2020/11/21/一文读懂iOS内存管理之概念篇/" class="post-title-link" itemprop="url">一文读懂iOS内存管理之概念篇</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2020-11-21 12:04:48" itemprop="dateCreated datePublished" datetime="2020-11-21T12:04:48+08:00">2020-11-21</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2020-11-22 22:00:58" itemprop="dateModified" datetime="2020-11-22T22:00:58+08:00">2020-11-22</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/iOS/" itemprop="url" rel="index"><span itemprop="name">iOS</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<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>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2020/10/25/iOS内存管理之深拷贝与浅拷贝/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.JPG">
<meta itemprop="name" content="Focus">
<meta itemprop="description" content="starryKey">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Focus's blog">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2020/10/25/iOS内存管理之深拷贝与浅拷贝/" class="post-title-link" itemprop="url">iOS内存管理之深拷贝与浅拷贝</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2020-10-25 21:27:44" itemprop="dateCreated datePublished" datetime="2020-10-25T21:27:44+08:00">2020-10-25</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2020-11-21 15:36:21" itemprop="dateModified" datetime="2020-11-21T15:36:21+08:00">2020-11-21</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/iOS/" itemprop="url" rel="index"><span itemprop="name">iOS</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<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>