[iOS]MRC和ARC

来源:互联网 发布:单片机红外发射 编辑:程序博客网 时间:2024/06/07 00:05

objective-c中提供的两种内存管理机制MRC(MannulReference Counting) 和ARC(Automatic Reference Counting),分别提供内存的手动和自动管理,


MRC

retain,release,autorelease。

retain,作用将内存数据的所有权赋给另一个指针变量,引用计数加1,即retainCount +=1

release,释放指针变量对内存数据的所有权,引用计数减1,即retainCount -+1

autorelease,该方法将内存的管理放到autoreleasepool中


reatin和release操作的是引用计数。引用计数为0,便自动释放内存。


//假设Number为预定义的类

Number* num = [[Number alloc] init];

Number* num2 = [num retain];//此时引用记数+1,现为2

[num2 release]; //num2 释放对内存数据的所有权 引用记数-1,现为1;

[num release];//num释放对内存数据的所有权 引用记数-1,现为0;


@autoreleasepool的用法

@autoreleasepool {

       Number* num = [[Number alloc] init];

              [numautorelease];//由autoreleasepool来管理其内存的释放

   }


对与Objective-c中属性的标识符可以总结为:

@property (nonatomic/atomic,retain/assign/copy, readonly/readwrite) Number* num;

(1) nonatomic/atomic,表示该属性是否是对多线程安全的,是不是使用线程锁,默认为atomic,

(2) retain/assign/copy,是有关对该属性的内存管理的,



assign"is the default. In the setter that is created by @synthesize, the value will simply be assigned to the attribute, don’t operate the retain count. My understanding is that "assign" should be used for non-pointer attributes.

l   "retain"is needed when the attribute is a pointer to an object. The setter generated by@synthesize will retain (aka add a retain count) the object. You will need to release the object when you are finished with it.

l   "copy"is needed when the object is mutable. Use this if you need the value of the object as it is at this moment, and you don't want that value to reflect any changes made by other owners of the object. You will need to release the object when you are finished with it because you are retaining the copy.

(3)      readwrite /readonly -"readwrite" is the default. When you @synthesize, both a getter and a setter will be created for you. If you use "readonly", no setter will be created. Use it for a value you don't want to ever change after the instantiation of the object.



ARC(AutomaticReference Counting)

在ARC中与内存管理有关的标识符,可以分为变量标识符和属性标识符,对于变量默认为__strong,而对于属性默认为unsafe_unretained。也存在autoreleasepool。

ARC主要提供了4种修饰符,他们分别是:(有两个下横线)

__strong,

__weak, 

__autoreleasing,

 __unsafe_unretained。

 

对于变量标识符的用法:

__strong Number* num = [[Number alloc]init];

或者 Number* __strong num = [[Number alloc]init];

后一种才是官方推荐的写法。


 

在ARC内存管理模式下,其属性的标识符存在以下几种:

@property (nonatomic/atomic,   assign/retain/strong/weak/unsafe_unretained/copy,    readonly/readwrite) Number* num;//默认为unsafe_unretained

基本数据类型默认关键字是atomic,readwrite,assign
对于普通的OC对象atomic,readwrite,strong

 

其中assign/retain/copy与MRC下property的标识符意义相同,strong类似与retain,assign类似于unsafe_unretained,strong/weak/unsafe_unretained与ARC下变量标识符意义相同,只是一个用于属性的标识,一个用于变量的标识(带两个下划短线__)。所列出的其他的标识符与MRC下意义相同。

(1)对于assign,你可以对标量类型(如int)使用这个属性。你可以想象一个float,它不是一个对象,所以它不能retain、copy。

(2)对于copy,指定应该使用对象的副本(深复制),前一个值发送一条release消息。基本上像retain,但是没有增加引用计数,是分配一块新的内存来放置它。特别适用于NSString,如果你不想改变现有的,就用这个,因为NSMutableString,也是NSString。

 

strong,weak,unsafe_unretained都是是用来声明属性的,如果想声明变量就用_strong,_weak,_unsafe_unretained,_autoreleasing

对于变量的标识符有:

(1) __strong,默认,只要有强指针指向它,就保持活跃,引用计数加1。

(2) __weak,一个有weak修饰符的对象a,其所引用的对象b,如果只有弱指针指向b,b也会被销毁,nil被赋值给a。

(3)__unsafe_unretained,类似于一个在引用的对象没有强指针是,不会被自动赋值为nil的weak指针,容易造成野指针,引起程序崩溃。可以理解为assign:纯粹只是将引用指向对象,没有任何额外的操作,在指向对象被释放时依然原原本本地指向原来被释放的对象(所在的内存区域)。所以非常不安全。现在完全可以忽略。

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

以下两行代码的意义是相同的。

NSString *str = [[[NSString alloc] initWithFormat:@"hehe"] autorelease]; // MRC

NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"hehe"]; // ARC

__autoreleasing在ARC中主要用在参数传递返回值(out-parameters)和引用传递参数(pass-by-reference)的情况下

比如常用的NSError的使用:

NSError *__autoreleasing error; 

if (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) 

  NSLog(@"Error: %@", error); 

}

(在上面的writeToFile方法中error参数的类型为(NSError *__autoreleasing *))

*error指向的对象在创建出来后,被放入到了autoreleasing pool中,等待使用结束后的自动释放,函数外error的使用者并不需要关心*error指向对象的释放。

另外一点,在ARC中,所有这种指针的指针 (NSError **)的函数参数如果不加修饰符,编译器会默认将他们认定为__autoreleasing类型。

比如下面的两段代码是等同的:

- (NSString *)doSomething:(NSNumber **)value

{

        // do something  

}

- (NSString *)doSomething:(NSNumber * __autoreleasing *)value

{

        // do something  

}

除非显式给value声明了__strong,否则value默认就是__autoreleasing的。


栈中指针默认值为nil

无论是被strong,weak还是autoreleasing修饰,声明在栈中的指针默认值都会是nil。所有这类型的指针不用再初始化的时候置nil了。虽然好习惯是最重要的,但是这个特性更加降低了“野指针”出现的可能性。

在ARC中,以下代码会输出null而不是crash:)

- (void)myMethod 

{

    NSString *name;

    NSLog(@"name: %@", name);

}



变量标识符eg:

    1. __strong NSString *yourString = [[NSString alloc] initWithUTF8String:"string 1"];   

    2.    __weak  NSString *myString = yourString;   

    3.    __unsafe_unretained NSString *theirString = myString;  

    4.    yourString = nil;   

    5.    //现在yourString与myString的指针都为nil,而theirString不为nil,但是是野指针。  


如果NSString *str = @"str test";这样将声明一个字符串常量,这样声明的不受上面所说的限制。

如:

 

    1.    __strong NSString *yourString = @"test string";   

    2.    __weak  NSString *myString = yourString;   

    3.    yourString = nil;   

    4.    //现在myString还是有值的  


NSString *str = [[NSString alloc]  initWithString:@"test"];这样返回的也是字符串常量, 效果与 NSString *str = @"test";是一样的。  但得遵循苹果内存管理,在非ARC的情况下还是要调用release,其实不需要调用也不会内存泄漏。


__autoreleasing的用法??





ARC与Block


在MRC时代,Block会隐式地对进入其作用域内的对象(或者说被Block捕获的指针指向的对象)加retain,来确保Block使用到该对象时,能够正确的访问。

这件事情在下面代码展示的情况中要更加额外小心。


MyViewController *myController = [[MyViewController alloc] init…];


// 隐式地调用[myController retain];造成循环引用

myController.completionHandler =  ^(NSInteger result) {

   [myController dismissViewControllerAnimated:YES completion:nil];

};


[self presentViewController:myController animated:YES completion:^{

   [myController release]; // 注意,这里调用[myController release];是在MRC中的一个常规写法,并不能解决上面循环引用的问题

}];


在这段代码中,myController的completionHandler调用了myController的方法[dismissViewController...],这时completionHandler会对myController做retain操作。而我们知道,myController对completionHandler也至少有一个retain(一般准确讲是copy),这时就出现了在内存管理中最糟糕的情况:循环引用!简单点说就是:myController retain了completionHandler,而completionHandler也retain了myController。循环引用导致了myController和completionHandler最终都不能被释放。我们在delegate关系中,对delegate指针用weak就是为了避免这种问题。

不过好在,编译器会及时地给我们一个警告,提醒我们可能会发生这类型的问题:

对这种情况,我们一般用如下方法解决:给要进入Block的指针加一个__block修饰符。

这个__block在MRC时代有两个作用:

说明变量可改

说明指针指向的对象不做这个隐式的retain操作

一个变量如果不加__block,是不能在Block里面修改的,不过这里有一个例外:static的变量和全局变量不需要加__block就可以在Block中修改。

使用这种方法,我们对代码做出修改,解决了循环引用的问题:

MyViewController * __block myController = [[MyViewController alloc] init…];

// ...

myController.completionHandler =  ^(NSInteger result) {

    [myController dismissViewControllerAnimated:YES completion:nil];

};

//之后正常的release或者retain

在ARC引入后,没有了retain和release等操作,情况也发生了改变:在任何情况下,__block修饰符的作用只有上面的第一条:说明变量可改。即使加上了__block修饰符,一个被block捕获的强引用也依然是一个强引用。这样在ARC下,如果我们还按照MRC下的写法,completionHandler对myController有一个强引用,而myController对completionHandler有一个强引用,这依然是循环引用,没有解决问题:(

于是我们还需要对原代码做修改。简单的情况我们可以这样写:

__block MyViewController * myController = [[MyViewController alloc] init…];

// ...

myController.completionHandler =  ^(NSInteger result) {

    [myController dismissViewControllerAnimated:YES completion:nil];

    myController = nil;  // 注意这里,保证了block结束myController强引用的解除

};

在completionHandler之后将myController指针置nil,保证了completionHandler对myController强引用的解除,不过也同时解除了myController对myController对象的强引用。这种方法过于简单粗暴了,在大多数情况下,我们有更好的方法。

这个更好的方法就是使用weak。(或者为了考虑iOS4的兼容性用unsafe_unretained,具体用法和weak相同,考虑到现在iOS4设备可能已经绝迹了,这里就不讲这个方法了)(关于这个方法的本质我们后面会谈到)

为了保证completionHandler这个Block对myController没有强引用,我们可以定义一个临时的弱引用weakMyViewController来指向原myController的对象,并把这个弱引用传入到Block内,这样就保证了Block对myController持有的是一个弱引用,而不是一个强引用。如此,我们继续修改代码:

MyViewController *myController = [[MyViewController alloc] init…];

// ...

MyViewController * __weak weakMyViewController = myController;

myController.completionHandler =  ^(NSInteger result) {

    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];

};

这样循环引用的问题就解决了,但是却不幸地引入了一个新的问题:由于传入completionHandler的是一个弱引用,那么当myController指向的对象在completionHandler被调用前释放,那么completionHandler就不能正常的运作了。在一般的单线程环境中,这种问题出现的可能性不大,但是到了多线程环境,就很不好说了,所以我们需要继续完善这个方法。

为了保证在Block内能够访问到正确的myController,我们在block内新定义一个强引用strongMyController来指向weakMyController指向的对象,这样多了一个强引用,就能保证这个myController对象不会在completionHandler被调用前释放掉了。于是,我们对代码再次做出修改:


MyViewController *myController = [[MyViewController alloc] init…];

// ...

MyViewController * __weak weakMyController = myController;

myController.completionHandler =  ^(NSInteger result) {

    MyViewController *strongMyController = weakMyController;


  if (strongMyController) {

        // ...

        [strongMyController dismissViewControllerAnimated:YES completion:nil];

        // ...

    }

    else {

        // Probably nothing...

    }

};


到此,一个完善的解决方案就完成了:)

为了更清楚地说明问题,这里用一个简单的程序举例。比如我们有如下程序:


#include <stdio.h>


int main()

{

    int b = 10;

    

    int *a = &b;

    

    void (^blockFunc)() = ^(){

    

        int *c = a;


    };

    

    blockFunc();

    

    return 1;

}


程序中,同为int型的指针,a是被Block捕获的变量,而c是在Block内定义的变量。我们用clang -rewrite-objc处理后,可以看到如下代码:

原main函数:


int main()

{

    int b = 10;


    int *a = &b;


    void (*blockFunc)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);


    ((void (*)(__block_impl *))((__block_impl *)blockFunc)->FuncPtr)((__block_impl *)blockFunc);


    return 1;

}


Block的结构:


struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  

  int *a; // 被捕获的引用 a 出现在了block的结构体里面

  

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};


实际执行的函数:


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  int *a = __cself->a; // bound by copy



        int *c = a; // 在block中声明的引用 c 在函数中声明,存在于函数栈上


    }


我们可以清楚得看到,a和c存在的位置完全不同,如果Block存在于堆上(在ARC下Block默认在堆上),那么a作为Block结构体的一个成员,也自然会存在于堆上,而c无论如何,永远位于Block内实际执行代码的函数栈内。这也导致了两个变量生命周期的完全不同:c在Block的函数运行完毕,即会被释放,而a呢,只有在Block被从堆上释放的时候才会释放。

回到我们的MyViewController的例子中,同上理,如果我们直接让Block捕获我们的myController引用,那么这个引用会被复制后(引用类型也会被复制)作为Block的成员变量存在于其所在的堆空间中,也就是为Block增加了一个指向myController对象的强引用,这就是造成循环引用的本质原因。对于MyViewController的例子,Block的结构体可以理解是这个样子:(准确的结构体肯定和以下这个有区别,但也肯定是如下这种形式:)


struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  

  MyViewController * __strong myController;  // 被捕获的强引用myController

  

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};


而反观我们给Block传入一个弱引用weakMyController,这时我们Block的结构:


struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  

  MyViewController * __weak weakMyController;  // 被捕获的弱引用weakMyController

  

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};


再看在Block内声明的强引用strongMyController,它虽然是强引用,但存在于函数栈中,在函数执行期间,它一直存在,所以myController对象也一直存在,但是当函数执行完毕,strongMyController即被销毁,于是它对myController对象的强引用也被解除,这时Block对myController对象就不存在强引用关系了!加入了strongMyController的函数大体会是这个样子:


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {


  MyViewController * __strong strongMyController = __cself->weakMyController; 


    // ....

    }


综上所述,在ARC下(在MRC下会略有不同),Block捕获的引用和Block内声明的引用无论是存在空间与生命周期都是截然不同的,也正是这种不同,造成了我们对他们使用方式的区别。

以上就解释了之前提到的所有问题,希望大家能看明白:)

好的,最后再提一点,在ARC中,对Block捕获对象的内存管理已经简化了很多,由于没有了retain和release等操作,实际只需要考虑循环引用的问题就行了。比如下面这种,是没有内存泄露的问题的:


TestObject *aObject = [[TestObject alloc] init];

    

aObject.name = @"hehe";


self.aBlock = ^(){

    

    NSLog(@"aObject's name = %@",aObject.name);

        

};


我们上面提到的解决方案,只是针对Block产生循环引用的问题,而不是说所有的Block捕获引用都要这么处理,一定要注意!

0 0
原创粉丝点击