iOS循序渐进之篇四:OC对象的生成和释放

2021-01-20  本文已影响0人  song91425

篇三上说iOS ARC(Automatic Reference Counting)管理的是堆上的OC对象。

1 开发人员对OC对象管理的四条规则

2 在内存中管理对象的操作方法

Objective-c 方法名 功能
alloc/new/copy/mutableCopy 方法 生成并持有对象,即使用这些方法生成对象,那么对这个对象的引用至少为1
retain 方法 持有对象,即使这个对象的引用数加1
release 方法 释放对象,即使这个对象的引用数减1,当这个引用数为0的时候,系统自动调用dealloc方法释放对象所占用的空间
dealloc 方法 释放对象,这个释放必须是对象的引用数为零才能释放

2.1 Tips:copy和mutableCopy的区别

方法 功能 协议 实现 可变对象调用 不可变对象调用
copy 生成并持有不可变对象的副本 NSCopying copyWithZone: 深复制 浅复制
mutableCopy 生成并持有可变对象的副本 NSMutableCopying mutableCopyWithZone: 深复制 深复制

由 上表可以知道,使用copy方法复制的并不都是浅复制,为了避免出错,如果要对一个对象进行深复制,则使用mutableCopy方法。

2.2 autorelease/__autoreleasing 关键字

autorelease是在MRC环境下的关键字,而__autoreleasingARC环境下的关键字,它们是等价的,只是看编译环境是不是支持ARC。该关键字:它可以取得对象,但是不持有对象,利用这个特性我们可以在下面的方法中返回一个不会被持有的对象和降低内存峰值。

-(id) object{
  id obj = [NSObject new];
  [obj autorelease]; // obj不持有[NSObject new] 生成的对象
  return obj; // 等价于返回一个引用
}

问题1:为什么autorelease它可以取得对象,但是不持有对象?
原因是: 调用这个方法,它可以把这个对象加入到自动释放池中,在清理自动释放池的时候,系统自动会为释放池中的对象调用[obj release];
问题2:如何降低内存峰值?
在回答这个问题之前,先介绍内存峰值和对比在MRCARC的自动释放池。
内存峰值:它是指在某一段较小的时间占用的内存高于它邻近时间的内存占有量

在MRC环境下的创建自动释放池,分为三步骤:
第一步:生成并持有释放池NSAutoreleasePool对象;
第二步:调用对象的autorelease方法,使对象加入连接池中;
第三步:释放NSAutoreleasePool对象,然后系统会对这个池中的每一个对象调用release方法;

//MRC环境下:
//第一步:生成并持有释放池NSAutoreleasePool对象;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// 在堆内存中生成一个对象
id obj = [[NSObject alloc] init];

// 第二步:调用autorelease方法,使对象加入连接池中;
[obj autorelease];

//第三步:释放NSAutoreleasePool对象;
[pool drain];   //向池中的所有对象调用release方法,此处由系统调用[obj release];

//obi已经释放,再次调用会崩溃
NSLog(@"%@", obj); 

由上例代码可知,对自动释放池的生成和释放的代码是固定不变的,所以在ARC环境下,它把自动释放池的创建和释放交给编译器了,直接使用@autoreleasepool块即可。例如上例可以改成:

@autoreleasepool{// 这里生成NSAutoreleasePool对象
// 在堆内存中生成一个对象
id obj = [[NSObject alloc] init];
} // 在这里自动调用[pool drain];

接下来假设一个减少内存峰值的一个应用场景:通过一个for循环加载大量文件,并用一个字符串来接收,如下所示:

// 此处只是模拟场景,
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
      NSError *error;
      NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
      /*处理字符串,创建并自动释放更多对象。 */
}

上例代码,在for循环内会占用大量的内存,这时就会产生内存峰值,如果我们使用自动释放池就可以降低内存峰值,如下所示。

// 此处只是模拟场景,
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /*处理字符串,创建并自动释放更多对象。 */
    }// 执行到这里,会释放池内的所有局部变量,这里会把fileContents释放掉。
}

3 自动释放池(NSAutoreleasePool)

自动释放池还有一个特性就是可以嵌套使用,如下面所示。

@autoreleasepool {
    // . . .
    @autoreleasepool {
        // . . .
    }
  //  . . .
}

注意:虽然自动释放池,能够帮助我们清理对象,但是不建议大量的使用自动释放池,原因有二:一是虽然自动释放池虽然占用较小,但是大量的话也会占有一定的内存;二是频繁的清理对象,会消耗CPU的性能和耗电。

3.1 RunLoop和NSAutoreleasePool的关系

每一个RunLoop都会创建一个Autorelease Pool对象,系统会为同一个RunLoop 的所有Autorelease Pool对维护一个栈,每个新创建的Autorelease Pool对象会压入栈顶,待RunLoop结束时,弹出栈顶,同时清理Autorelease Pool的所有对象。

3.2 线程和NSAutoreleasePool的关系

每一个线程都会有自己的Autorelease Pool 和一个RunLoop,但是非主线程的RunLoop,默认不回开启,需要手动开启。当线程终止的时候,系统会自动清理该线程的Autorelease Pool 对象。所以在程序开始运行的时候,主线程创建后,RunLoop和Autorelease Pool也会创建,并且RunLoop启动。

总结

在现在的iOS的开发几乎都是处于ARC环境下,ARC的工作原理也是非常简单的,就是监控一个对象的引用数,如果这个对象的引用数为0,系统就会回收这个对象。这样就帮助开发人员从MRC编程释放出来。本文主要介绍了对一个对象从生成到释放和在ARC环境下,@autorelease的用法和它的背后原理。

上一篇 下一篇

猜你喜欢

热点阅读