内存管理(ARC)

1.ARC的本质

ARC是编译器的特性,它并没有改变OC引用计数式内存管理的本质,更不是GC(垃圾回收),底层实现依然依赖引用计数,只不过ARC模式下编译器会自动帮我们管理。

打开ARC:-fobjc-arc

关闭ARC:-fno-objc-arc

2.引用计数管理原则

(1)自己生成的对象自己持有

(2)非自己生成的对象自己也可以持有

(3)自己持有的对象不需要时可以释放

(4)非自己持有的对象自己不能释放

3.四种变量所有权修饰符

__strong

__weak

__autoreleasing

__unsage_unretaied

以上是变量所有权修饰符


以下是属性修饰符

copy 对应的所有权类型是 __strong

拷贝,复制一个对象并创建strong关联,引用计数为1,原对象计数不变

retain 对应的所有权类型是 __strong

持有(MRC),强引用,原对象引用计数加1

strong 对应的所有权类型是 __strong

持有(ARC),强引用,原对象引用计数加1

weak 对应的所有权类型是 __weak

赋值,弱引用,不改变引用计数。对象释放后把指针置为nil,避免了也指针

assing 对应的所有权类型是 __unsage_unretaied

赋值,弱引用,引用计数不变。ARC中对象不能使用assign,但基本类型(BOOL、int、float)仍可以使用

unsafe_unretained 对应的所有权类型是 __unsage_unretaied


关于__strong

强引用,对于所有对象,只有当没有任何一个强引用指向它时,它才能够被释放。如果声明引用时不加修饰符,默认是强引用。如果要释放强引用指向的对象时,要将强引用置为nil。


关于__weak

弱引用,弱引用不会影响对象的释放,如果一个对象释放了,那么指向它的所有弱引用全部置为nil,防止产生野指针。

最常见的用法是使用__weak来避免强循环引用。比如:

(1)设置delegate属性为weak。

(2)block中防止强循环引用。

(3)一般由SB和xib脱线的控件

@property (weak, nonatomic) IBOutlet UIButton *testButton;。

关于__autoreleasing

表示在autorelease pool中自动释放对象,与MRC模式下相同。并没有对应的属性修饰符,任何一个对象的属性都不应该是autorelease型的。但是autorelease是一直存在于ARC模式下的。

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

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

__autoreleasing常见的用法:

(1)参数传递返回值

示例:

- (NSObject *)object {
    NSObject *o = [[NSObject alloc] init];

    return o;
}

这里o默认是__strong,由于return使得o超出了其作用于,本来应该释放的,但是因为需要将它作为返回值,所以一般情况下要将它注册倒Autorelease Pool中(ARC模式下可以通过运行时优化方案来跳过autorelease机制)。

autorelease机制:

接收方:调用方法的对象,使用o的话就需要强引用它,那么retaincount +1,使用后再rataincount -1。

提供方:提供对象作为返回值,创建了对象那么retaincount +1,使用后再rataincount -1。

但是你要保证对象在返回前没有被释放,否则返回的是nil,这个时候需要一个合理的机制来延长对象的生命周期,又能保证释放它,这个机制就是autorelease机制。

对象作为返回值时,会被放入正在使用的Autorelease Pool中,Autorelease Pool会强引用这个对象,所以对象不会被释放,延长了生命周期,Autorelease Pool自己销毁的时候会讲里面的对象一并销毁。

Autorelease Pool是与线程一一映射的,如果Autorelease Pool的drain方法没有在接收方和提供方交接过程中触发,那么autorelease对象不会被释放(除非严重的线程错乱)。

Autorelease Pool释放的时机

  • Run Loop会在每次loop结尾时销毁它。
  • GCD 的 dispatched blocks 会在一个 Autorelease Pool 的上下文中执行,这个 Autorelease Pool 不时的就被销毁了(依赖于实现细节)。NSOperationQueue 也是类似。
  • 其他线程则会各自对他们对应的 Autorelease Pool 的生命周期负责。

ARC下跳过autorelease机制的优化方法

为了兼容MRC,以及在MRC-to-ARC,ARC-to-MRC,ARC-to-ARC切换。

return的时候:ARC调用objc_autoreleaseReturnValue() 替代autorelease。

调用方持有对象的时候:ARC 会调用objc_retainAutoreleasedReturnValue()。

在调用 objc_autoreleaseReturnValue() 时,ARC会在栈上查询return address来确定return value是否传给了objc_retainAutoreleasedReturnValue()。如果没传,那么会走autorelease过程。如果传了(返回值能从提供方传给交接方),就跳过autorelease并同时修改retain address来跳过objc_retainAutoreleasedReturnValue(),从而一举消除了autorelease和retain。

(2)访问__weak修饰的变量

访问__weak修饰的变量,实际上必定会访问到Autorelease Pool中注册的对象。

id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);

因为对象只是弱引用,为了保证访问对象的时候,对象没有被废弃,将对象注册到Autorelease Pool中,这样就能保证在Autorelease Pool被销毁前对象时存在的。

(3)引用传递参数

NSError *__autoreleasing error; 
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) 
{ 
  NSLog(@"Error: %@", error); 
} 

error参数的类型是(NSError *__autoreleasing *)。如果你讲error定义为strong类型,那么编译器会隐式的进行转换:

NSError *error; 
NSError *__autoreleasing tempError = error; // 编译器添加 
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError]) 
{ 
  error = tempError; // 编译器添加 
  NSLog(@"Error: %@", error); 
}

所以为了提高效率,在定义error的时候将其声明为_autrorelease类型的。

在ARC中,这种指针的指针类型的函数参数(NSError **),如果不加修饰符,编译器默认为_autrorelease类型。

(4)某些类的方法会隐式的使用自己的Autorelease Pool,这时使用_autorelease类型要小心。


- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          @autoreleasepool  // 被隐式创建
      {
              if (there is some error && error != nil)
              {
                    *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
              }
          }
    }];

    // *error 在这里已经被dict的做枚举遍历时创建的autorelease pool释放掉了 :(  
}  

为了能正常使用*error,需要一个临时的强引用,在dict的block中使用它,保证引用的对象不会在出了block后被释放:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
  __block NSError* tempError; // 加__block保证可以在Block内被修改  
  [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
  { 
    if (there is some error) 
    { 
      *tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil]; 
    }  

  }] 

  if (error != nil) 
  { 
    *error = tempError; 
  } 
} 

关于__unsafe _unretained

为了兼容低版本,相当于MRC模式下的assign,仅仅是引用了对象,没有其他任何操作,当对象被释放后依然指向对象(所在的内存地址),会形成野指针,非常的不安全。

现版本可以忽略掉这个修饰符,因为是ARC的时代了。


*的正确使用方式:

NSString * __weak str = @"hehe";

声明在栈中的指针默认为nil,如:

- (void)myMethod 
{
    NSString *name;
    NSLog(@"name: %@", name);
}

会输出null而不是crash。


ARC与Block

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...
    }
};

这里myController强引用了completionHandler

completionHandler调用了dismiss方法,也强引用了completionHandler

这样的话就形成了循环引用。

ARC模式下__ block只代表能在block中修改这个变量,没有其他作用。

要想避免循环引用,需要使用一个weakMyController对象弱引用myController,这样block中对myController持有弱引用的话,就不会产生循环引用。

但是由于block对myController是持有弱引用,那么就有可能在block引用myController之前,myController已经被释放,block因此不能正常使用(单线程中问题不大,一般出现在多线程中)。

所以我们在block中定义一个强引用strongMyController,用它来指向weakMyController指向的对象(我是这样想的,引用其实是指针,这里是将weakMyController指向的地址赋值给了strongMyController,这样strongMyController也指向了相同的地址,也就是同一个对象),这样在block使用之前,myController就不会被提前释放了。

block捕获的变量和在block中定义的变量是有区别的,存在空间和生命周期都是不同的

并不是说所有的block都存在循环引用,比如下面这个:

TestObject *aObject = [[TestObject alloc] init];
    
aObject.name = @"hehe";

self.aBlock = ^(){
    
    NSLog(@"aObject's name = %@",aObject.name);
        
};

堆和栈的区别

栈:系统分配,自动管理,存放参数,变量,指针等。基本数据类型存放在栈中,所以内存管理不包括这些。

堆:程序员自己分配,自己释放,存放对象类型,是内存管理的主要内容。

block是分配在栈上面的,为了不被系统回收,声明时会使用copy属性,copy到堆中。

结构体也一样,由于是分配到栈中,所以如果想要传递结构体类型的参数的话,应该将结构体作为一个对象的属性,然后将对象放到数组或字典中(只能存放对象类型)。

引用文章链接:iOS开发ARC内存管理技术要点

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,780评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,424评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,397评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,576评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,997评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,945评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,107评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,850评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,625评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,804评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,285评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,613评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,291评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,164评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,963评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,096评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,886评论 2 278

推荐阅读更多精彩内容