Top Banner
使 ARC 管理内存 范圣刚,[email protected] www.tfan.org
45

03 Managing Memory with ARC

Dec 05, 2014

Download

Technology

Tom Fan

使用 ARC 管理内存,强引用,ruo'yin'yon
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 2: 03 Managing Memory with ARC

•学习在 iOS 中内存是如何被管理的,以及⾃自动引⽤用计数背后的概念

Page 3: 03 Managing Memory with ARC

堆(The Heap)

•所有的Objective-C 对象都存在堆中

• alloc

•memory chunk -> 包括对象实例变量需要的空间

Page 4: 03 Managing Memory with ARC

对象⼤大⼩小•NSDate

• double类型 -> 存储从⼀一个固定参考点开始过去的秒数

• isa 指针 -> 从 NSObject 继承

• double + pointer = 8 + 4 = 12 字节

• BNRItem

•四个指针(isa, itemName, serialNumber, dateCreated)

•⼀一个int (valueInDollars)

• 4*4 + 4 = 20 字节

Page 5: 03 Managing Memory with ARC

BNRItem 和 NSDate 实例的字节数

Page 6: 03 Managing Memory with ARC

栈(The Stack)

•本地变量,也就是在⽅方法内声明的变量的值都存储在栈内•⽅方法执⾏行 -> 分配内存块(frame)

• pushed on & popped off

Page 7: 03 Managing Memory with ARC

指针变量和对象所有权•指针变量传递它们指向的对象的所有权•⽅方法的本地变量指向⼀一个对象,⽅方法拥有(own)指向的对象

•对象有指向另⼀一个对象的实例变量,我们说包含指针的对象 own 被指向的对象

Page 8: 03 Managing Memory with ARC

RandomPossessions 中的对象和指针• main ⽅方法中的 items 本地变量指向 NSMutableArray,NSMutableArray 属于

main 函数

• NSMutableArray -> BNRItems

• BNRItem -> 实例变量指向的对象

Page 9: 03 Managing Memory with ARC

内存管理•有限的内存•不再使⽤用的对象要释放,还需要的不要销毁•对象所有者的理念帮助我们确定⼀一个对象是否应该被销毁:•没有所有者的对象应该被释放(⽆无法被发送消息),内存泄露。

•⾄至少拥有⼀一个所有者的对象不应该被释放。(过早释放)

Page 10: 03 Managing Memory with ARC

新建 Quiz 项⺫⽬目

Page 11: 03 Managing Memory with ARC

对象是如何失去所有者的?•指向对象的变量更改成指向另外⼀一个对象•指向对象的变量被设置成 nil

•指向对象的变量⾃自⾝身被销毁

Page 12: 03 Managing Memory with ARC

指针更改当 itemName 的值从“Rusty Spork”字符串的地址更改成指向“Shiny Spork”字符串的地址时,“Rusty Spork”字符串就失去了所有者。

Page 13: 03 Managing Memory with ARC

指针设成 nil

• serialNumber = nil;

Page 14: 03 Managing Memory with ARC

指针变量⾃自⾝身被销毁•实例变量,在堆⾥里作为对象的⼀一部分•对象销毁,实例变量也被销毁•实例变量指向的对象就失去了所有者•本地变量,存在于⽅方法的 frame 中•⽅方法执⾏行完毕,frame 被弹出堆栈

•本地变量指向的对象就失去了所有者

Page 15: 03 Managing Memory with ARC

在集合对象中的对象•位于数组中的对象,其拥有者是集合对象• [items removeObject:p];

•被移除的对象(p)失去所有者

Page 16: 03 Managing Memory with ARC

连锁反应•⼀一个对象可以拥有其他对象,其他对象⼜又可以拥有对象

•单个对象的析构可能会引起(失去所有者,对象析构,释放内存)⼀一系列连锁反应

Page 17: 03 Managing Memory with ARC

RandomPossessions 的变量和指针

Page 18: 03 Managing Memory with ARC

RandomPossession 的连锁反应1.在 main.m 中,打印完 BNRItem 之后,我们把

items = nil;

2.数组失去所有者被销毁3.NSMutableArray 中指向 BNRItem 的指针也被销毁4.BNRItem失去所有者,也被销毁5.BNRItem 实例变量被销毁,实例变量指向的对象失去所有者,也被销毁

Page 19: 03 Managing Memory with ARC

重写 dealloc// BNRItem.m- (void)dealloc{ NSLog(@"被销毁: %@", self);}

// main.m for (BNRItem *item in items) { NSLog(@"%@", item); }

NSLog(@"设置 items 为 nil ..."); items = nil;

Page 20: 03 Managing Memory with ARC

强引⽤用和弱引⽤用•任何时候只要⼀一个对象的地址被保存在⼀一个指针变量中,对象就有它的拥有者,并且保持存活,这就叫做 strong reference

•有时⼀一个变量并不拥有它指向的对象,称作 weak reference

• retain cycle: 当两个或者更多对象互相之间具有 强引⽤用,当两个对象互相 own 对⽅方时,永远不会通过 ARC 销毁。

Page 21: 03 Managing Memory with ARC

Retail Cycle Demo

•让 BNRItem 可以持有另外⼀一个 BNRItem

•同时 BNRItem 也可以知道被谁所持有

•步骤:• BNRItem.h 中增加两个实例变量和 accessors

• BNRItem.m 中实现 accessors

•修改 main.m, 把原来的随机⽣生成 item 去掉,改成⽣生成两个 item,⼀一个包含另外⼀一个。

Page 22: 03 Managing Memory with ARC

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;

Page 23: 03 Managing Memory with ARC

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

Page 24: 03 Managing Memory with ARC

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;

Page 25: 03 Managing Memory with ARC

带 retain cycle 的 RandomPossessions

Page 26: 03 Managing Memory with ARC

带 retain cycle 的 运⾏行结果

Page 27: 03 Managing Memory with ARC

⼀一个 retain cycle

•两个 BNRItem 不会被销毁,同时它们实例变量指向的对象也⽆无法被销毁。

Page 28: 03 Managing Memory with ARC

谁应该被设成弱引⽤用?•两个 BNRItem 之中的⼀一个要设成 weak,which?

•⽗父⼦子关系

• 每⼀一个 retain cycle 都可以被分解成⽗父⼦子关系

• parent 通常保存⼀一个强引⽤用到它的 child

•所以如果 child 也需要⼀一个到 parent 的指针,这个指针必须是弱引⽤用才可以避免 retain cycle

• __weak BNRItem *container;

• parent’s parent

• Xcode Leaks ⼯工具

Page 29: 03 Managing Memory with ARC

弱引⽤用销毁的⾃自动检测•weak reference 的有趣属性

Page 30: 03 Managing Memory with ARC

__unsafe_unretained

•和弱引⽤用⼀一样,不拥有它指向的对象的所有权•和弱引⽤用不⼀一样的是,不⾃自动设成 nil

•向后兼容的需要•尽量使⽤用 __weak 替代 __unsafe_unretained

Page 31: 03 Managing Memory with ARC

避免了 retain cycle 的 RandomPosessions

Page 32: 03 Managing Memory with ARC

属性•实例变量 -> 声明和实现⼀一对 accessor ⽅方法

•使⽤用 properties 替代 -> 简化输⼊入,代码更加清晰易读

Page 33: 03 Managing Memory with ARC

声明属性•属性在类的接⼝口中声明,形式:• @peoperty NSString *itemName;

•声明⼀一个属性,相当于给实例变量隐式声明了⼀一个同名的 setter 和 getter ⽅方法。• - (void)setItemName:(NSString *)str;

• - (NSString *)itemName;

Page 34: 03 Managing Memory with ARC

property 的 attributes

•属性的属性 -> accessors ⽅方法的⾏行为

•在 @property 指⽰示符后⾯面的括号中声明•@property (nonatomic, readwrite, strong) NSString

*itemName;

•共有三个 peoperty attributes, 每个 attributes 有两个或三个options,其中⼀一个是 default 所以不需要显式声明。

Page 35: 03 Managing Memory with ARC

property 的第⼀一个 attribute

• 2 个选项•nonatomic

• atomic

Page 36: 03 Managing Memory with ARC

使⽤用属性替换访问器⽅方法• 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 的。

Page 37: 03 Managing Memory with ARC

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;

Page 38: 03 Managing Memory with ARC

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;

Page 39: 03 Managing Memory with ARC

合成属性• synthesize: 为 accessors ⽅方法⽣生成代码

@synthesize itemName;

- (void)setItemName:(NSString *)str{ itemName = str;}

- (NSString *)itemName{ return itemName;}

•除了使⽤用 property 来声明访问器⽅方法以外,还可以 synthesize ⼀一个 property 在实现⽂文件中为 accessor

Page 40: 03 Managing Memory with ARC

BNRItem.m 中的 Synthesize

•可以在⼀一⾏行或多⾏行中 synthesize properties

@synthesize itemName;@synthesize container, containedItem, serialNumber, valueInDollars, dateCreated;

Page 41: 03 Managing Memory with ARC

在 accessor 中执⾏行额外⼯工作的情况

•我们增加的任意实现会覆盖 synthesize 的版本 :)

•⼀一般我们都会 synthesize 我们在头⽂文件中声明的 property,除⾮非 getter 和 setter 都有附加⾏行为。

- (void)setContainedItem:(BNRItem *)item{ containedItem = item; // 设置被包含的 item 的时候,同时要给该 item 设置⼀一个包含它的容器 [item setContainer:self];}

- (void)setContainedItem:(BNRItem *)item{ containedItem = item; }

Page 42: 03 Managing Memory with ARC

实例变量和属性•更进⼀一步的代码清晰化•默认:synthesized property 将会访问同名的实例变量

•举例:itemName 属性访问 itemName 实例变量• itemName ⽅方法返回 itemName 实例变量的值

• setItemName ⽅方法改变 itemName 实例变量

•如果不存在和 synthesized property 的名字相匹配的实例变量呢?• 会⾃自动创建⼀一个

•所以既声明实例变量⼜又 synthesizing property 是冗余的。可以把实例变量声明包括⼤大括号都删除掉

Page 43: 03 Managing Memory with ARC

复制•注意:两个指向 NSString 实例的属性

•具有mutable subclass 的类:NSString ,NSArray

•⽣生成⼀一个对象的拷⻉贝并指向,⽐比指向⼀一个可能已经有了其他 owner 的现有对象要安全些

•使⽤用 copy 属性选项替换 strong

Page 44: 03 Managing Memory with ARC

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 ,数据也不会被更改。

Page 45: 03 Managing Memory with ARC

点号语法•虽然可⽤用,但是不建议使⽤用,避免 confusing

// 下⾯面的两⾏行是完全相等的int value = [item valueInDollars];int value = item.valueInDollars; [item setValueInDollars:5];item.valueInDollars = 5;