iOS开发的四种内存管理
<h4 id="一block内存管理">一、block内存管理</h4><h5 id="1block内存类型">1.block内存类型</h5>
<p>block内存分为三种类型:</p>
<ul>
<li>_NSConcreteGlobalBlock(全局)</li>
<li>_NSConcreteStackBlock(栈)</li>
<li>_NSConcreteMallocBlock(堆)</li>
</ul>
<h5 id="2三种类型的内存的创建时机">2.三种类型的内存的创建时机</h5>
<p><strong>1)对于<code>_NSConcreteStackBlock</code>和<code>_NSConcreteGlobalBlock</code>类型</strong><br>
<code>_NSConcreteStackBlock</code>和<code>_NSConcreteGlobalBlock</code>这两种类型的block,我们可以手动创建,如下所示:</p>
<pre><code>void (^globalBlock)() = ^{
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^stackBlock1)() = ^{
};
}
return 0;
}
</code></pre>
<p>那么我们怎么确定这两个block,就是我们所说的两种类型的block呢,我们可以使用<code>clang -rewrite-objc xxx.m</code>(报错可以使用详细命令: <code>clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxxxx.m</code>)编译转换成C++实现,就可以看到转换完的结果,如下所示:</p>
<pre><code>// globalBlock
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
// stackBlock
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
}
return 0;
}
</code></pre>
<p>可以看出可以看出globalBlock是_NSConcreteGlobalBlock类型,即在全局区域创建,block变量存储在全局数据存储区;stackBlock是_NSConcreteStackBlock类型,即在栈区创建。<br>
<strong>2)对于<code>_NSConcreteMallocBlock</code>类型</strong><br>
<code>NSConcreteMallocBlock</code>类型的内存是通过<code>_NSConcreteStackBlock</code>类型的block copy得到的,那么哪些类型会对block进行copy呢?</p>
<ul>
<li><strong>block作为返回值</strong></li>
</ul>
<pre><code>
// 如果是weak类型的block,依然不会自动进行copy
// <__NSStackBlock__: 0x7fff5fbff728>
__weak void (^weakBlock)() = ^{i;};
// ARC情况下输出
// <__NSMallocBlock__
NSLog(@"%@", );
- (id)callBack:(void (^)(void))callBack
{
NSLog(@"%@", callBack);
return callBack;
}
//输出结果
<__NSStackBlock__: 0x7ffee2559838>
<__NSMallocBlock__: 0x600003a99ce0>
</code></pre>
<ul>
<li><strong>block作为属性,使用copy修饰时(strong修饰符不会改变block内存类型)</strong></li>
</ul>
<pre><code>@property (copy, nonatomic) id myCopyBlock;
@property (strong, nonatomic) id myStrongBlock;
// 如果是weak类型的block,依然不会自动进行copy
// <__NSStackBlock__: 0x7fff5fbff728>
__weak void (^weakBlock)() = ^{i;};
NSLog(@"%@", weakBlock);
//会进行copy操作
//<__NSMallocBlock__: 0x6000037e8db0>
self.myCopyBlock= weakBlock;
NSLog(@"%@", self.myCopyBlock);
// 会进行strong操作
// <__NSStackBlock__: 0x7fff5fbff728>
self.myStrongBlock= weakBlock;
NSLog(@"%@", self.myStrongBlock);
//打印结果
//<__NSStackBlock__: 0x7ffee8ed5838>
//<__NSMallocBlock__: 0x6000037e8db0>
//<__NSStackBlock__: 0x7ffee8ed5838>
</code></pre>
<ul>
<li><strong>block为strong类型,且捕获了外部变量时。</strong></li>
</ul>
<pre><code>int i = 10;
void (^block)() = ^{i;};
// 因为block为strong类型,且捕获了外部变量,所以赋值时,自动进行了copy
// <__NSMallocBlock__: 0x100206920>
NSLog(@"%@", block);
</code></pre>
<p><strong>对于作为参数传递的block,其类型是什么呢?</strong></p>
<pre><code>int i = 10;
void (^block)() = ^{i;};
__weak void (^weakBlock)() = ^{i;};
void (^stackBlock)() = ^{};
// ARC情况下
// 创建时,都会在栈中
// <__NSStackBlock__: 0x7fff5fbff730>
NSLog(@"%@", ^{i;});
// 因为block为strong类型,且捕获了外部变量,所以赋值时,自动进行了copy
// <__NSMallocBlock__: 0x100206920>
NSLog(@"%@", block);
// 如果是weak类型的block,依然不会自动进行copy
// <__NSStackBlock__: 0x7fff5fbff728>
NSLog(@"%@", weakBlock);
// 如果block是strong类型,并且没有捕获外部变量,那么就会转换成__NSGlobalBlock__
// <__NSGlobalBlock__: 0x100001110>
NSLog(@"%@", stackBlock);
;
;
;
- (id)callBack:(void (^)(void))callBack
{
NSLog(@"%@", callBack);
return callBack;
}
//结果
//<__NSStackBlock__: 0x7ffee2572838>
//<__NSMallocBlock__: 0x600002e881e0>
// <__NSGlobalBlock__: 0x10d68c0f8>
//<__NSStackBlock__: 0x7ffee2572838>
//<__NSMallocBlock__: 0x600002e881e0>
//<__NSGlobalBlock__: 0x10d68c0f8>
</code></pre>
<p>我们可以发现函数参数的block为什么类型,block在函数中就是什么类型。</p>
<h4 id="二autorelease内存管理">二、autorelease内存管理</h4>
<h5 id="1哪些对象是autorelease管理的">1、哪些对象是autorelease管理的?</h5>
<p><strong>1)enumerateObjectsUsingBlock中的对象</strong></p>
<pre><code> enumerateObjectsUsingBlock:^(id_Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//自动缓存池
}
</code></pre>
<p><strong>2)__autoreleasing 修饰的对象</strong></p>
<pre><code>id obj = ;
id __autoreleasing o = obj;
</code></pre>
<p><strong>3)array、dictiongnary、stringWithString等非init或者new方法生成的对象</strong></p>
<pre><code>int main(int argc, char * argv[]) {
NSMutableArray *array = ;
NSMutableArray *array1 = ;
NSMutableDictionary *dict = ;
NSMutableString *str = ;
</code></pre>
<p><strong>以上类型实验结果:</strong></p>
<pre><code>int main(int argc, char * argv[]) {
id obj = ;
id __autoreleasing o = obj;
id __autoreleasing o1 = obj;
NSMutableArray *array = ;
;
;
;
;
;
;
;
NSMutableArray *array1 = ;
;
;
;
;
;
;
[array1 enumerateObjectsUsingBlock:^(id_Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id __autoreleasing o = obj;
}];
NSMutableDictionary *dict = ;
;
NSMutableString *str = ;
// _objc_autoreleasePoolPrint()
}
//在armv7上、使用_objc_autoreleasePoolPrint()调试打印结果
(lldb) po _objc_autoreleasePoolPrint()
objc: ##############
objc: AUTORELEASE POOLS for thread 0x20d080
objc: 6 releases pending.
objc: ................PAGE(hot) (cold)
objc: 0x7be71ca0NSObject
objc: 0x7be71ca0NSObject
objc: 0x7c470560__NSArrayM
objc: 0x7be723b0__NSArrayM
objc: 0x7c170b80__NSDictionaryM
objc: 0x7be72540__NSCFString
objc: ##############
0x0a5c2500
//在arm64的手机上、使用_objc_autoreleasePoolPrint()调试打印结果
(lldb) po _objc_autoreleasePoolPrint()
objc: ##############
objc: AUTORELEASE POOLS for thread 0x1151d75c0
objc: 5 releases pending.
objc: ................PAGE(hot) (cold)
objc: 0x600003a6c840__NSArrayI//系统创建对象
objc: 0x600000c358b0__NSSetI//系统创建对象
objc: 0x600002d380d0NSObject
objc: 0x600002d380d0NSObject
objc: 0x6000021649f0__NSArrayM
objc: ##############
0xe0675b6edaa1003f
(lldb) po 0x6000021649f0
<__NSArrayM 0x600001435d70>(
0,
1,
2,
3,
4,
5,
6
)
</code></pre>
<p><strong>注意:这里面的实验结果不一样,在arm64上、<code>array、dictiongnary、stringWithString等方法生成的对象</code>,在自动缓存池中只能看见第一个对象,而armv7的机型上,可以看见所有的,不知这里是什么原因,有知道的欢迎告诉我</strong></p>
<p><strong>两个常用的调试命令</strong></p>
<pre><code>//打印自动缓存池对象
_objc_autoreleasePoolPrint()
//打印引用计数
_objc_rootRetainCount(obj)
</code></pre>
<blockquote>
<p>作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:519832104 不管你是小白还是大牛欢迎入驻,分享经验,讨论技术,大家一起交流学习成长!</p>
</blockquote>
<p>另附上一份各好友收集的大厂面试题,需要iOS开发学习资料、面试真题,可以添加iOS开发进阶交流群,进群可自行下载!</p>
<p><img src="https://upload-images.jianshu.io/upload_images/23675445-aacd467c64de6083.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/801/format/webp"></p>
<h5 id="2autoreleasepool什么时候创建的里面的对象又是什么时候释放的">2、autoreleasePool什么时候创建的,里面的对象又是什么时候释放的?</h5>
<p><strong>1)系统通过runloop创建的autoreleasePool</strong><br>
runloop 可以说是iOS 系统的灵魂。内存管理/UI 刷新/触摸事件这些功能都需要 runloop 去管理和实现。runloop是通过线程创建的,和线程保持一对一的关系,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。</p>
<p><strong>runloop和autoreleasePool又是什么关系呢?对象又是什么时候释放的?</strong></p>
<p>App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。</p>
<p>第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。</p>
<p>第二个 Observer 监视了两个事件: <code>BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。</code>这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。</p>
<p>在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。</p>
<p><strong>验证结果:</strong></p>
<pre><code>int main(int argc, char * argv[]) {
id obj = ;
id __autoreleasing o = obj;
id __autoreleasing o1 = obj;
NSMutableArray *array = ;
;
;
;
;
;
;
;
// _objc_autoreleasePoolPrint()
}
//_objc_autoreleasePoolPrint调试打印结果
(lldb) po _objc_autoreleasePoolPrint()
objc: ##############
objc: AUTORELEASE POOLS for thread 0x107b0d5c0
objc: 5 releases pending.
objc: ................PAGE(hot) (cold)
objc: 0x6000000d66c0__NSArrayI
objc: 0x6000036b9680__NSSetI
objc: 0x600001780160NSObject
objc: 0x600001780160NSObject
objc: 0x600001bcd230__NSArrayM
objc: ##############
0x67c4279ea7c20079
(lldb) po 0x600001bcd230
<__NSArrayM 0x600001bcd230>(
0,
1,
2,
3,
4,
5,
6
)
(lldb) po
<NSThread: 0x6000000953c0>{number = 1, name = main}
</code></pre>
<p><strong>2)手动autoreleasePool</strong><br>
我们可以通过<code>@autoreleasepool {}</code>方式手动创建autoreleasepool对象,那么这个对象什么时候释放呢?答案是除了autoreleasepool的大括号就释放了,我们可以看下下面的实验结果</p>
<pre><code>int main(int argc, char * argv[]) {
//1\. _objc_autoreleasePoolPrint()
@autoreleasepool {
id obj = ;
id __autoreleasing o = obj;
id __autoreleasing o1 = obj;
//2\. _objc_autoreleasePoolPrint()
}
//3\. _objc_autoreleasePoolPrint()
}
//1\. _objc_autoreleasePoolPrint()
(lldb) po _objc_autoreleasePoolPrint()
objc: ##############
objc: AUTORELEASE POOLS for thread 0x11331a5c0
objc: 2 releases pending.
0x2196ee78f1e100fd
objc: ................PAGE(hot) (cold)
objc: 0x600002dbb600__NSArrayI
objc: 0x600001bd8a50__NSSetI
objc: ##############
//2\. _objc_autoreleasePoolPrint()
(lldb) po _objc_autoreleasePoolPrint()
objc: ##############
objc: AUTORELEASE POOLS for thread 0x11331a5c0
0x2196ee78f1e100fd
objc: 5 releases pending.
objc: ................PAGE(hot) (cold)
objc: 0x600002dbb600__NSArrayI
objc: 0x600001bd8a50__NSSetI
objc: ################POOL 0x7fc2a9802048
objc: 0x600003afc030NSObject
objc: 0x600003afc030NSObject
objc: ##############
//3\. _objc_autoreleasePoolPrint()
(lldb) po _objc_autoreleasePoolPrint()
objc: ##############
objc: AUTORELEASE POOLS for thread 0x11331a5c0
0x2196ee78f1e100fd
objc: 2 releases pending.
objc: ................PAGE(hot) (cold)
objc: 0x600002dbb600__NSArrayI
objc: 0x600001bd8a50__NSSetI
objc: ##############
(lldb)
</code></pre>
<p>从上面1、2、3的结果可以看出,当对象出了autoreleasepool的大括号就释放了。</p>
<p><strong>3、子线程的autoreleasepool对象的管理?</strong><br>
线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。所以在我们创建子线程的时候,如果没有获取runloop,那么也就没用通过runloop来创建autoreleasepool,那么我们的autorelease对象是怎么管理的,会不会存在内存泄漏呢?答案是否定的,当子线程有autoreleasepool的时候,autorelease对象通过其来管理,如果没有autoreleasepool,会通过调用 autoreleaseNoPage 方法,将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!这部分我们可以看下runtime中NSObject.mm的部分,有相关代码。</p>
<pre><code>static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
//调用 autoreleaseNoPage 方法管理autorelease对象。
return autoreleaseNoPage(obj);
}
}
</code></pre>
<h4 id="三weak对象内存管理">三、weak对象内存管理</h4>
<p><strong>1.释放时机</strong><br>
在dealloc的时候,会将weak属性的值设置为nil</p>
<p><strong>2.如何实现</strong><br>
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针,对于 weak 对象会放入一个 hash 表中,<strong>Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组</strong>。 当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。<br>
<strong>注:</strong>由于可能多个weak指针指向同一个对象,所以value为一个数组</p>
<p><strong>weak 的实现原理可以概括以下三步:</strong></p>
<p><strong>1)初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。</strong><br>
我们以下面这行代码为例:</p>
<p>代码清单1:示例代码</p>
<pre><code>{
id __weak obj1 = obj;
}
</code></pre>
<p>当我们初始化一个weak变量时,runtime会调用objc_initWeak函数。这个函数在Clang中的声明如下:</p>
<pre><code>id objc_initWeak(id *object, id value);
</code></pre>
<p>其具体实现如下:</p>
<pre><code>id objc_initWeak(id *object, id value)
{
*object = 0;
return objc_storeWeak(object, value);
}
</code></pre>
<p>示例代码轮换成编译器的模拟代码如下:</p>
<pre><code>id obj1;
objc_initWeak(&obj1, obj);
</code></pre>
<p>因此,这里所做的事是先将obj1初始化为0(nil),然后将obj1的地址及obj作为参数传递给objc_storeWeak函数。<br>
objc_initWeak函数有一个前提条件:就是object必须是一个没有被注册为__weak对象的有效指针。而value则可以是null,或者指向一个有效的对象。</p>
<p><strong>2)添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数。</strong><br>
objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。</p>
<p><strong>3)释放时,调用clearDeallocating函数。</strong><br>
clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。</p>
<h4 id="四nsstring内存管理">四、NSString内存管理</h4>
<h5 id="1nsstring内存的类型">1.NSString内存的类型</h5>
<p>NSString内存分为两种类型:</p>
<ul>
<li>__NSCFConstantString(常量区)</li>
<li>__NSCFString(堆区)、NSTaggedPointerString(堆区)</li>
</ul>
<h5 id="2两种内存类型的创建时机">2.两种内存类型的创建时机。</h5>
<p>生成一个NSString类型的字符串有三种方法:</p>
<ul>
<li>方法1.直接赋值:</li>
</ul>
<pre><code> NSString *str1 = @"my string";
</code></pre>
<ul>
<li>方法2.类函数初始化生成:</li>
</ul>
<pre><code>NSString *str2 = ;
</code></pre>
<ul>
<li>方法3.实例方法初始化生成:</li>
</ul>
<pre><code>NSString *str3 = [ initWithString:@"my string"];
NSString *str4 = [initWithFormat:@"my string"];
</code></pre>
<p><strong>1)对于<code>__NSCFConstantString</code></strong><br>
这种类型的字符串是常量字符串。该类型的字符串以字面量的方式创建,保存在字符串常量区,是在编译时创建的。</p>
<pre><code>NSString *a = @"str";
NSString *b = [init];
NSString *c = [initWithString:@"str"];
NSString *d = ;
NSLog(@"%@ : class = %@",a,NSStringFromClass());
NSLog(@"%@ : class = %@",b,NSStringFromClass());
NSLog(@"%@ : class = %@",c,NSStringFromClass());
NSLog(@"%@ : class = %@",d,NSStringFromClass());
//打印结果
2019-06-23 19:23:13.240611+0800 BlockDemo str : class = __NSCFConstantString
2019-06-23 19:23:13.240764+0800 BlockDemo: class = __NSCFConstantString
2019-06-23 19:23:13.240870+0800 BlockDemo str : class = __NSCFConstantString
2019-06-23 19:23:13.240957+0800 BlockDemo str : class = __NSCFConstantString
</code></pre>
<p><strong>2)对于<code>__NSCFString</code>和<code>NSTaggedPointerString</code></strong></p>
<ul>
<li>__NSCFString 表示对象类型的字符串,在运行时创建,保存在堆区,初始引用计数为1,其内存管理方式就是对象的内存管理方式。</li>
<li>NSTaggedPointerString是对__NSCFString类型的一种优化,在运行创建字符串时,会对字符串内容及长度作判断,若内容由ASCII字符构成且长度较小(具体要多小暂时不太清楚),这时候创建的字符串类型就是 NSTaggedPointerString</li>
</ul>
<p><strong>对于不可以变NSString的测试结果:</strong></p>
<pre><code>NSString *e = [initWithFormat:@"str"];
NSString *f = ;
NSString *g = ;
NSString *h = ;
NSLog(@"%@ : class = %@",e,NSStringFromClass());
NSLog(@"%@ : class = %@",f,NSStringFromClass());
NSLog(@"%@ : class = %@",g,NSStringFromClass());
NSLog(@"%@ : class = %@",h,NSStringFromClass());
//打印结果
2019-06-23 19:27:19.115212+0800 BlockDemo str : class = NSTaggedPointerString
2019-06-23 19:27:19.115286+0800 BlockDemo str : class = NSTaggedPointerString
2019-06-23 19:27:19.115388+0800 BlockDemo 123456789 : class = NSTaggedPointerString
2019-06-23 19:27:19.115476+0800 BlockDemo 1234567890 : class = __NSCFString
</code></pre>
<p><strong>对于可变的NSMutableString</strong></p>
<pre><code>NSMutableString *ms1 = [init];
NSMutableString *ms2 = [initWithString:@"str"];
NSMutableString *ms3 = [initWithFormat:@"str"];
NSMutableString *ms4 = ;
NSMutableString *ms5 = ;
NSMutableString *ms6 = ;
NSLog(@"%@ : class = %@",ms1,NSStringFromClass());
NSLog(@"%@ : class = %@",ms2,NSStringFromClass());
NSLog(@"%@ : class = %@",ms3,NSStringFromClass());
NSLog(@"%@ : class = %@",ms4,NSStringFromClass());
NSLog(@"%@ : class = %@",ms5,NSStringFromClass());
NSLog(@"%@ : class = %@",ms6,NSStringFromClass());
//打印结果
2019-06-23 19:34:08.521931+0800 BlockDemo: class = __NSCFString
2019-06-23 19:34:08.522058+0800 BlockDemo str : class = __NSCFString
2019-06-23 19:34:08.522131+0800 BlockDemo str : class = __NSCFString
2019-06-23 19:34:08.522196+0800 BlockDemo str : class = __NSCFString
2019-06-23 19:34:08.522281+0800 BlockDemo 123456789 : class = __NSCFString
2019-06-23 19:34:08.522372+0800 BlockDemo 1234567890 : class = __NSCFString
</code></pre>
<p>从结果我们可以看出来NSMutableString都是分配在堆区,且是__NSCFString类型,NSString中Format相关方法也是都分配在堆区,但是会根据字符串的长度,区分为__NSCFString和NSTaggedPointerString两种。在分配堆区的这些变量,其实一部分是正常的对象,一部分变成autorelease对象,具体是哪些,我们可以使用_objc_autoreleasePoolPrint()打印出来,比如实例中的g、ms4、ms5、ms6。</p>
<p>参考:<br>
引用计数带来的一次讨论<br>
Objective-C 引用计数原理<br>
各个线程 Autorelease 对象的内存管理<br>
Practical Memory Management<br>
iOS内存管理<br>
Xcode 10 下如何创建可调试的objc4-723、objc4-750.1工程<br>
Block技巧与底层解析<br>
Objective-C Autorelease Pool 的实现原理<br>
《招聘一个靠谱的 iOS》<br>
iOS 中 weak 的实现原理<br>
iOS 底层解析weak的实现原理<br>
weak的生命周期:具体实现方法<br>
iOS weak 关键字漫谈<br>
Objective-C weak 弱引用实现<br>
NSString内存管理<br>
NSString的内存管理问题<br>
iOS开发--引用计数与ARC</p>
<p>点击此处,立即与iOS大牛交流学习</p><br><br>
来源:https://www.cnblogs.com/chengxyyh/p/13169137.html
頁:
[1]