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:
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 ;
}
打印结果:
-- ::04.537 RandomItems[:] Backpack (): Worth $,recorded on -- :: +
-- ::04.537 RandomItems[:] Calculator (): Worth $,recorded on -- :: +
Program ended with exit code:
可见,并没有打印出释放的信息。对此我们可以这么理解:当执行 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:
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编程编写)的更多相关文章
- iOS 下ARC的内存管理机制
本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇 ...
- iOS 非ARC基本内存管理系列 -手把手教你ARC——iOS/Mac开发ARC入门和使用(转)
手把手教你ARC——iOS/Mac开发ARC入门和使用 Revolution of Objective-c 本文部分实例取自iOS 5 Toturail一书中关于ARC的教程和公开内容,仅用于技术交流 ...
- iOS 非ARC基本内存管理系列 2-多对象内存管理(3) 利用@property来自动管理内存
iOS 基本内存管理-多对象内存管理(2)中可以看到涉及到对象的引用都要手动管理内存:每个对象都需要写如下代码 // 1.对要传入的"新车"对象car和目前Person类对象所拥有 ...
- iOS 非ARC基本内存管理系列 5-autorelease方法使用总结
autorelase:可以将对象交给自动释放池中,释放池销毁的时候对里面的对象做一次release操作代码如下 @autoreleasepool { Person *person = [[[Perso ...
- iOS 非ARC基本内存管理系列 2-多对象内存管理(2)
/* 多对象内存管理: 以人拥有车为例涉及到@property底层set方法管理内存的实现 注意:人在换车的时候要进行当前传入的车和人所拥有的车进行判断 */ /******************* ...
- iOS 非ARC基本内存管理系列 1-引用计数器
1.什么是内存管理 移动设备的内存极其有限,每个app所能占用的内存是有限制的 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间.比如回收一些不需要使用的对象.变量 ...
- IOS 非ARC开发内存管理的几条规则
关于ios内存管理.在开发过程中,内存管理很重要,我简单说明一下. 1.正确用法 UIView *v = [[UIView alloc] init]; //分配后引用计数为1 [self.view a ...
- iOS 非ARC基本内存管理系列总结6 -设计微博模型
设计简单的微博模型:用User类和Status类来模拟实现 在非ARC机制下有两种方式,两者没有太大的区别之所以写了两种只是为了方便学习和对比两种写法! 第一种:没有使用atuorelease和自动释 ...
- iOS 非ARC基本内存管理系列 4-autorelease方法和@autoreleasepool
1.autorelease 基本用法 对象执行autorelease方法时会将对象添加到自动释放池中 当自动释放池销毁时自动释放池中所有对象作release操作 对象执行autorelease方法后自 ...
随机推荐
- IOS第三天-新浪微博 - 版本新特性,OAuth授权认证
*********版本新特性 #import "HWNewfeatureViewController.h" #import "HWTabBarViewController ...
- ABP理论学习之MVC控制器(新增)
返回总目录 本篇目录 介绍 AbpController基类 本地化 异常处理 响应结果的包装 审计日志 授权 工作单元 其他 介绍 ABP通过Abp.Web.Mvc nuget包集成了ASP.NET ...
- SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因
原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...
- AutoMapper使用手册(一)
阅读目录 1. 介绍 2. 基本使用 3. 自动分割映射(Flattening) 4. 自定义字段映射(Projection) 5. 验证配置(Configuration validation) 介绍 ...
- 【Java并发编程实战】-----“J.U.C”:Semaphore
信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...
- Three.js + HTML5 Audio API 打造3D音乐频谱,Let’s ROCK!
继续玩味之前写的音乐频谱作品,将原来在Canvas标签上的 作图利用Three.js让它通过WebGL呈现,这样就打造出了一个全立体感的频谱效果了. 项目详情及源码 项目GitHub地址:https: ...
- spring事务管理器设计思想(一)
在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...
- appledoc 使用brew命令安装使用
appledoc --project-name yushuyi12345677 --project-company "xiaoyu123" --company-id aaaa -- ...
- Atitit 深入理解耦合Coupling的原理与attilax总结
Atitit 深入理解耦合Coupling的原理与attilax总结 耦合是指两个或两个以上的电路元件或电网络等的输入与输出之间存在紧密配合与相互影响,并通过相互作用从一侧向另一侧传输能量的现 ...
- javascript命名规范
javascript命名规范 3.命名 命名的方法通常有以下几类: a).命名法说明 1).camel命名法,形如thisIsAnApple 2).pascal命名法,形如ThisIsAnApple ...