服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - Objective-C学习之ARC的实现方法

Objective-C学习之ARC的实现方法

2021-04-08 17:23小白HAKU IOS

自动引用计数(Automatic Reference Counting, ARC)把压在程序员们肩头的管理内存的重担卸除了不少,更不用说让跟踪内存泄漏那样的烦心事也少了很多。下面这篇文章主要给大家介绍了关于Objective-C学习之ARC的实现方法,需要的朋友可以参

前言

ARC出现以前,程序员们只能靠retain/relese/autorelease来确保对象们恰好“坚持”到被需要的那一刻。如果忘了retain,或者多次release某个对象,程序就会发生内存泄漏的问题,甚至直接崩溃。

在Xcode 4.2中,除了语法检查外,Apple的新LLVM编译器还将内存管理的苦差事接了过来,它会检查代码,决定何时释放对象。Apple的文档里是这么定义ARC的:

“自动引用计数(ARC)是一个编译器级的功能,它能简化Cocoa应用中对象生命周期管理(内存管理)的流程。”

ARC使内存管理在大部分时候变得如同小事一桩,但我们仍要在决定自己的类如何管理其它对象的引用时承担一些责任。

苹果的官方说明中称,ARC是“由编译器进行内存管理”的,但是实际上只有编译其是无法完全胜任的,再次基础上还需要Objective-C运行时库的协助。

也就是说,ARC由以下工具、库来实现。

  • clang(LLVM编辑器)
  • objc4 Objective-C 运行时库

__strong 修饰符

?
1
2
3
{
 id __strong obj = [[NSObject alloc] init];
}

~~本人是c/cpp小白,没有编译成功,没看到汇编输出TAT~~

以上代码编译器的模拟代码:

?
1
2
3
4
/** 编译器的模拟代码 */
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);

如上所示,调用了2次objc_msgSend方法,变量的作用域结束时通过objc_release释放对象。虽然ARC有效时不能使用release方法,但由此可知编译器自动插入了release。

使用alloc/new/copy/mutableCopy以外的方法时:

?
1
2
3
{
 id __strong obj = [NSMutableArray array];
}

编译器的模拟代码如下:

?
1
2
3
4
/** 编译器的模拟代码 */
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

其中的objc_retainAutoreleasedReturnValue函数主要用于优化程序运行,它用于自己持有(retain)对象的函数,但它持有的对象应为返回注册在autoreleasepool中对象的方法,或是函数的返回值。在调用alloc/new/copy/mutableCopy以外的方法,由编译器插入该函数。

与 objc_retainAutoreleaseReturnValue函数相对的函数为objc_autoreleaseReturnValue函数。它用于alloc/new/copy/mutableCopy方法以外的NSMutableArray类的array类方法等返回对象的实现上。

?
1
2
3
4
+ (id) array
{
 return [[NSArray alloc] init];
}

转换后

?
1
2
3
4
5
6
+ (id) array
{
 id obj = objc_msgSend(NSArray,@selector(alloc));
 objc_msgSend(obj,@selector(init));
 return objc_autoreleaseReturnValue(obj);
}

返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturValue函数返回注册到autoreleasepool中的对象。但是objc_autoreleaseReturValue函数同objc_autorelease函数不同,一般不仅限于注册对象到autoreleasepool中。

objc_autoreleaseReturValue函数会检查使用该函数的方法或函数调用方的执行命令列表。如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue()函数,那么就不将返回的对象注册到autoreleasepool中,而直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。

通过objc_autoreleaseReturnValue函数和objc_retainAutoreleasedReturnValue函数的协作,可以不讲对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。

__weak 修饰符

  • 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量。
  • 使用附有__weak修饰符的变量,即是使用注册到了autoreleasepool中的对象。
?
1
2
3
{
 id __weak obj1 = obj;
}
?
1
2
3
4
/** 编译器的模拟代码 */
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);

通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destroyWeak函数释放该变量。
如以下源码所示,objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数。

?
1
2
obj1 = 0;
objc_storeWeak(&obj1,obj);

objc_destroyWeak函数将0作为参数调用objc_storeWeak函数。

?
1
objc_storeWeak(&obj1,0);

即前面的源代码和以下代码相同:

?
1
2
3
4
id obj1;
obj1 = 0;
objc_storeWeak(&obj1,obj);
objc_storeWeak(&obj1,0);

objc_weakStore函数把第二参数的复制对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二参数为0,则把变量的地址从weak表中删除。

weak表与引用计数表相同,作为散列表被实现。如果使用weak表,将废弃对象的地址作为键值进行检索,能高速地获取对应的附有__weak修饰符的变量的地址。另外,由于一个对象可以同时赋值给多个附有__weak修饰符的变量中,所以对于一个键值,可注册多个变量的地址。

释放对象时,废弃谁都不持有的对象,通过objc_release函数释放。

  • objc_release
  • 因为计数为0,所以执行dealloc
  • _objc_rootDealloc
  • object_dispose
  • objc_destructInstanse
  • objc_clear_deallocating

对象被废弃时最后调用的objc_clear_deallocating函数动作如下:

  • 从weak表中获取废弃对象的地址为键值的记录。
  • 将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil
  • 从weak表中删除该记录。
  • 从引用计数表中删除废弃对象的地址作为键值的记录。

根据以上步骤,前面说的如果附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量这一功能即被实现。由此可知,如果大量使用附有__weak修饰符的变量,则会消耗相应的CPU资源,对此只在需要避免循环引用的时候使用__weak修饰符。

使用__weak修饰符时,以下代码会引起编译器警告

?
1
2
3
4
{
 id __weak obj = [[NSObject alloc] init];
 NSLog(@"obj = %@",obj);
}

编译结果如下:

?
1
Assigning retained object to weak variable; object will be released after assignment

编译器模拟代码如下:

?
1
2
3
4
5
6
id obj;
id temp = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(temp,@selector(init));
objc_initWeak(&obj,temp);
objc_release(temp);
objc_destroyWeak(&obj);

运行结果如下:

?
1
2017-12-07 19:37:24.075939+0800 ImageOrientation[10963:3581164] obj = (null)

使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象。

?
1
2
3
4
{
 id __weak obj1 = obj;
 NSLog(@"%@",obj1);
}

该代码可以转换为如下形式:

?
1
2
3
4
5
6
7
/** 编译器模拟代码*/
id obj1;
objc_initWeak(&obj1,obj);
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(@"%@",obj1);
objc_destroyWeak(&obj1);

与赋值时相比,在使用附有__weak修饰符变量的情形下,增加了对objc_loadWeakRetained函数和objc_autorelease函数的调用。这些函数的动作如下:

  • objc_loadWeakRetained函数取出附有__weak修饰符变量所引用的对象并retain
  • objc_autorelease函数将对象注册到autoreleasepool中。

__autoreleasing 修饰符

将对象赋值给附有__autoreleasing修饰符的变量等同于MRC时调用对象的autorelease方法。

?
1
2
3
@autoreleasepool{
 id __autoreleasing obj = [[NSObject alloc] init];
}

模拟代码如下:

?
1
2
3
4
5
6
/** 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autoreleas(obj);
objc_autoreleasePoolPop(pool);

alloc/new/copy/mutableCopy之外的方法实现:

?
1
2
3
@autoreleasepool{
 id __autoreleasing obj = [NSMutableArray array];
}
?
1
2
3
4
5
6
/** 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutorelesedReturnedValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

引用计数

获取引用计数的函数为CFGetRetainCount

例如:

?
1
2
3
4
{
 id __strong obj = [[NSObject alloc] init];
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为1

?
1
2
3
4
5
{
 id __strong obj = [[NSObject alloc] init];
 id __weak obj1 = obj;
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为2

?
1
2
3
4
5
{
 id __strong obj = [[NSObject alloc] init];
 id __autoreleaing obj1 = obj;
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为2

?
1
2
3
4
5
6
7
8
9
{
 id __strong obj = [[NSObject alloc] init];
 @autoreleasepool{
  id __autoreleaing obj1 = obj;
  NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}
 }
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为2和1

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://juejin.im/post/5a29fbf15188257dd2398df9

延伸 · 阅读

精彩推荐
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22