iOS与OS X内存管理

作者: 油菜小白 | 来源:发表于2016-04-20 15:31 被阅读93次
    • 本文的写作目的是为学习记录,同时分享给大家,希望大神能够对文中错误的理解进行指正。
    • 如果文章内容涉及到其他已经发表了,但文章中又未提及转载事项,请及时与本人联系。
    • 本文为个人理解,如果部分知识点与真实情况有出入,请忽略本文。

    1 相关知识点

    1.1 内存管理

    软件运行的时候,对计算机内存资源的分配和使用进行管理的策略。

    1.2 引用计数

    一种内存管理策略。(以下解释来自百度百科)

    • 当一个新的引用指向对象时,引用计数器就递增;
    • 当去掉一个引用时,引用计数就递减;
    • 当引用计数到零时,该对象就将释放占有的资源。

    1.3 iOS与OS X的自动释放池

    把所有生成的对象放到一个“池子”里,当超过“池子”的作用域,就会自动帮你释放对象,不用你手动release。

    1.4 iOS与OS X的自动引用计数

    • “俗称”ARC(Automatic Reference Counting)。
    • 如名,就是iOS和OS X系统内自行处理内存管理,不用你专门敲代码去给对象的引用计数+1、-1。
    • 从iOS 5和OS X Lion开始引入了自动引用计数,所以除非你需要适配在那之前的版本,不然你完全可以打开ARC(想知道怎么开?请自行百度)。

    1.5 内存泄漏

    应该让出的内存资源,却一直占着不放。

    2 引用计数

    • 生成对象(+1);
    • 持有对象(+1);
    • 释放对象(-1);
    • 废弃对象(=0)。

    3 iOS与OS X的引用计数

    3.1 生成对象(alloc/new/copy/mutableCopy)

    • alloc方法
      生成对象,并用某个变量去持有对象;
    • new方法
      也是生成对象,并用某个变量去持有对象([NSObject new]与[[NSObject alloc] init]完全一致);
    • copy方法
      生成对象,并用某个变量去持有对象的副本;
    • mutableCopy方法
      同copy,区别在于生成的对象,copy生成不可变更的对象,mutableCopy则生成可变。

    使用以上方法名称开头,并且以驼峰拼写法命名的方法,也意味着自己生成并持有对象。例如:

    allocFirstObject
    newSecondObject
    copyThirdThat
    mutableCopyFourthObject等。

    3.2 持有对象(retain)

    除去上述的四种方法之外取得的对象,可以用retain方法持有该对象。例如:

    id testArr = [NSMutableArray array];
    [testArr retain];
    

    3.3 释放对象(release)

    自己持有的对象,一旦不再需要,持有者有义务释放该对象。释放使用release方法。例如:

    id testObj = [[NSObject alloc] init];
    [testObj release];
    

    3.4 废弃对象(dealloc)

    当引用计数为0时,则调用dealloc方法废弃对象。
    如果需要显示调用dealloc方法,即在废弃对象后进行必要的操作,则需要调用[super dealloc]。例如:

    - ( void ) dealloc
    {
        [super dealloc];
    }
    

    3.5 释放之后再次释放

    在持有对象之后,需要释放对象,但不能再次释放已经释放的对象。下例会Crash。例如:

    id testObj = [[NSObject alloc] init];
    [testObj release];
    [testObj release];
    

    3.6 释放自己不持有的对象

    非自己生成的对象,需要用retain持有,如果没有用retain持有,就释放也会Crash。例如:

    id testObj = [NSMutableArray array];
    [testObj release];
    

    4 iOS与OS X的自动释放池autorelease

    4.1 具体过程:

    • 生成自动释放池;
    • 调用autorelease方法,将对象放进自动释放池中;
    • 废弃自动释放池,所有在池中的对象自动调用release方法;

    例如:

    @autoreleasepool {
      Id testObj = [[NSObject alloc] init];
      [testObj autorelease];
    }
    //也可以通过生成NSAutoreleasePool对象的方式,来生成自动释放池
    //通过生成NSAutoreleasePool对象的方式,需要手动废弃自动释放池(使用drain方法)。
    

    4.2 注意事项

    • 当大量读取大容量文件时,要在适当的地方生成、持有或废弃自动释放池。如果不及时释放自动释放池所占用的内存,会导致内存占用过大而Crash。
    • Cocoa框架中,部分方法返回已调用autorelease方法的对象。具体信息请查阅官方文档。

    5 iOS与OS X的自动引用计数(ARC)

    所有ARC的操作需要在Xcode4.2之后进行,并且打开ARC功能(Xcode4.2之后默认打开)。

    5.1 变量修饰符

    5.1.1 __strong

    • “强引用”。
    • 生成并持有对象,非生成也可以通过赋值持有对象。
    • 超出作用域时,自动释放。
    • id类型和对象类型默认添加该修饰符。

    例如:

    {
        id __strong testObj = [[NSObject alloc] init];
        //;在ARC中,以上写法与id testObj = [[NSObject alloc] init]完全相同
    }
    //超出范围,自动释放
    

    5.1.2 __weak

    • “弱引用”。
    • 弱引用不能持有对象。
    • 不能直接生成,需要间接赋值。
    • 弱变量持有对象时,若对象被废弃,则此变量自动失效或为nil。
    • 防止循环引用,而导致内存泄漏。

    例如:

    @interface Test : NSObject
    {
        id __weak testObj;
    }
    - ( void ) setObject: (id __strong)obj;
    @end
    

    @implementation Test
    - ( id )init
    {
        self = [super init];
    }
    - ( void ) setObject: (id __strong)obj
    {
        testObj = obj;
    }
    

    以下为引用:

    {
        id test0 = [[Test alloc] init];
        id test1 = [[Test alloc] init];
        [test0 setObject:test1]; 
        [test1 setObject:test0];
        //由于testObj为__weak类型,不持有对象,此处不会发生循环引用
    }
    //如果testObj为__strong类型,则持有对象
    //导致testObj一直持有对象,导致对象无法释放==内存泄漏
    

    5.1.3 __unsafe_unretained

    • 该修饰符修饰的变量,不属于自动引用计数的范围。
    • 不能直接生成,需要间接赋值。
    • 如果赋值的对象被废弃或不存在,则程序有可能(有可能,即个别情况)崩溃。

    例如:

    id __unsafe_unretained testObj1 = nil;
    {
        id testObj0 = [[NSObject alloc] init];
        testObj1 = testObj0;
        NSLog(@”%@”,testObj1);
    }
    NSLog(@”%@”,testObj1);
    //其中testObj0的对象引用已被废弃,则访问testObj1有可能出现程序崩溃
    

    5.1.4 __autoreleasing

    • 如名,ARC的自动释放池(以下简称“池”)。
    • 在iOS的main函数中就已经生成了池,并且包含程序的所有内容。
    • 不能用生成NSAutoreleasePool的方式,来生成自动释放池,编译器会报错。
    • 正常情况下,如果你用@autoreleasepool生成池,必须显式将对象注册到池中。

    但也存在个别情况:
    1、非自己生成并持有的对象,会自动注册到池中。
    例如:

    @autoreleasepool {
        id testObj = [NSMutableArray array];
        //生成的对象已自动注册到池中
    }
    

    2、访问附有__weak修饰符的变量时,会将即将引用的对象注册到池中。
    例如:

    @autoreleasepool {
        id testObj0 = [[NSArray alloc] init]; 
        //此处若需将testObj0注册到池中,须显式使用__autoreleasepool修饰符声明
    
        id __weak testObj1 = testObj0;
        //在赋值的同时,将testObj0引用的对象注册到池中
    }
    

    3、id的指针、对象的指针变量会自动附上__autoreleasepool修饰符。
    例如:

    @autoreleasepool {
        id testObj0 = [[NSObject alloc] init]; // testObj0自动附上__strong修饰符
        id *testObj1 = &testObj0; //*testObj1自动附上__autoreleasepool修饰符
        //此处会报错,因为指针赋值时,修饰符必须一致
    }
    

    Tips:无论ARC是否开启,_objc_autoreleasePoolPrint();函数都可以打印出已经注册到当前池中所有对象。

    5.2 iOS与OS X中ARC开启的状态下,注意事项

    5.2.1 禁用关键字

    • 不能使用alloc/retain/release/retainCount/autorelease。
    • 估计大家都知道这点,在ARC开启状态下,以上这些方法都是不能使用的。

    5.2.2 须遵守的命名规则

    • 在ARC未开启时候的命名规则,参考3.1 最后一条所述。
    • 而在ARC开启的时候,除去以上规则同样有效外,还要考虑init关键字:
      1、以init开头(并且用驼峰拼写发)命名的方法,必须是一个实例方法,并且需要返回对象。
      2、以init开头(并且用驼峰拼写发)命名的方法,返回的对象并不自动注册到自动释放池中。

    5.2.3 不要显示调用dealloc

    • 对象被废弃时,不管ARC是否开启,都会调用对象的dealloc方法。
    • 参考以上所述,可以在类中实现dealloc方法(实现dealloc方法在ARC是否开启之间的区别,请参考3.4)。

    例如:

    - ( void ) dealloc
    {
        //无需[super dealloc]
    }
    
    • 如上例中所述,在ARC开启的状态下,不要显式调用dealloc。

    5.2.4 不要使用NSAutoreleasePool

    在ARC开启的状态下,使用NSAutoreleasePool类生成自动释放池,编译器会报错。

    5.2.5 不能使用区域(NSZone)

    无论ARC是否开启,最好都不要用(其实我还没去了解这个“区域”是个什么东西o(╯□╰)o)。

    5.2.6 对象型变量不能成为struct的成员

    例如:

    struct TestStruct {
        NSArray * array;
        //报错,不能用对象型变量
    }
    

    5.2.7 __bridge转换

    • 不能显示转换id和void *(C/C++语言中的空指针类型),需要用__bridge关键字进行转换。

    例如:

    id testObj = [[NSObject alloc] init];
    void *testVoid = (__bridge void *)testObj;
    id testObj1 = (__bridge id)testVoid;
    
    • __bridge_retained转换,可使要转换赋值的变量,也持有所赋值的对象。
    • __bridge_transfer转换,被转换的变量所持有的对象,在转换完成后释放。

    Tips:Foundation框架与Core Foundation框架的对象互相转换,需要用__bridge关键字进行转换。

    5.3 iOS与OS X的属性(property)

    属性声明的属性与所有权修饰符的对应关系:

    属性声明的属性 所有权修饰符
    assign __unsafe_unretained修饰符
    copy __strong修饰符(但是赋值的是被复制的对象)
    retain __strong修饰符
    strong __strong修饰符
    unsafe_unretained __unsafe_unretained修饰符
    weak __weak修饰符

    5.4 容器

    5.4.1静态容器(NSArray、NSDictionary、NSSet)

    • 除去__unsafe_unretained修饰符以外,__strong、__weak、__autoreleasing修饰符都能保证其指定变量初始化为nil。
    • 数组超出其作用域,则变量失效、对象释放(蛮押韵的)。

    5.4.2 动态容器(NSMutableArray、NSMutableDictionary、NSMutableSet)

    • Objective-C的动态容器使用的基本规则,基本同静态一致。
    • iOS中C语言的动态容器:

    1、声明动态容器,需要用指针。
    例如:

    id __strong *array = nil;
    

    2、附有__strong修饰符的id型变量初始化为nil,附有__strong修饰符的id型指针变量初始化不一定为nil。
    3、calloc函数,给附有__strong修饰符的id型指针变量分配内存。
    例如:

    array = (id __strong *)calloc(entries, sizeof(id));
    

    4、操作__strong修饰符的变量,需要自己释放所有元素。释放所有元素后,利用free(array)的方法废弃内存块。
    例如:

    for (NSUInteger i = 0; i < entries; ++i) {
        array[i] = nil;
    }
    free(array);
    

    5、以下函数会造成内存泄漏的危险,请慎用:

    • 用malloc函数分配内存(之后,可用memset函数将内存置零);
    • 用memcpy函数拷贝数组元素;
    • 用realloc函数重新分配内存块。

    6、__weak修饰符与__strong修饰符使用方式相同;但__autoreleasing修饰符存在不可控因素,慎用;__unsafe__unretained修饰符在编译器的内存管理对象之外,只能作为指针类型使用。

    参考:

    • 《Objective-C高级编程 iOS与OS X多线程和内存管理》【日】Kazuki Sakamoto Tomohiko Furumoto 著 黎华 译

    相关文章

      网友评论

        本文标题:iOS与OS X内存管理

        本文链接:https://www.haomeiwen.com/subject/zawzlttx.html