首页 > 代码库 > iOS通过ARC管理内存(内容根据iOS编程编写)
iOS通过ARC管理内存(内容根据iOS编程编写)
- 栈
当程序执行某个方法(或函数)时,会从内存中一个叫栈的区域分配一块内存空间,这块内存空间我们叫帧。帧负责保护程序在方法内声明的变量的值。在方法内声明的变量我们称之为局部变量。
当我们的程序开始启动,作为程序的入口main函数,他的帧会被保存在栈的地步。当main调用另一个方法时,这个方法会被压入栈的顶部。被调用的方法还会调用其他的方法,这样一直调用,就会形成一个帧序列。当调用的方法执行结束的时候,程序会将其帧从栈顶“弹出”并释放响应的内存。
所以栈的内存形式是先进后出。
- 堆
堆是值内存中的另一块区域,是和栈分开的。堆中包含了大量无序的活动对象,需要通过指针来保存这些对象在堆中的地址。当应用向某个类发送 alloc 消息时,系统会从堆中分配出一块内存,其大小为对象的全部的实例变量大小。
iOS应用在启动和运行时会持续创建需要的对象,如果堆的空间是无限的,则可以随意创建所需的对象。但是可惜,我们可用应用支配的内存空间是很有限的。因此,当应用不再需要某些对象时,就要将其释放掉。释放掉的对象可以将其占用的内存归还给堆,使之能够重新使用。最终要的是,我们要时刻避免释放应用正在使用的对象。
- 指针变量与对象所有权
指针变量暗含了对其所指向的对象的所有权。
- 当某个方法(或函数)有一个指向某个对象的局部指针时,可以成该变量拥有该变量所指向的对象。
- 当某个对象有一个指向其他对象的实验变量时,可以称该对象拥有该实例变量所指向的对象。
- 如果某个对象没有拥有者,就应该将其释放掉。没有拥有者,程序是无法向其发送消息的。保留这样的对象就会造成内存泄漏
- 如果某个对象有一个或者多个拥有者,就必须保留下来,不能被释放。如果释放了某个对象,但是其他对象或者方法仍然有指向该对象的指针,那么向该指针指向的对象发送消息就会使应用崩溃。指向不存在的对象的指针称为空指针
那些情况是使对象失去拥有者
- 当程序修改某个指向特定对象的变量并将其指向另一个对象的时候就会失去拥有者。
- 当程序将某个指向特定对象的变量设置为nil的时候。
- 当程序释放对象的某个拥有者的时候
- 当从 collection 类中删除对象的时候。
- 强引用与弱引用
只要指针变量指向某个对象,那么相应的对象就会多一个拥有者,并且不会被程序释放。这种指针特性称为强引用。
程序也可以选择让指针变量不影响其指向对象的拥有者个数。这种不会改变对象拥有者个数的指针特性称之为弱引用。
弱引用非常适合解决一种称为强引用循环的内存管理问题。当两个或者以上的对象相互之间有强引用特性的指针关联的时候,就会产生强引用循环。这种循环会导致内存泄漏。因为这种循环中程序无法通过ARC机制来释放内存。
在 RandomItems 添加一处强引用循环,来解释如何解决此类问题。
#import <Foundation/Foundation.h>@interface JXItem : NSObject{ NSString *_itemName; NSString *_serialNumber; int _valueInDollars; NSDate *_dateCreated; // 这里添加让JXItem对象能够保存另一个JXItem对象。 JXItem *_containedItem; JXItem *_container;}// 初始化方法- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;- (instancetype)initWithItemName:(NSString *)name;- (void)setContainedItem:(JXItem *)item;- (JXItem *)containedItem;- (void)setContainer:(JXItem *)item;- (JXItem *)container;// 存方法- (void)setItemName:(NSString *)str;// 取方法- (NSString *)itemName;- (void)setSerialNumber:(NSString *)str;- (NSString *)serialNumber;- (void)setValueInDollars:(int)v;- (int)valueInDollars;- (NSDate *)dateCreated;@end
实现方法中:
#import "JXItem.h"@implementation JXItem- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber { // 调用父类的初始化方法 self = [super init]; // 判断父类的指定初始化方法是否是成功创建 if (self) { // 为实例变量设置初始值 _itemName = name; _serialNumber = sNumber; _valueInDollars = value; // 设置 _dateCreated 的值为系统当前时间 // 因为我们没有为该实例变量设置 set 方法来从外部获取 (只是一个只读属性) _dateCreated = [NSDate date]; } // 返回初始化后的对象的新地址 return self; }- (void)setContainedItem:(JXItem *)item { _containedItem = item; // 将item加入容纳他的JXItem对象时,会将它的container实例变量指向容纳它的对象 item.container = self;}- (JXItem *)containedItem { return _containedItem;}- (void)setContainer:(JXItem *)item { _container = item;}- (JXItem *)container { return _container;}- (instancetype)initWithItemName:(NSString *)name { return [self initWithItemName:name valueInDollars:0 serialNumber:@""];}// 默认的初始化方法,调用自定义的初始化方法,并将之出入一个默认值- (instancetype)init { return [self initWithItemName:@"Item"];}- (void)setItemName:(NSString *)str { _itemName = str;}- (NSString *)itemName { return _itemName;}- (void)setSerialNumber:(NSString *)str { _serialNumber = str;}- (NSString *)serialNumber { return _serialNumber;}- (void)setValueInDollars:(int)v { _valueInDollars = v;}- (int)valueInDollars { return _valueInDollars;}- (NSDate *)dateCreated { return _dateCreated;}- (NSString *)description { NSString * descriptionString = [[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d,recorded on %@",self.itemName,self.serialNumber,self.valueInDollars,self.dateCreated]; return descriptionString;}@end
在 main.m 实现方法
#import <Foundation/Foundation.h>#import "JXItem.h"int main(int argc, const char * argv[]) { @autoreleasepool { // 创建一个NSMutableArray对象,并用items变量保存该对象的地址 NSMutableArray * items = [[NSMutableArray alloc] init]; JXItem * backpack = [[JXItem alloc] initWithItemName:@"Backpack"]; [items addObject:backpack]; JXItem * calculator = [[JXItem alloc] initWithItemName:@"Calculator"]; [items addObject:calculator]; backpack.containedItem = calculator; backpack = nil; calculator = nil; // 使用快速枚举法来遍历 for (NSString * item in items) { NSLog(@"%@",item); } } return 0;}
打印结果:
2016-09-09 00:47:04.537 RandomItems[27531:2815858] Backpack (): Worth $0,recorded on 2016-09-08 16:47:04 +00002016-09-09 00:47:04.537 RandomItems[27531:2815858] Calculator (): Worth $0,recorded on 2016-09-08 16:47:04 +0000Program ended with exit code: 0
可见,并没有打印出释放的信息。对此我们可以这么理解:当执行 backpack.containedItem = calculator; 其实就是执行了 JXItem 中的set方法。也就是 [backpack setContainer:calculator]; 我们可以查看在类中我们自定义的方法,可以看到这时候 backpack 就是self,此时的self拥有指向 calculator 的指针,也就是 calculator 的拥有方;同时,我们在实现方法中 item.container = self; 此时的 item 就是 calculator ,所有就造成了循环引用。
要解决这种循环引用问题,我们就需要在新创建的两个对象之间的任意一个指针改为弱引用特性。在决定哪个指针改为弱引用之前,我们可以先为存在强引用循环问题的多个对象决定响应的父-子关系。我们可以让父对象拥有子对象,并确保子对象不会拥有父对象。定义形式为: __weak JXItem *_container;
- 属性
属性是用来简化声明变量和存储方法。申明方式: @property NSString * itemName;
#import <Foundation/Foundation.h>@interface JXItem : NSObject@property NSString * itemName;@property NSString * serialNumber;@property int valueInDollars;@property NSDate * dateCreated;@property JXItem * containedItem;@property JXItem * container;// 初始化方法- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;- (instancetype)initWithItemName:(NSString *)name;- (void)setContainedItem:(JXItem *)item;- (JXItem *)containedItem;- (void)setContainer:(JXItem *)item;- (JXItem *)container;// 存方法- (void)setItemName:(NSString *)str;// 取方法- (NSString *)itemName;- (void)setSerialNumber:(NSString *)str;- (NSString *)serialNumber;- (void)setValueInDollars:(int)v;- (int)valueInDollars;- (NSDate *)dateCreated;@end
实现方法:属性的名字是实例变量的名字去掉下划线,编译器会根据属性生成实例变量时会自动在变量名前加上下划线,同时还能自动生成相应的存取方法。
#import "JXItem.h"@implementation JXItem- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber { // 调用父类的初始化方法 self = [super init]; // 判断父类的指定初始化方法是否是成功创建 if (self) { // 为实例变量设置初始值 _itemName = name; _serialNumber = sNumber; _valueInDollars = value; // 设置 _dateCreated 的值为系统当前时间 // 因为我们没有为该实例变量设置 set 方法来从外部获取 (只是一个只读属性) _dateCreated = [NSDate date]; } // 返回初始化后的对象的新地址 return self; }- (void)setContainedItem:(JXItem *)item { _containedItem = item; // 将item加入容纳他的JXItem对象时,会将它的container实例变量指向容纳它的对象 item.container = self;}- (JXItem *)containedItem { return _containedItem;}- (void)setContainer:(JXItem *)item { _container = item;}- (JXItem *)container { return _container;}- (instancetype)initWithItemName:(NSString *)name { return [self initWithItemName:name valueInDollars:0 serialNumber:@""];}// 默认的初始化方法,调用自定义的初始化方法,并将之出入一个默认值- (instancetype)init { return [self initWithItemName:@"Item"];}- (void)setItemName:(NSString *)str { _itemName = str;}- (NSString *)itemName { return _itemName;}- (void)setSerialNumber:(NSString *)str { _serialNumber = str;}- (NSString *)serialNumber { return _serialNumber;}- (void)setValueInDollars:(int)v { _valueInDollars = v;}- (int)valueInDollars { return _valueInDollars;}- (NSDate *)dateCreated { return _dateCreated;}- (NSString *)description { NSString * descriptionString = [[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d,recorded on %@",self.itemName,self.serialNumber,self.valueInDollars,self.dateCreated]; return descriptionString;}@end
属性的特性
任何属性都可以有一组特性,用于描述响应存取方法的行为。这些特性需要写在小括号里,并跟在 @property 指令之后。例如: @property (nonatomic,copy) NSString * itemName; 任何属性都有三个特性,每个特性都有多种不同的可选类型。
属性的特性-多线程特性
此特性有两种可选类型: nonatomic 和 atomic 。前一种是非原子访问,不会加线程锁,后一种相反,但是线程锁虽然是绝对安全的,但是效率很低,一般不推荐使用。默认是 原子访问。
#import <Foundation/Foundation.h>@interface JXItem : NSObject@property (nonatomic) NSString * itemName;@property (nonatomic) NSString * serialNumber;@property (nonatomic) int valueInDollars;@property (nonatomic) NSDate * dateCreated;@property (nonatomic) JXItem * containedItem;@property (nonatomic) JXItem * container;// 初始化方法- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;- (instancetype)initWithItemName:(NSString *)name;@end
属性的特性-读/写特性
此特性也有两种可选类型: readwrite 和 readonly 。编译器默认会是 readwrite 特性的属性生成存取方法。但是如果是 readonly 属性,只会生成取方法。
#import <Foundation/Foundation.h>@interface JXItem : NSObject@property (nonatomic) NSString * itemName;@property (nonatomic) NSString * serialNumber;@property (nonatomic) int valueInDollars;@property (nonatomic,readonly) NSDate * dateCreated;@property (nonatomic) JXItem * containedItem;@property (nonatomic) JXItem * container;// 初始化方法- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;- (instancetype)initWithItemName:(NSString *)name;@end
属性的特性-内存管理特性
此特性有四种可选类型: strong (默认属性), weak , copy , unsafe_unretained 。对于不指向任何对象的属性,也就是基本数据类型,我们不需要做内存管理,这时候我们应该选用 unsafe_unretained ,他表示存取方法会直接为实例变量赋值。在 ARC 之前使用的是 assign ,现在我们仍旧可以使用这个属性。
通常情况下,但给某个属性是指向其他对象的指针,而且这个属性的类有可修改的子类( NSString/NSMutableString , NSArray/NSMutableArray )这时我们应该将其属性内存管理设置为 copy 。
#import <Foundation/Foundation.h>@interface JXItem : NSObject@property (nonatomic,copy) NSString * itemName;@property (nonatomic,copy) NSString * serialNumber;@property (nonatomic) int valueInDollars;@property (nonatomic,readonly,strong) NSDate * dateCreated;@property (nonatomic,strong) JXItem * containedItem;@property (nonatomic,weak) JXItem * container;// 初始化方法- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;- (instancetype)initWithItemName:(NSString *)name;@end
更改为 copy 特性之后,其属性的存方法可能类似于如下代码:
- (void)setItemName:(NSString *)itemName { _itemName = [itemName copy];}
这段代码没有将传入的值 itemName 直接赋值给实例变量 _itemName ,而是先向 itemName 发送了 copy 信息。该对象的 copy 方法会返回一个新的 NSString 对象。我们这么做的原因就是:如果属性指向的对象的类有可修改的子类,那么该属性可能会指向可修改的子类对象,同时,该对象可能会被其他拥有者修改。因此,我们在操作的时候最好先复制该对象,然后再将属性指向复制后的对象。但是在 copy 方法中, NSString 对象是不会发生任何变化的,所以我们一般只有对可变对象设置为 copy ,复制不可变对象就是浪费空间而已。
自定义属性的存取方法
默认情况下回自动生成存取方法,而且非常简单;
- (void)setContainedItem:(JXItem *)item { _containedItem = item;}- (JXItem *)containedItem { return _containedItem;}
属性自定义添加的存取方法我们可以直接拿来用,同时我们也可以自定义存取方法;当我们自定义的存取方法之后,编译器就不会为我们创建默认的存取方法了。
- (void)setContainedItem:(JXItem *)containedItem { _containedItem = containedItem; self.containedItem.container = self;}
iOS通过ARC管理内存(内容根据iOS编程编写)