03 managing memory with arc
DESCRIPTION
使用 ARC 管理内存,强引用,ruo'yin'yonTRANSCRIPT
使⽤用 ARC 管理内存
范圣刚,[email protected],www.tfan.org
•学习在 iOS 中内存是如何被管理的,以及⾃自动引⽤用计数背后的概念
堆(The Heap)
•所有的Objective-C 对象都存在堆中
• alloc
•memory chunk -> 包括对象实例变量需要的空间
对象⼤大⼩小•NSDate
• double类型 -> 存储从⼀一个固定参考点开始过去的秒数
• isa 指针 -> 从 NSObject 继承
• double + pointer = 8 + 4 = 12 字节
• BNRItem
•四个指针(isa, itemName, serialNumber, dateCreated)
•⼀一个int (valueInDollars)
• 4*4 + 4 = 20 字节
BNRItem 和 NSDate 实例的字节数
栈(The Stack)
•本地变量,也就是在⽅方法内声明的变量的值都存储在栈内•⽅方法执⾏行 -> 分配内存块(frame)
• pushed on & popped off
指针变量和对象所有权•指针变量传递它们指向的对象的所有权•⽅方法的本地变量指向⼀一个对象,⽅方法拥有(own)指向的对象
•对象有指向另⼀一个对象的实例变量,我们说包含指针的对象 own 被指向的对象
RandomPossessions 中的对象和指针• main ⽅方法中的 items 本地变量指向 NSMutableArray,NSMutableArray 属于
main 函数
• NSMutableArray -> BNRItems
• BNRItem -> 实例变量指向的对象
内存管理•有限的内存•不再使⽤用的对象要释放,还需要的不要销毁•对象所有者的理念帮助我们确定⼀一个对象是否应该被销毁:•没有所有者的对象应该被释放(⽆无法被发送消息),内存泄露。
•⾄至少拥有⼀一个所有者的对象不应该被释放。(过早释放)
新建 Quiz 项⺫⽬目
对象是如何失去所有者的?•指向对象的变量更改成指向另外⼀一个对象•指向对象的变量被设置成 nil
•指向对象的变量⾃自⾝身被销毁
指针更改当 itemName 的值从“Rusty Spork”字符串的地址更改成指向“Shiny Spork”字符串的地址时,“Rusty Spork”字符串就失去了所有者。
指针设成 nil
• serialNumber = nil;
指针变量⾃自⾝身被销毁•实例变量,在堆⾥里作为对象的⼀一部分•对象销毁,实例变量也被销毁•实例变量指向的对象就失去了所有者•本地变量,存在于⽅方法的 frame 中•⽅方法执⾏行完毕,frame 被弹出堆栈
•本地变量指向的对象就失去了所有者
在集合对象中的对象•位于数组中的对象,其拥有者是集合对象• [items removeObject:p];
•被移除的对象(p)失去所有者
连锁反应•⼀一个对象可以拥有其他对象,其他对象⼜又可以拥有对象
•单个对象的析构可能会引起(失去所有者,对象析构,释放内存)⼀一系列连锁反应
RandomPossessions 的变量和指针
RandomPossession 的连锁反应1.在 main.m 中,打印完 BNRItem 之后,我们把
items = nil;
2.数组失去所有者被销毁3.NSMutableArray 中指向 BNRItem 的指针也被销毁4.BNRItem失去所有者,也被销毁5.BNRItem 实例变量被销毁,实例变量指向的对象失去所有者,也被销毁
重写 dealloc// BNRItem.m- (void)dealloc{ NSLog(@"被销毁: %@", self);}
// main.m for (BNRItem *item in items) { NSLog(@"%@", item); }
NSLog(@"设置 items 为 nil ..."); items = nil;
强引⽤用和弱引⽤用•任何时候只要⼀一个对象的地址被保存在⼀一个指针变量中,对象就有它的拥有者,并且保持存活,这就叫做 strong reference
•有时⼀一个变量并不拥有它指向的对象,称作 weak reference
• retain cycle: 当两个或者更多对象互相之间具有 强引⽤用,当两个对象互相 own 对⽅方时,永远不会通过 ARC 销毁。
Retail Cycle Demo
•让 BNRItem 可以持有另外⼀一个 BNRItem
•同时 BNRItem 也可以知道被谁所持有
•步骤:• BNRItem.h 中增加两个实例变量和 accessors
• BNRItem.m 中实现 accessors
•修改 main.m, 把原来的随机⽣生成 item 去掉,改成⽣生成两个 item,⼀一个包含另外⼀一个。
BNRItem.h: 实例变量和 accessor 声明// BNRItem.h@interface BNRItem : NSObject{ NSString *itemName; NSString *serialNumber; int valueInDollars; NSDate *dateCreated; // 增加两个 BNRItem 实例变量 BNRItem *containedItem; BNRItem *container;}// 增加 accessor- (void)setContainedItem:(BNRItem *)item;- (BNRItem *)containedItem;
- (void)setContainer:(BNRItem *)item;- (BNRItem *)container;
BNRItem.m: 实现 accessors- (void)setContainedItem:(BNRItem *)item{ containedItem = item; [item setContainer:self];}
- (BNRItem *)containedItem{ return containedItem;}
- (void)setContainer:(BNRItem *)item{ container = item;}
- (BNRItem *)container{ return container;}
main.m: 包含和被包含的 item
NSMutableArray *items = [[NSMutableArray alloc] init];
BNRItem *backpack = [[BNRItem alloc] init]; [backpack setItemName:@"背包"]; [items addObject:backpack]; BNRItem *calculator = [[BNRItem alloc] init]; [calculator setItemName:@"计算器"]; [items addObject:calculator]; [backpack setContainedItem:calculator]; NSLog(@"设置 items 为 nil ..."); items = nil;
带 retain cycle 的 RandomPossessions
带 retain cycle 的 运⾏行结果
⼀一个 retain cycle
•两个 BNRItem 不会被销毁,同时它们实例变量指向的对象也⽆无法被销毁。
谁应该被设成弱引⽤用?•两个 BNRItem 之中的⼀一个要设成 weak,which?
•⽗父⼦子关系
• 每⼀一个 retain cycle 都可以被分解成⽗父⼦子关系
• parent 通常保存⼀一个强引⽤用到它的 child
•所以如果 child 也需要⼀一个到 parent 的指针,这个指针必须是弱引⽤用才可以避免 retain cycle
• __weak BNRItem *container;
• parent’s parent
• Xcode Leaks ⼯工具
弱引⽤用销毁的⾃自动检测•weak reference 的有趣属性
__unsafe_unretained
•和弱引⽤用⼀一样,不拥有它指向的对象的所有权•和弱引⽤用不⼀一样的是,不⾃自动设成 nil
•向后兼容的需要•尽量使⽤用 __weak 替代 __unsafe_unretained
避免了 retain cycle 的 RandomPosessions
属性•实例变量 -> 声明和实现⼀一对 accessor ⽅方法
•使⽤用 properties 替代 -> 简化输⼊入,代码更加清晰易读
声明属性•属性在类的接⼝口中声明,形式:• @peoperty NSString *itemName;
•声明⼀一个属性,相当于给实例变量隐式声明了⼀一个同名的 setter 和 getter ⽅方法。• - (void)setItemName:(NSString *)str;
• - (NSString *)itemName;
property 的 attributes
•属性的属性 -> accessors ⽅方法的⾏行为
•在 @property 指⽰示符后⾯面的括号中声明•@property (nonatomic, readwrite, strong) NSString
*itemName;
•共有三个 peoperty attributes, 每个 attributes 有两个或三个options,其中⼀一个是 default 所以不需要显式声明。
property 的第⼀一个 attribute
• 2 个选项•nonatomic
• atomic
使⽤用属性替换访问器⽅方法• BNRItem.h
• demo
@property(nonatomic) BNRItem *containedItem;@property(nonatomic) BNRItem *container;
@property(nonatomic) NSString *itemName;@property(nonatomic) NSString *serialNumber;@property(nonatomic) int valueInDollars;@property(nonatomic) NSDate *dateCreated;
不幸的是nonatomic 不是默认值,因此每次都要显式声明 property 是 nonatomic 的。
property 的第⼆二个 attribute
• 2 个 options
• readwrite:声明⼀一个 setter 和 getter
• readonly:只声明⼀一个 getter
•默认是:readwrite
•@property(nonatomic, readonly) NSDate *dateCreated;
@property(nonatomic) BNRItem *containedItem;@property(nonatomic) BNRItem *container;
@property(nonatomic) NSString *itemName;@property(nonatomic) NSString *serialNumber;@property(nonatomic) int valueInDollars;@property(nonatomic, readonly) NSDate *dateCreated;
property 的 后⼀一个 attribute
•描述内存管理• 常⻅见的option:实例变量到它指向的对象是否有强引⽤用或弱引⽤用
•默认的 option: assign,为像 valueInDollars 这样的不指向⼀一个对象的 property 准备的。• container: weak
•其他: strong@property(nonatomic, strong) BNRItem *containedItem;@property(nonatomic, weak) BNRItem *container;
@property(nonatomic, strong) NSString *itemName;@property(nonatomic, strong) NSString *serialNumber;@property(nonatomic) int valueInDollars;@property(nonatomic, readonly, strong) NSDate *dateCreated;
合成属性• synthesize: 为 accessors ⽅方法⽣生成代码
@synthesize itemName;
- (void)setItemName:(NSString *)str{ itemName = str;}
- (NSString *)itemName{ return itemName;}
•除了使⽤用 property 来声明访问器⽅方法以外,还可以 synthesize ⼀一个 property 在实现⽂文件中为 accessor
BNRItem.m 中的 Synthesize
•可以在⼀一⾏行或多⾏行中 synthesize properties
@synthesize itemName;@synthesize container, containedItem, serialNumber, valueInDollars, dateCreated;
在 accessor 中执⾏行额外⼯工作的情况
•我们增加的任意实现会覆盖 synthesize 的版本 :)
•⼀一般我们都会 synthesize 我们在头⽂文件中声明的 property,除⾮非 getter 和 setter 都有附加⾏行为。
- (void)setContainedItem:(BNRItem *)item{ containedItem = item; // 设置被包含的 item 的时候,同时要给该 item 设置⼀一个包含它的容器 [item setContainer:self];}
- (void)setContainedItem:(BNRItem *)item{ containedItem = item; }
实例变量和属性•更进⼀一步的代码清晰化•默认:synthesized property 将会访问同名的实例变量
•举例:itemName 属性访问 itemName 实例变量• itemName ⽅方法返回 itemName 实例变量的值
• setItemName ⽅方法改变 itemName 实例变量
•如果不存在和 synthesized property 的名字相匹配的实例变量呢?• 会⾃自动创建⼀一个
•所以既声明实例变量⼜又 synthesizing property 是冗余的。可以把实例变量声明包括⼤大括号都删除掉
复制•注意:两个指向 NSString 实例的属性
•具有mutable subclass 的类:NSString ,NSArray
•⽣生成⼀一个对象的拷⻉贝并指向,⽐比指向⼀一个可能已经有了其他 owner 的现有对象要安全些
•使⽤用 copy 属性选项替换 strong
NSString Copying
@property(nonatomic, strong) BNRItem *containedItem;@property(nonatomic, weak) BNRItem *container;
@property(nonatomic, copy) NSString *itemName;@property(nonatomic, copy) NSString *serialNumber;@property(nonatomic) int valueInDollars;@property(nonatomic, readonly, strong) NSDate *dateCreated;
NSMutableString *mutableString = [[NSMutableString alloc] init];BNRItem *item = [[BNRItem alloc] initWithItemName:mutableString valueInDollars:5 serialNumber:@"4F2W7R"];
- (void)setItemName:(NSString *)str{ itemName = [str copy];}
•原有的字符串没有以任何形式被更改,没有获得也没有失去 owner ,数据也不会被更改。
点号语法•虽然可⽤用,但是不建议使⽤用,避免 confusing
// 下⾯面的两⾏行是完全相等的int value = [item valueInDollars];int value = item.valueInDollars; [item setValueInDollars:5];item.valueInDollars = 5;