通过 ARC 管理内存

标签:堆栈、内存管理、ARC、@property 属性、@synthesize 属性合成、@autoreleasepool 自动释放池;

3.1 栈

当程序执行某个方法(或函数)时,会从内存中名为栈(stack)的区域分配一块内存空间,这块内存空间称为帧( frame)。帧负责保存程序在方法内声明的变量的值。在方法内声明的变量称为局部变量(local variable)。

当某个应用启动并运行 main 函数时,它的帧会被保存在栈的底部。当 main 调用另一个方法(或函数)时,这个方法(或函数)的帧会压入栈的顶部。被调用的方法还可以再调用其他方法,依此类推,最终会在栈中形成一个塔状的帧序列。当被调用的方法(或函数)结束时,程序会将其帧从栈顶“弹出”并释放。如果同一个方法再次被调用,则应用会创建一个全新的帧,并将其压入栈的顶部。

通过 ARC 管理内存_第1张图片
调用 RandomItems 的 main 函数时栈的变化过程

3.2 堆

堆(heap)是指内存中的另一块区域,和栈是分开的。为这两类内存区域分别取名堆和栈,是为了能够形象地描述这两个概念。

栈会按照后进先出的规则保存一组帧,而堆则包含了大量无序的活动对象,需要通过指针来保存这些对象在堆中的地址。

当应用向某个类发送 alloc 消息时,系统会从堆中分配出一块内存,其大小足够存放相应对象的全部实例变量。

ARC 和内存管理

编写 iOS 应用时,需要通过 ARC 管理内存,也就是自动引用计数。

3.3 指针变量与对象所有权

指针变量暗含了对其所指向的对象的所有权(ownership)。

  • 当某个方法(或函数)有一个指向某个对象的局部变量时,可以称该变量拥有该变量所指向的对象。
  • 当某个对象有一个指向其他对象的实例变量时,可以称该对象拥有该实例变量所指向的对象。

3.4 强引用与弱引用

只要指针变量指向了某个对象,那么该对象就会多一个拥有者,并且不会被程序释放。这种指针特性称为强引用(strong reference)。

程序也可以选择让指针变量不影响其指向对象的拥有者个数。这种不会改变对象拥有者个数的指针特性称为弱引用( weak reference)。

使用 __weak 关键字,可以将某个变量声明为具有弱引用特性。

大部分强引用循环问题都可以为其确定一个父-子关系。通常情况下,父对象应该使用具有强引用特性的指针,指向子对象。而子对象则应该使用具有弱引用特性的指针,指回父对象。这样就可以避免强引用循环问题。

具有弱引用特性的指针指向的对象被释放后,指针会自动设置为 nil

3.5 属性

通过属性,也可以为类声明实例变量并实现相应的存取方法,而且更简便。

声明一个属性,等于隐含地为相应名称的实例变量声明一对存取方法。

属性的特性

属性的特性用于描述相应存取方法的行为。

@property (nonatomic, readwrite, strong) NSString *itemName;

多线程特性

多线程特性(Multi-threading attribute)有两种可选类型:nonatomicatomic

大多数 Objective-C 程序员会将这个特性设置为 nonatomic

因为 nonatomic 不是默认类型,所以在声明属性时,必须明确地写出 nonatomic

读/写特性

读/写特性(Read/write attribute)也有两种可选类型:readwritereadonly

编译器会为具有 readwrite 特性的属性生成存方法取方法,如果是 readonly 类型,则只会生成取方法

内存管理特性

内存管理特性(Memory management attribute)有四种可选类型: strongweakcopy
unsafe_unretained。这些类型决定相应的实例变量将如何引用对象。

对于不指向任何对象的属性(例如int valuelnDollars),不需要做内存管理,这时应该选用 unsafe_unretained,它表示存取方法会直接为实例变量赋值。 Apple 引入ARC 之前曾经使用 assign 表示这种类型。

unsafe_unretained 中的 “unsafe(不安全)” 可能会误导读者。该类型的“不安全”是相对于弱引用而言的。与弱引用不同, unsafe_unretained 类型的指针指向的对象被销毁时,指针不会自动设置为 nil,而是成为空指针,因此不安全。但是当处理非对象属性(non -object-)时,是不会出现空指针问题的。

unsafe_unretained 是非对象属性的默认值,所以 valuelnDollars 属性不用明确写出该类型。

通常情况下,当某个属性是指向其他对象的指针,而且该对象的类有可修改的子类(例如 NSString/NSMutableString 或 NSArray/NSMutableArray)时,应该将该属性的内存管理特性设置为 copy

声明为 copy 的原因:如果属性指向的对象的类有可修改的子类,那么该属性可能会指向可修改的子类对象,同时,该对象可能会被其他拥有者修改。因此,最好先复制该对象,然后再将属性指向复制后的对象(编写具有防御性的代码)。

自定义属性的存取方法

可以在实现文件中编写自定义的存取方法,覆盖属性默认实现的方法。

⚠️ 如果既覆盖了存方法,也覆盖了取方法(或者为只读属性覆盖了取方法),那么编译器就不会再自动创建
相应的实例变量了。如果需要实例变量,就必须明确声明。

3.6 属性合成

属性会自动生成存取方法,也会自动声明和创建实例变量。

在头文件中声明属性时,只会生成存取方法的声明。为了让属性生成实例变量并实现存取方法,该属性必须被合成 synthesized)。通常情况下,编译器会自动合成属性并生成默认的实例变量和存取方法。如果需要自定义属性的合成方式,可以在实现文件中使用 @synthesize 指令:

示例:见源码中的 Person 类。

3.7 Autorelease 池与 ARC 历史

Clang 静态分析器(Clang static analyzer)。

当对象收到 autorelease 消息时,某个自动释放池会成为该对象的临时拥有者。

自动释放池解决的问题:某个方法创建了一个新的对象,但是创建方又不需要成为该对象的拥有者。为了能返回新创建的对象,同时避免提前释放问题,就可以向新创建的对象发送 autorelease 消息。便捷方法借助自动释放池,将新创建的对象返回给调用方,又不产生内存管理问题。便捷方法的代码示例如下:

+ (BNRItem *)someItem {
    BNRItem *item = [[[BNRItem alloc] init] autorelease];
    return item;
}

‼️ ARC 下禁止使用 autorelease 特性!

@autoreleasepool 指令后面跟一对花括号,可以创建一个自动释放池。

@autoreleasepool 中,如果某个方法返回一个新创建的对象,而该方法的方法名不包含 allocinit,那么这个新创建的对象通常会被放入相应的自动释放池。在应用执行完某个@autoreleasepool 中的程序段后,该自动释放池中的所有对象都会失去一个拥有者,代码如下:

@autoreleasepool {
    // 从 someitem 方法得到一个 BNRItem 对象,该方法的方法名没有包含 alloc 或 copy
    BNRItem *item = [BNRItem someitem];
}// 自动释放池被销毁,item 变量所指向的对象会被释放。

iOS 应用会自动创建一个默认的自动释放池,所以开发者无需关心这个问题,但是了解 @autoreleasepool 的作用有益无害。

你可能感兴趣的:(通过 ARC 管理内存)