美文网首页
iOS内功心法--内存管理机制

iOS内功心法--内存管理机制

作者: 东了个尼 | 来源:发表于2017-11-20 15:55 被阅读0次
    Artwork

    There is something inside ,that they can't get to , that they can't touch. That's yours!

    前言:

    移动app开发中,由于移动设备内存的限制,内存管理是一个非常重要的话题。我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上)。如果一个对象创建并使用后没有得到及时释放那么就会占用大量内存。其他高级语言如C#、Java都是通过垃圾回收来(GC)解决这个问题的,但在OjbC中并没有类似的垃圾回收机制,因此它的内存管理就需要由开发人员手动维护。今天将着重介绍ObjC内存管理。

    1. ARC 和 非ARC

    oc的内存管理方式,分为ARC(automatic reference counting自动引用计数)和非ARC模式。Apple 在 Xcode 4.2 中发布了 Automatic Reference Counting (ARC),该功能为开发人员消除了手动执行引用计数的负担。

    目前xcode新建项目,都会推荐默认的ARC方式(ARC的确有很大的优势)。当然,如果必须要使用非ARC,可以在build setting中修改automatic reference counting选项为NO。如果在ARC项目中引入非ARC的代码或者静态库,需要在build phases设置相应资源为-fno-objc-ar;相反,非arc项目设置arc使用-fobjc-arc。

    ARC与非ARC,从字面上来看,在于是否auto,即是由编译器来自动实现reference counting,还是由开发者手动完成引用计数的加减。作为现在经常使用arc模式来开发的我们来说,ARC大大减少了我们对内存管理的工作,甚至,很多入门开发者完全像开发java应用一样,没有管object的释放。然而对oc来说,从mac os x10.8开始,garbage collector也已废弃,iOS上压根就没有出现过这个概念。iOS上oc的内存管理,本质上是引用计数的管理,理解引用计数,是iOS内存管理的核心。

    2.引用计数器:

    在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)机制,程序编译时Xcode可以自动给你的代码添加内存释放代码,如果编写手动释放代码Xcode会报错,因此在今天的内容中如果你使用的是Xcode4.2之后的版本(相信现在大部分朋友用的版本都比这个要高),必须手动关闭ARC,这样才有助于你理解ObjC的内存回收机制。

    ObjC中的内存管理机制跟C语言中指针的内容是同样重要的,要开发一个程序并不难,但是优秀的程序则更测重于内存管理,它们往往占用内存更少,运行更加流畅。虽然在新版Xcode引入了ARC,但是很多时候它并不能完全解决你的问题。在Xcode中关闭ARC:项目属性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting设置为No即可。

    3.内存管理原理

    在ObjC中没有垃圾回收机制,那么ObjC中内存又是如何管理的呢?其实在ObjC中内存的管理是依赖对象引用计数器来进行的:在ObjC中每个对象内部都有一个与之对应的整数(retainCount),叫“引用计数器”,当一个对象在创建之后它的引用计数器为1,当调用这个对象的alloc、retain、new、copy方法之后引用计数器自动在原来的基础上加1(ObjC中调用一个对象的方法就是给这个对象发送一个消息),当调用这个对象的release方法之后它的引用计数器减1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象。

    下面通过一个简单的例子看一下引用计数器的知识:
    Person.h

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    #pragma mark - 属性
    @property (nonatomic,copy) NSString *name;
    @property (nonatomic,assign) int age;
    

    Person.m

    #import "Person.h"
    
    @implementation Person
    
    #pragma mark - 覆盖方法
    #pragma mark 重写dealloc方法,在这个方法中通常进行对象释放操作
    -(void)dealloc{
        NSLog(@"Invoke Person's dealloc method.");
        [super dealloc];//注意最后一定要调用父类的dealloc方法(两个目的:一是父类可能有其他引用对象需要释放;二是:当前对象真正的释放操作是在super的dealloc中完成的)
    }
    @end
    

    main.m

    #import <Foundation/Foundation.h>
    #import "Person.h"
    
    void Test1(){
        Person *p=[[Person alloc]init]; //调用alloc,引用计数器+1
        p.name=@"Kenshin";
        p.age=28;
        
        NSLog(@"retainCount=%lu",[p retainCount]);
        //结果:retainCount=1
        
        [p release];
        //结果:Invoke Person's dealloc method.
     
        //上面调用过release方法,p指向的对象就会被销毁,但是此时变量p中还存放着Person对象的地址,
        //如果不设置p=nil,则p就是一个野指针,它指向的内存已经不属于这个程序,因此是很危险的
        p=nil;
        //如果不设置p=nil,此时如果再调用对象release会报错,但是如果此时p已经是空指针了,
        //则在ObjC中给空指针发送消息是不会报错的
        [p release];
    }
    
    void Test2(){
        Person *p=[[Person alloc]init];
        p.name=@"Kenshin";
        p.age=28;
        
        NSLog(@"retainCount=%lu",[p retainCount]);
        //结果:retainCount=1
        
        [p retain];//引用计数器+1
        NSLog(@"retainCount=%lu",[p retainCount]);
        //结果:retainCount=2
        
        [p release];//调用1次release引用计数器-1
        NSLog(@"retainCount=%lu",[p retainCount]);
        //结果:retainCount=1
        [p release];
        //结果:Invoke Person's dealloc method.
        p=nil;
    }
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Test1();
        }
        return 0;
    }
    

    在上面的代码中我们可以通过dealloc方法来查看是否一个对象已经被回收,如果没有被回收则有可能造成内存泄露。如果一个对象被释放之后,那么最后引用它的变量我们手动设置为nil,否则可能造成野指针错误,而且需要注意在ObjC中给空对象发送消息是不会引起错误的。

    野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。因为你访问了一块已经不属于你的内存。
    

    4. 内存释放的原则

    手动管理内存有时候并不容易,因为对象的引用有时候是错综复杂的,对象之间可能互相交叉引用,此时需要遵循一个法则:谁创建,谁释放。
    
    假设现在有一个人员Person类,每个Person可能会购买一辆汽车Car,通常情况下购买汽车这个活动我们可能会单独抽取到一个方法中,同时买车的过程中我们可能会多看几辆来最终确定理想的车,现在我们的代码如下:
    

    Car.h

    #import <Foundation/Foundation.h>
    
    @interface Car : NSObject
    
    #pragma mark - 属性
    #pragma mark 车牌号
    @property (nonatomic,copy) NSString *no;
    
    #pragma mark - 公共方法
    #pragma mark 运行方法
    -(void)run;
    
    @end
    

    Car.m

    
    #import "Car.h"
    
    @implementation Car
    
    #pragma mark - 公共方法
    #pragma mark 运行方法
    -(void)run{
        NSLog(@"Car(%@) run.",self.no);
    }
    
    #pragma mark - 覆盖方法
    #pragma mark 重写dealloc方法
    -(void)dealloc{
        
        NSLog(@"Invoke Car(%@) dealloc method.",self.no);
        [super dealloc];
    }
    @end
    

    Person.h

    #import <Foundation/Foundation.h>
    @class Car;
    
    @interface Person : NSObject{
        Car *_car;
    }
    #pragma mark - 属性
    #pragma mark 姓名
    @property (nonatomic,copy) NSString *name;
    
    #pragma mark - 公共方法
    #pragma mark Car属性的set方法
    -(void)setCar:(Car *)car;
    #pragma mark  Car属性的get方法
    -(Car *)car;
    @end
    

    Person.m

    #import "Person.h"
    #import "Car.h"
    
    @implementation Person
    
    #pragma mark - 公共方法
    #pragma mark Car属性的set方法
    -(void)setCar:(Car *)car{
        if (_car!=car) { //首先判断要赋值的变量和当前成员变量是不是同一个变量
            [_car release]; //释放之前的对象
            _car=[car retain];//赋值时重新retain
        }
    }
    #pragma mark  Car属性的get方法
    -(Car *)car{
        return _car;
    }
    
    #pragma mark - 覆盖方法
    #pragma mark 重写dealloc方法
    -(void)dealloc{
        NSLog(@"Invoke Person(%@) dealloc method.",self.name);
        [_car release];//在此释放对象,即使没有赋值过由于空指针也不会出错
        [super dealloc];
    }
    @end
    

    main.m

    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import "Car.h"
    
    void getCar(Person *p){
        Car *car1=[[Car alloc]init];
        car1.no=@"888888";
        
        p.car=car1;
        
        NSLog(@"retainCount(p)=%lu",[p retainCount]);
        
        Car *car2=[[Car alloc]init];
        car2.no=@"666666";
        
        [car1 release];
        car1=nil;
        
        [car2 release];
        car2=nil;
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p=[[Person alloc]init];
            p.name=@"Kenshin";
            
            getCar(p);
            
            [p.car run];
            
            [p release];
            
            p=nil;
            
        }
        return 0;
    }
    
    261710001826308.png

    从运行结果来看创建的三个对象p、car1、car2都被回收了,而且[p.car run]也能顺利运行,已经达到了我们的需求。但是这里需要重点解释一下setCar方法的实现,setCar方法中为什么没有写成如下形式:

    -(void)setCar:(Car *)car{
        _car=car;
    }
    

    前面在我们说到属性的定义时不是都采用的这种方式吗?

    根据前面说到的内存释放原则,getCar方法完全符合,在这个方法中定义的两个对象car1、car2也都是在这个方法中释放的,包括main函数中的p对象也是在main函数中定义和释放的。但是如果发现调用完getCar方法之后紧接着调用了汽车的run方法,当然这在程序设计和开发过程中应该是再普通不过的设计了。如果setCar写成“_car=car”的形式,当调用完getCar方法后,人员的car属性被释放了,此时调用run方法是会报错的(大家自己可以试试)。但是如下的方式却不会有问题:

    -(void)setCar:(Car *)car{
        if (_car!=car) { //首先判断要赋值的变量和当前成员变量是不是同一个变量
            [_car release]; //释放之前的对象
            _car=[car retain];//赋值时重新retain
        }
    }
    

    因为在这个方法中我们通过[car retain]保证每次属性赋值的时候对象引用计数器+1,这样一来调用过getCar方法可以保证人员的car属性不会被释放,其次为了保证上一次的赋值对象(car1)能够正常释放,我们在赋新值之前对原有的值进行release操作。最后在Person的dealloc方法中对_car进行一次release操作(因为setCar中做了一次retain操作)保证_car能正常回收。

    5.属性的所有权语义(ownership semantic)

    @property (strong, nonatomic) UIWindow *window;
      strong, nonatomic
    这些属性修饰语义,表征了属性的特性。oc的编译器会为属性自动生成get和set方法,而这两个方法会涉及对象所有权的保留与释放,诸如strong/weak这些不同的属性修饰符会造成不同的内存操作。ARC和非ARC模式下修饰符是不同的。

    非ARC

    语义 说明
    retain 保留新值,释放旧值,将新值赋给对象。引用计数+1
    assign 简单赋值,共享同一块内存地址。引用计数不变
    copy 建立一个新的引用计数为1的对象,然后释放旧对象。

    ARC

    语义 说明
    strong 表明拥有对象,同retain。
    weak 表明非拥有对象,同assign类似,但当对象被摧毁后,对象会被清空(变为nil)
    assign 同非ARC中assign,设置方法只会针对“纯量类型”,如NSInteger,CGFloat等
    copy 同非ARC中copy
    unsafe_unretained 同assign,但适用于“对象类型”,表明非拥有对象,但当对象摧毁后,对象不会自动清空,即可能会EXC_BAD_ACCESS。

    6、内存管理-黄金法则

    The basic rule to apply is everything that increases the reference counter with alloc, [mutable]copy[withZone:] or retain is in charge of the corresponding [auto]release.

    如果对一个对象使用了alloc、[mutable]copy、retain,那么你必须使用相应的release或者autorelease。

    类型定义:
    基本类型:任何C的类型,如:int、short、char、long、struct、enum、union等属于基本类型或者结构体;
    内存管理对于C语言基本类型无效;
    任何继承与NSObject类的对象都属于OC类型。
    所有OC对象都有一个计数器,保留着当前被引用的数量。

    内存管理对象:
    OC的对象:凡是继承于NSObject;
    每一个对象都有一个retainCount计数器。表示当前的被应用的计数。如果计数为0,那么就真正的释放这个对象。

    alloc、retain、release函数:

      1)alloc 函数是创建对象使用,创建完成后计数器为1;只用1次。
    
      2)retain是对一个对象的计数器+1;可以调用多次。
    
      3)release是对一个对象计数器-1;减到0对象就会从内存中释放。
    
     增加对象计数器的三种方式:
    
      1)当明确使用alloc方法来分配对象;
    
      2)当明确使用copy[WithZone:]或者mutableCopy[WithZone:]来copy对象的时;
    
      3)当明确使用retain消息。
    

    上述三种方法使得计数器增加,那么就需要使用[auto]release来明确释放对象,也就是递减计数器。

    copy属性:copy属性是完全把对象重新拷贝一份,计数器重新设置为1,和之前拷贝的数据完全脱离关系。

    7.ARC的本质

    ARC是编译器(时)特性,而不是运行时特性,更不是垃圾回收器(GC)。

    Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.
    
    

    ARC只是相对于MRC(Manual Reference Counting或称为非ARC,下文中我们会一直使用MRC来指代非ARC的管理方式)的一次改进,但它和之前的技术本质上没有区别。具体信息可以参考ARC编译器官方文档。

    ARC的开启与关闭
    不同于XCode4可以在创建工程时选择关闭ARC,XCode5在创建的工程是默认开启ARC,没有可以关闭ARC的选项。

    如果需要对特定文件开启或关闭ARC,可以在工程选项中选择Targets -> Compile Phases -> Compile Sources,在里面找到对应文件,添加flag:

    打开ARC:-fobjc-arc
    关闭ARC:-fno-objc-arc

    如图: 311559437226630.png

    ARC的修饰符
    ARC主要提供了4种修饰符,他们分别是:__strong,__weak,__autoreleasing,__unsafe_unretained。

    __strong
    表示引用为强引用。对应在定义property时的"strong"。所有对象只有当没有任何一个强引用指向时,才会被释放。
    注意:如果在声明引用时不加修饰符,那么引用将默认是强引用。当需要释放强引用指向的对象时,需要将强引用置nil。

    举例:如果我想创建一个字符串,使用完之后将它释放调用,使用MRC管理内存的写法应该是这样:

    {
    
        NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"对象的RC=1
    
        NSLog(@"%@", text);
    
        [text release];                      //@"Hello, world"对象的RC=0
    
    }
    

    而如果是使用ARC方式的话,就text对象无需调用release方法,而是当text变量超过作用域时,编译器来自动加入[text release]方法来释放内存

    {
    
        NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象的RC=1
    
        NSLog(@"%@", text);
    
    }
    
    /*
    
     *  当text超过作用域时,@"Hello, world"对象会自动释放,RC=0
    
     */
    
    而当你将text赋值给其他变量anotherText时,MRC需要retain一下来持有所有权,当text和anotherText使用完之后,各个调用release方法来释放。
    
    
    {
    
        NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象的RC=1
    
        NSLog(@"%@", text);
    
         
    
        NSString *anotherText = text;        //@"Hello, world"对象的RC=1
    
        [anotherText retain];                //@"Hello, world"对象的RC=2
    
        NSLog(@"%@", anotherText);
    
         
    
        [text release];                      //@"Hello, world"对象的RC=1
    
        [anotherText release];               //@"Hello, world"对象的RC=0
    
    }
    而使用ARC的话,并不需要调用retain和release方法来持有跟释放对象。
    
    
    {
    
        NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"对象的RC=1
    
        NSLog(@"%@", text);
    
         
    
        NSString *anotherText = text;        //@"Hello, world"对象的RC=2
    
        NSLog(@"%@", anotherText);
    
    }
    
    /*
    
     *  当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法, @"Hello, world"对象的RC=0
    
     */
    
    

    __weak
    其实编译器根据__strong修饰符来管理对象内存。但是__strong并不能解决引用循环(Reference Cycle)问题:对象A持有对象B,反过来,对象B持有对象A;这样会导致不能释放内存造成内存泄露问题。

    表示引用为弱引用。对应在定义property时用的"weak"。弱引用不会影响对象的释放,即只要对象没有任何强引用指向,即使有100个弱引用对象指向也没用,该对象依然会被释放。不过好在,对象在被释放的同时,指向它的弱引用会自动被置nil,这个技术叫zeroing weak pointer。这样有效得防止无效指针、野指针的产生。__weak一般用在delegate关系中防止循环引用或者用来修饰指向由Interface Builder编辑与生成的UI控件。

    举一个简单的例子,有一个类Test有个属性objc,有两个对象test1和test2的属性objc互相引用test1和test2:

    
    @interface Test : NSObject
    
    @property (strong, nonatomic) id objc;
    
    @end
    
    {
    
        Test *test1 = [Test new];        /* 对象a */
    
        /* test1有一个强引用到对象a */
    
        
        Test *test2 = [Test new];        /* 对象b */
    
        /* test2有一个强引用到对象b */
    
        
        test1.objc = test2;              /* 对象a的成员变量objc有一个强引用到对象b */
    
        test2.objc = test1;              /* 对象b的成员变量objc有一个强引用到对象a */
    
    }
    
    /*   当变量test1超过它作用域时,它指向a对象会自动release
    
     *   当变量test2超过它作用域时,它指向b对象会自动release
    
     *   
    
     *   此时,b对象的objc成员变量仍持有一个强引用到对象a
    
     *   此时,a对象的objc成员变量仍持有一个强引用到对象b
    
     *   于是发生内存泄露
    
     */
    
    

    如何解决?于是我们引用一个__weakownership qualifier,被它修饰的变量都不持有对象的所有权,而且当变量指向的对象的RC为0时,变量设置为nil。例如:

    __weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];
    NSLog(@"%@", text);
    

    由于text变量被__weak修饰,text并不持有@"Sam Lau"对象的所有权,@"Sam Lau"对象一创建就马上被释放,并且编译器给出警告,所以打印结果为(null)。

    所以,针对刚才的引用循环问题,只需要将Test类的属性objc设置weak修饰符,那么就能解决。

    @interface Test : NSObject
    
    @property (weak, nonatomic) id objc;
    
    @end
    
    {
    
        Test *test1 = [Test new];        /* 对象a */
    
        /* test1有一个强引用到对象a */
    
         
    
        Test *test2 = [Test new];        /* 对象b */
    
        /* test2有一个强引用到对象b */
    
         
    
        test1.objc = test2;              /* 对象a的成员变量objc不持有对象b */
    
        test2.objc = test1;              /* 对象b的成员变量objc不持有对象a */
    
    }
    
    /*   当变量test1超过它作用域时,它指向a对象会自动release
    
     *   当变量test2超过它作用域时,它指向b对象会自动release
    
     */
    

    __autoreleasing

    表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。

    一个常见的误解是,在ARC中没有autorelease,因为这样一个“自动释放”看起来好像有点多余。这个误解可能源自于将ARC的“自动”和autorelease“自动”的混淆。其实你只要看一下每个iOS App的main.m文件就能知道,autorelease不仅好好的存在着,并且变得更fashion了:不需要再手工被创建,也不需要再显式得调用[drain]方法释放内存池。

    __unsafe_unretained

    __unsafe_unretained ownership qualifier,正如名字所示,它是不安全的。它跟__weak相似,被它修饰的变量都不持有对象的所有权,但当变量指向的对象的RC为0时,变量并不设置为nil,而是继续保存对象的地址;这样的话,对象有可能已经释放,但继续访问,就会造成非法访问(Invalid Access)。例子如下:

    __unsafe_unretained id obj0 = nil;
    
     
    
    {
    
        id obj1 = [[NSObject alloc] init];     // 对象A
    
        /* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */
    
        obj0 = obj1;
    
        /* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */
    
        NSLog(@"A: %@", obj0);
    
    }
    
    /* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */
    
     
    
    NSLog(@"B: %@", obj0);
    
    /* 由于obj0是__unsafe_unretained,当它指向的对象RC=0时,它会继续保存对象的地址,所以两个地址相同 */
    

    打印结果是内存地址相同:

    1455700144595337.png

    如果将__unsafe_unretained改为weak的话,两个打印结果将不同

    __weak id obj0 = nil;
    
    {
    
        id obj1 = [[NSObject alloc] init];     // 对象A
    
        /* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */
    
        obj0 = obj1;
    
        /* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */
    
        NSLog(@"A: %@", obj0);
    
    }
    
    /* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */
    
     
    
    NSLog(@"B: %@", obj0);
    
    /* 由于obj0是__weak, 当它指向的对象RC=0时,它会自动设置为nil,所以两个打印结果将不同*/
    
    1455700201721033.png

    ARC是在iOS 5引入的,而这个修饰符主要是为了在ARC刚发布时兼容iOS 4以及版本更低的设备,因为这些版本的设备没有weak pointer system,简单的理解这个系统就是我们上面讲weak时提到的,能够在weak引用指向对象被释放后,把引用值自动设为nil的系统。这个修饰符在定义property时对应的是"unsafe_unretained",实际可以将它理解为MRC时代的assign:纯粹只是将引用指向对象,没有任何额外的操作,在指向对象被释放时依然原原本本地指向原来被释放的对象(所在的内存区域)。所以非常不安全。
    现在可以完全忽略掉这个修饰符了,因为iOS 4早已退出历史舞台很多年。*使用修饰符的正确姿势(方式=。=)

    这可能是很多人都不知道的一个问题,包括之前的我,但却是一个特别要注意的问题。
    苹果的文档中明确地写道:

    You should decorate variables correctly. When using qualifiers in an object variable declaration,
    
    the correct format is:
    
    ClassName * qualifier variableName;
    

    按照这个说明,要定义一个weak型的NSString引用,它的写法应该是:

    NSString * __weak str = @"hehe"; // 正确!
    
    __weak NSString *str = @"hehe";  // 错误!
    
    

    自动释放池

    在开发中,我们常常都会使用到局部变量,局部变量一个特点就是当它超过作用域时,就会自动释放。而autorelease pool跟局部变量类似,当执行代码超过autorelease pool块时,所有放在autorelease pool的对象都会自动调用release。它的工作原理如下:
    1.创建一个NSAutoreleasePool对象
    2.autorelease pool块的对象调用autorelease方法
    3.释放NSAutoreleasePool对象

    1455699554550339.png

    在ObjC中也有一种内存自动释放的机制叫做“自动引用计数”(或“自动释放池”),与C#、Java不同的是,这只是一种半自动的机制,有些操作还是需要我们手动设置的。自动内存释放使用@autoreleasepool关键字声明一个代码块,如果一个对象在初始化时调用了autorelase方法,那么当代码块执行完之后,在块中调用过autorelease方法的对象都会自动调用一次release方法。这样一来就起到了自动释放的作用,同时对象的销毁过程也得到了延迟(统一调用release方法)。看下面的代码:

    Person.h

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    #pragma mark - 属性
    #pragma mark 姓名
    @property (nonatomic,copy) NSString *name;
    
    #pragma mark - 公共方法
    #pragma mark 带参数的构造函数
    -(Person *)initWithName:(NSString *)name;
    #pragma mark 取得一个对象(静态方法)
    +(Person *)personWithName:(NSString *)name;
    

    Person.m

    #import "Person.h"
    
    @implementation Person
    
    #pragma mark - 公共方法
    #pragma mark 带参数的构造函数
    -(Person *)initWithName:(NSString *)name{
        if(self=[super init]){
            self.name=name;
        }
        return self;
    }
    #pragma mark 取得一个对象(静态方法)
    +(Person *)personWithName:(NSString *)name{
        Person *p=[[[Person alloc]initWithName:name] autorelease];//注意这里调用了autorelease
        return p;
    }
    
    #pragma mark - 覆盖方法
    #pragma mark 重写dealloc方法
    -(void)dealloc{
        NSLog(@"Invoke Person(%@) dealloc method.",self.name);
        [super dealloc];
    }
    @end
    

    main.m

    #import <Foundation/Foundation.h>
    #import "Person.h"
    
    
    int main(int argc, const char * argv[]) {
    
        @autoreleasepool {
            Person *person1=[[Person alloc]init];
            [person1 autorelease];//调用了autorelease方法后面就不需要手动调用release方法了
            person1.name=@"Kenshin";//由于autorelease是延迟释放,所以这里仍然可以使用person1
            
            Person *person2=[[[Person alloc]initWithName:@"Kaoru"] autorelease];//调用了autorelease方法
            
            Person *person3=[Person personWithName:@"rosa"];//内部已经调用了autorelease,所以不需要手动释放,这也符合内存管理原则,因为这里并没有alloc所以不需要release或者autorelease
            
            Person *person4=[Person personWithName:@"jack"];
            [person4 retain];
        }
        /*结果:
         Invoke Person(rosa) dealloc method.
         Invoke Person(Kaoru) dealloc method.
         Invoke Person(Kenshin) dealloc method.
         */
        
        return 0;
    }
    

    当上面@autoreleaespool代码块执行完之后,三个对象都得到了释放,但是person4并没有释放,原因很简单,由于我们手动retain了一次,当自动释放池释放后调用四个对的release方法,当调用完person4的release之后它的引用计数器为1,所有它并没有释放(这是一个反例,会造成内存泄露);autorelase方法将一个对象的内存释放延迟到了自动释放池销毁的时候,因此上面person1,调用完autorelase之后它还存在,因此给name赋值不会有任何问题;在ObjC中通常如果一个静态方法返回一个对象本身的话,在静态方法中我们需要调用autorelease方法,因为按照内存释放原则,在外部使用时不会进行alloc操作也就不需要再调用release或者autorelase,所以这个操作需要放到静态方法内部完成。

    对于自动内存释放简单总结一下:
    autorelease方法不会改变对象的引用计数器,只是将这个对象放到自动释放池中;
    自动释放池实质是当自动释放池销毁后调用对象的release方法,不一定就能销毁对象(例如如果一个对象的引用计数器>1则此时就无法销毁);
    由于自动释放池最后统一销毁对象,因此如果一个操作比较占用内存(对象比较多或者对象占用资源比较多),最好不要放到自动释放池或者考虑放到多个自动释放池;
    ObjC中类库中的静态方法一般都不需要手动释放,内部已经调用了autorelease方法;

    8. block的内存管理

    iOS中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:
      1)如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。

     @property (nonatomic, copy) void(^block)(NSData * data);
    

    2)block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。

     __weak typeof(self) weakSelf = self;
    

    [参考链接](http://blog.csdn.net/shaobo8910/article/details/55051846
    iOS开发ARC内存管理技术要点
    iOS开发系列—Objective-C之内存管理

    相关文章

      网友评论

          本文标题:iOS内功心法--内存管理机制

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