iOS内存管理

iOS内存管理,第1张

概述1.引用计数式内存管理的思考方式 自己生成的对象,自己持有 非自己生成的对象,自己也能持有 不在需要自己持有的对象时释放 非自己持有的对象无法释放 2.alloc方法 + alloc+ allocWithZone:class_creatInstancecalloc 调用alloc方法首先调用allocWithZone:类方法,然后调用class_creatInstance函数,最后调用cal 1.引用计数式内存管理的思考方式 自己生成的对象,自己持有 非自己生成的对象,自己也能持有 不在需要自己持有的对象时释放 非自己持有的对象无法释放 2.alloc方法
+ alloc+ allocWithZone:class_creatInstancecalloc

调用alloc方法首先调用allocWithZone:类方法,然后调用class_creatInstance函数,最后调用calloc来分配内存块。

3.retainCount/retain/release 方法
- retainCount__CFDoExternRefOperationCFBasicHashGetCountOfKey
-  retain__CFDoExternRefOperationCFBasicHashAddValue
- retainCount__CFDoExternRefOperationCFBasicHashRemoveValue //CFBasicHashRemoveValue 为0时,-release调用dealloc

各个方法都通过同一个__CFDoExternRefOperation函数,调用一系列名称相似的函数。并且从函数名看出苹果采用散列表(引用计数表)来管理引用计数,表键值为内存块地址的散列值。然而GNUStep将引用计数保存在对象占用内存块头部的变量中(objc_layout这个结构体中)。

内存块头部管理引用计数的好处:

少量代码皆可完成 能够统一管理引用计数内存块与对象内存块。

引用技术表管理引用计数的好处:
1. 对象内存快的分配无需考虑内存块头部

引用计数表各记录中存有内存块地址,可从各个记录追溯到各个内存块。
第二条特征在调试时很重要,即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被损坏,就能够确认各个内存块的地址
4.autorelease方法

NSautoreleasePool是通过以autoreleasePoolPage为结点的双向链表来实现的。autoreleasePoolPage是一个C++实现的类,类结构如图:

magic 用来校验 autoreleasePoolPage 的结构是否完整。 next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin()。 thread 指向当前线程。 parent 指向父结点,第一个结点的 parent 值为 nil。 child 指向子结点,最后一个结点的 child 值为 nil。 depth 代表深度,从 0 开始,往后递增 1。 hiwat 代表 high water mark。 autoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址内存结构如图:

在Cocoa框架中,NSRunloop每次循环过程中NSautoreleasePool对象被生成或废弃。在大量产生autorelease对象时,只要不废弃NSautoreleasePool那么生成的对象就不能被释放,在此情况下有时会产生内存不足的现象,因此有必要适当的生成,持有和废弃NSautoreleasePool。通常在使用Objective-C,无论调用哪一个对象的autorelease/retain方法,实现上都是调用NSObject类的autorelease/retain实例方法,但是对于NSautoreleasePool类,autorelease/retain实例方法已被重写,因此运行时会出错(exception)。autorelease实际上把对象的释放时机交给NSautoreleasePool管理,使用方法如下:

生成并持有NSautoreleasePool对象。

NSautoreleasePool *pool = [[NSautoreleasePool alloc] init]; // 等同于 objc_autoreleasePoolPush()

调用已分配对象的autorelease实例方法。

ID obj = [[NSObject alloc] init];[obj autorelease]; // 等同于 objc_autorelease()obj

废弃NSautoreleasPool对象(自动调用分配对象的release)。

[pool drain]; // 等同于 objc_autoreleasePoolPop(pool)
5.ARC规则

ARC(automatic Reference Counting)是编译阶段自动做了retain/release,原先需要手动添加处理引用计数的代码可以自动地由编译器完成,但实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行时库协助。同一程序中按文件单位可以选择ARC有效和无效。Core Foundation中的malloc()或者free()等,还是需要自己手动进行内存管理。ARC规则如下

不能使用retain/release/retainCount/autorelease。 不能使用NSAllocateObject/NSDeallocateObject。 必须遵守内存管理的方法命名规则。 不要显式调用dealloc。 使用@autoreleasepool块代替NSautoreleasePool。 不能使用区域。
虽说ARC有效时,不能使用区域(NSZone)。不管ARC是否有效,区域在现在的运行时系统(编译器宏__OBJC2__)中已单纯的被忽略

对象型变量不能作为C语言结构体的成员。

struct Data {    NSMutableArray *array; /* error: ARC forbIDs Objective-C obJs in structs or unions  NSMutableArray *array */}/* 要把对象型白能量加入到结构体成员中时,可强制转换为voID *(见下一条规则)或是附加“__unsafe_unreatained”修饰符。但是附有“__unsafe_unreatained”修饰的变量不属于编译器的内存管理对象,可能造成内存泄露或者崩溃。*/ struct Data {    NSMutableArray __unsafe_unreatained *array; /}

显式转换“ID” 和 “voID”。

在MRC下"ID"和"voID *"可以强制转换,但在ARC下编译器报错,代码如下:

ID obj = [[NSObject alloc] init];voID *p = obj;   // ARC编译报错ID o = p;  // ARC编译报错[o release];

ARC情况下要用 "brIDge转换",但是ARC不推荐此用法,这种转换经常用在Objective-C和Core Foundation对象之间的相互变换。

__brIDge转换:只做类型转换,在不改变对象的引用计数,可能会造成悬垂指针而导致程序崩溃。

__brIDge_retained转换:可使目标变量也持有赋值对象。对象引用计数加1。以下是ARC和MRC等价的代码

// ARCID obj = [[NSObject alloc] init];voID *p = (__brIDge_retained voID *)obj;// 等效 MRC 代码ID obj = [NSObject alloc] init];voID *p = (voID *)obj;[(ID)p retain];

__brIDge_transfer转换:被转换的变量所持有的对象在赋值给目标变量后随之释放。

// ARC ① (voID)(__brIDge_transfer ID)p; //指定返回结果为voID,不利用返回值 对象引用计数减1② ID obj = (__brIDge_transfer ID)p; // 如果返回值赋值给目标变量,转换后对象引用计数不变// 等效 MRC 代码① [p release];② ID obj = (ID)p; [obj retain];[(ID)p release];
6.所有权修饰符

__strong修饰符:对对象实例的强引用,是ID类型或对象类型的默认所有权修饰符。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象
会随之释放。

// 以下两行代码是等效的ID obj = [[NSObject alloc] init];ID __strong obj = [[NSObject alloc] init];
__weak修饰符:提供弱引用,弱引用不能持有对象实例,与__strong修饰符相反。可用来解决循环引用造成的内存泄露问与题。 __unsafe_unretained修饰符:不安全的所有权修饰符,尽管ARC式的内存管理属于编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象,既不持有对象的强引用也不持有对象的弱引用,对象的可能随时被释放,访问释放的对象运行时会崩溃。

__autoreleasing修饰符:对象变量要为自动变量(包含局部变量、函数以及方法参数)。利用“__objc_autoreleasePoolPrint()”调试autoreleasepool上的对象

等价于MRC时调用对象的autorelease方法。以下两段代码等效:

// MRCNSautoreleasePool *pool = [[NSautoreleasePool alloc] init];ID obj = [[NSObject alloc] init];[obj autorelease];[pool drain];// ARC@autoreleasepool {    ID __autoreleasing obj = [[NSObject alloc] init];}

显式的附加__autoreleasing修饰符和显式度附加__strong修饰符一样“罕见”,有些情况下非
显式的声明__autoreleasing修饰符也是可以的,这些情况会自动将对象注册到自动释放池。
1.非自己生成并持有的对象

@autoreleasepool {    ID  obj = [NSMutableArray array];    // 变量obj为对象的强引用(默认__strong),编译器判断方法名后(非alloc/new等)自动注册大autoreleasepool}

2.ID的指针和对象的指针(二级指针)在没有显示的指定时会被附加上__autoreleasing修饰符。

NSError *error = nil; //所有权为__strong类型 NSError **pError = &error; //编译出错, NSError * (对象)默认为_autoreleasing类型,赋值前后所有权不匹配。 NSError * __strong *pError = &error; //编译正确

3.自己生成并持有的对象作为返回值

+ (ID)array {    ID obj = [[NSMutableArray alloc] init];    return obj;  }

4.虽然__weak修饰符是为了避免循环引用而使用的,但访问附有__weak修饰符的变量时实j 际上必定要访问注册到autoreleasepool的对象为了避免该对象被废弃,注册到autoreleasepool之后可以保证在autoreleasepool块结束之前对象存在。

ID __weak obj1 = obj0; NSLog(@"class = %@",[obj1 class]); // 以下代码和以上代码相同 ID __weak obj1 = obj0; ID __autoreleasing tmp = obj1; NSLog(@"class = %@",[tmp class]);
7.引用计数查看

Apple 提供一些方法查看对象的引用计数,但是并不能完全信任这些函数提供的引用计数值。对于已释放的对象一级不正确的对象地址,有时 也返回”1“,在多线程中,因为存在竞态条件的问题,所以取得的的数值不一定可信。

[object retainCount]; //得到object的引用计数,此方法仅仅适用于MRC  _objc_rootRetainCount(object); // ARC和MRC适用,头文件声明:OBJC_EXTERN int _objc_rootRetainCount(ID);
总结

以上是内存溢出为你收集整理的iOS内存管理全部内容,希望文章能够帮你解决iOS内存管理所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/web/1074136.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-26
下一篇2022-05-26

发表评论

登录后才能评论

评论列表(0条)

    保存