Effective Objective-C 2.0 总结与笔记(第五章)—— 内存管理

第五章:内存管理

​ 内存管理是Objective-C里重要的概念,要写出内存使用效率高又没有bug的代码,就需要掌握其内存管理的种种细节。Objective-C的内存管理因为有自动引用计数,所以并不复杂,ARC几乎把所有的内存管理的事情都交给编译器决定,作为开发者只需要专注于业务逻辑。

第29条:理解引用计数
  • Objective-C对象使用引用计数来管理内存,每个对象都有一个可以递增或者递减的计数器,如果想使对象继续存活就要递增其引用计数,用完了之后就需要递减。
  • 在ARC中,所有与引用计数有关的方法的无法编译。虽然不能直接调用,但是理解这些方法能够帮助我们去理解引用计数的概念,ARC也是一种引用计数机制。
  • 引用计数的原理:NSObject协议生命了3个方法用于操作计数器
retain: 递增保留计数
release: 递减保留计数
autorealease: 待稍后清理"自动释放池"时,再递减保留计数
  • 保留计数至少为1,当对象被初始化之后,对象的引用计数为1,然后如果被强引用,那么表示其他对象想令其继续存活,就可以保留(引用计数+1),等到用完之后,再释放(引用计数-1),当引用计数降至0的时候,那么对象所占的内存也许被回收,一般为了保证不会出现可能指向无效对象的指针(悬挂指针),可以在realease之后清空指针。
NSNumber *number = [[NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
number = nil;
  • 属性存取方法的内存管理:如果属性为strong的修饰符修饰,那么设置的属性值会保留:
- (void)setFoo:(id)foo {
    [foo retain];//保留新值
    [_foo release];//释放旧值
    _foo = foo;//让实例变量指向新值
}
  • 自动释放池:调用autorelease会在稍后递减引用计数,通常是下一次事件循环(event loop),也有可能更早。
/*
使用以下的方法会让引用计数+1,而没有释放操作
*/
- (NSString *)stringValue {
    NSString *str = [NSString stringWithFormat:@"Test String Value"];
    return str;
}

/*
使用autorelease能够很好的避免这种情况,给调用者留下足够的时间。
*/
- (NSString *)stringValue {
    NSString *str = [NSString stringWithFormat:@"Test String Value"];
    return [str autorelease];
}
  • 保留环,如果几个对象相互引用,这样会导致内存泄漏,因为循环中的对象其引用计数不会降为0。通常的解决方案是使用弱引用代替强引用。
第30条:以ARC简化引用计数
  • ARC就是自动引用计数,引用计数实际上还是要执行的,只不过保留和释放操作都是由ARC自动为你添加,所以不能直接在ARC下调用内存管理相关的方法:retainreleaseautoreleasedealloc。而且ARC调用的并不是Objective-C的这些方法,而是和这些方法等效的C语言版本,这样可以节省更多的CPU周期。
  • 使用ARC必须遵循的方法命名规则:如果使用以下词语开头的方法,其返回对象归调用者所有:allocnewcopymutableCopy。归调用者所有的意思是:调用上述四种方法的那段代码要负责释放所返回的对象。
  • 若方法名不以上述四个词语开头,则表示其返回的对象并不归调用者所有,这种情况下所返回的对象会自动释放。
  • ARC具有向后兼容的特性,他可以兼容那些不使用ARC的代码。ARC有特殊的函数能优化函数:
+ (EOCPerson *)personWithName:(NSString *)name {
    EOCPerson *person = [[EOCPerson alloc] init];
    person.name = name;
    objc_autoreleaseReturnValue(person);
}

//code using EOCPerson Class
EOCPerson *tmp = [EOCPerson personWithName:@"GDGD"];
_myperson = objc_retainAutoreleasedReturnValue(tmp);

id objc_autoreleaseReturnValue(id object) {
    if (/* caller will retain object */) {
        set_flag(object);//设置标志位,表示对象要执行retain操作
        return object; // No autorelease;
    }
    else {
        return [object autorelease];
    }
}

id objc_retainautoreleasedReturnValue(id object) {
    if (get_flag(object)) {
        clear_flag(object);
        return object;//No retain;
    }
    else {
        return [object retain];
    }
}

​ 将内存管理交给编译器和运行期组件来做,代码可以得到多种优化,上面只是其中一种。

  • 变量的内存管理语义:ARC会用一种安全的方式来设置,先保留新值,在释放旧值,最后设置实例变量。而且使用了ARC之后无需考虑这些情况。
__strong:默认语义,保留此值;
__unsafe_unretained:不保留此值,这样可能不安全,等到再次使用变量的时候,对象可能已经被回收了;
__weak:不保留此值,但是变量可以安全使用,如果对象被回收,那变量也会自动清空;
__autoreleasing:把对象按引用传递给方法的时候,使用这个特殊的修饰符,在返回时自动释放;

​ 使用__weak__unsafe_unretained可以打破保留环。

  • 在手动引用计数的时候,可以这样编写dealloc方法:
- (void)dealloc {
    [_foo release];
    [_bar release];
    [super dealloc];
}

​ 在ARC里不能直接调用dealloc,但是非Objective-C对象还是需要清理,可以这样写dealloc方法:

- (void)dealloc {
    CFRelease(_coreFoundationObject);
    free(_heapAllocatedMemoryBlob);
}
  • 如果不使用ARC,可以覆写内存管理方法。例如将单例模式下的release方法替换为空操作。
第31条:在dealloc方法中只释放引用并解除监听
  • 对象在声明周期结束后,会被系统回收,就需要执行dealloc方法了,这个方法在对象的引用计数降为0的时候被执行。
  • dealloc主要做以下几个事情:
    • 释放所有的Objective-C对象,ARC会通过自动生成的.cxx_destruct方法在dealloc中自动添加这些释放方法。
    • 手动释放非Objective-C对象。
    • 将配置过的观测行为都清理掉,如果把通知发给了释放的对象,那么会令应用程序崩溃。
  • 在iOS系统里,应用程序终止时则会调用UIApplicationDelegate中的下述方法:
- (void)applicationWillTerminate:(UIApplication *)application
  • 尽量不要在dealloc里调用其他方法。执行异步任务的方法不应该在dealloc里调用,只能在正常状态下执行的方法也不应在dealloc里调用,因为此时对象已经处于正在回收的状态了。
第32条:编写“异常安全代码”时留意内存管理问题
  • 在Objective-C的错误模型里,异常应该在发生严重错误后抛出。
  • 如果使用手动引用计数,就需要手动处理异常的时候的内存管理。
@try {
    EOCSomeClass *object = [[EOCSomeClass alloc] init];
    [object doSomething];
    //如果把释放放在这里的话,那么可能在doSomething发生异常,导致内存泄漏
}
@catch(...) {
    NSLog(@"...");
}
@finally {
    [object release];
}
  • 在ARC里,由于不能手动管理内存,所以如果异常出现对象并不会被释放,这样可能出现异常泄漏,虽然也可以开启手动内存管理,但是默认不开启的原因是因为:在Objective-C中,只有引用程序因异常状况而终止时才应该抛出异常,如果应用程序终止了,是否发生内存泄漏就已经无关紧要了。所以如果去处理反而会影响运行效率。
第33条:以弱引用避免保留环
  • 如果对象之间以某种方式互相引用,从而形成环,因为Objective-C采用引用计数的内存管理机制,这样就会导致内存泄漏。
  • 一个最简单的保留环:
#import <Foundation/Foundation.h>
@class EOCClassA;
@class EOCClassB;

@interface EOCClassA : NSObject
@property (nonatomic, strong) EOCClassB *other;
@end

@interface EOCClassB : NSObject
@property (nonatomic, strong) EOCClassA *other;
@end

​ 避免保留环最佳方式就是弱引用,这种引用表示非拥有关系,不会让引用计数+1。

@property (nonatomic, unsafe_unretained) EOCClassA *other;
//或者
@property (nonatomic, weak) EOCClassA *other;

属性特质的unsafe_unretained表明属性可能不安全,如果系统已经把属性所指的对象回收了,那么在其上调用方法可能会导致应用程序崩溃,因为本对象不保留属性对象,因此有可能为系统所回收。

使用weak的话,就可以避免这种情况,它的作用和unsafe_unretained相同,但是在属性被系统回收后,属性值会自动设置为nil。

也就是说:当只想EOCClassA实例的引用移除后,unsafe_unretained属性仍然指向被回收的那个实例,但是weak指向nil。

第34条:以“自动释放池块”降低内存峰值
  • 释放对象有两种方式:

    • 调用release方法,使其引用计数立即递减。
    • 调用autorelease方法,将其加入“自动释放池”中。清空自动释放池的时候,系统会向其中的对象发送release消息。
    //自动释放池的方法:
    @autoreleasepool {
        //...
    }
    
  • 一般来说不需要自己创建自动释放池,通常只需要在main函数里使用自动释放池来包裹应用程序的主入口点。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

​ 这个池可以理解为最外围捕捉全部自动释放对象所用的池。

  • 自动释放池可以嵌套,它于左花括号处创建,并对应右花括号处自动清空,位于自动释放池内的对象,在范围的末尾处收到release消息。自动释放池嵌套使用的好处是可以控制应用程序的内存峰值,使其不至过高。

    如果要从数据库里读取数据,那么可能在读取的时候会有新对象创建并加入自动释放池,这样所有对象要等到for循环执行完才会释放,导致内存过高。

//通过下面的方式,加入EOCPerson在创建的时候同时还有很多临时对象的创建,那么内存中会有很多不必要的临时对象没有被释放,导致内存过高
NSArray *databaseRecords = /* record from database */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
    EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
    [people addObject:person];
}

//要解决这个问题,增加一个自动释放池就可以
NSArray *databaseRecords = /* record from database */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
    @autoreleasepool {
        EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
    	[people addObject:person];
    }
}
//这样循环中自动释放的对象就会放在这个池子,而不是线程的主池里
  • 自动释放池的也有开销,尽量不要建立额外的自动释放池。@autoreleasepool的方式能够更方便的创建出自动释放池。
第35条:用”僵尸对象“调试内存管理问题
  • 主要是使用instrument来进行调试,这里不做详述,贴一个我觉得写的不错的博客吧。

    iOS 僵尸对象调试

第36条:不要使用retainCount
  • NSObject协议里定义了一个方法,可以查询当前的引用计数,这个方法在ARC中是不可用的。
- (NSUInteger)retainCount
  • 这个方法是无用的,因为所返回的引用计数只是某个时间点上的值,并未考虑到系统会稍后把自动释放池清空,因而不会将后续的释放操作从返回值里减去。
  • 同时这个方法可能永远不返回0,因为可能在保留计数还是1的时候,就被系统回收了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值