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

  当我们的程序开始启动,作为程序的入口main函数,他的帧会被保存在栈的地步。当main调用另一个方法时,这个方法会被压入栈的顶部。被调用的方法还会调用其他的方法,这样一直调用,就会形成一个帧序列。当调用的方法执行结束的时候,程序会将其帧从栈顶“弹出”并释放响应的内存。

  所以栈的内存形式是先进后出。

  堆是值内存中的另一块区域,是和栈分开的。堆中包含了大量无序的活动对象,需要通过指针来保存这些对象在堆中的地址。当应用向某个类发送 alloc 消息时,系统会从堆中分配出一块内存,其大小为对象的全部的实例变量大小。

  iOS应用在启动和运行时会持续创建需要的对象,如果堆的空间是无限的,则可以随意创建所需的对象。但是可惜,我们可用应用支配的内存空间是很有限的。因此,当应用不再需要某些对象时,就要将其释放掉。释放掉的对象可以将其占用的内存归还给堆,使之能够重新使用。最终要的是,我们要时刻避免释放应用正在使用的对象。

  • 指针变量与对象所有权

  指针变量暗含了对其所指向的对象的所有权

  1. 当某个方法(或函数)有一个指向某个对象的局部指针时,可以成该变量拥有该变量所指向的对象。
  2. 当某个对象有一个指向其他对象的实验变量时,可以称该对象拥有该实例变量所指向的对象。
  3. 如果某个对象没有拥有者,就应该将其释放掉。没有拥有者,程序是无法向其发送消息的。保留这样的对象就会造成内存泄漏
  4. 如果某个对象有一个或者多个拥有者,就必须保留下来,不能被释放。如果释放了某个对象,但是其他对象或者方法仍然有指向该对象的指针,那么向该指针指向的对象发送消息就会使应用崩溃。指向不存在的对象的指针称为空指针

  

  那些情况是使对象失去拥有者

  1. 当程序修改某个指向特定对象的变量并将其指向另一个对象的时候就会失去拥有者。
  2. 当程序将某个指向特定对象的变量设置为nil的时候。
  3. 当程序释放对象的某个拥有者的时候
  4. 当从 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编程编写)的更多相关文章

  1. iOS 下ARC的内存管理机制

    本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇 ...

  2. iOS 非ARC基本内存管理系列 -手把手教你ARC——iOS/Mac开发ARC入门和使用(转)

    手把手教你ARC——iOS/Mac开发ARC入门和使用 Revolution of Objective-c 本文部分实例取自iOS 5 Toturail一书中关于ARC的教程和公开内容,仅用于技术交流 ...

  3. iOS 非ARC基本内存管理系列 2-多对象内存管理(3) 利用@property来自动管理内存

    iOS 基本内存管理-多对象内存管理(2)中可以看到涉及到对象的引用都要手动管理内存:每个对象都需要写如下代码 // 1.对要传入的"新车"对象car和目前Person类对象所拥有 ...

  4. iOS 非ARC基本内存管理系列 5-autorelease方法使用总结

    autorelase:可以将对象交给自动释放池中,释放池销毁的时候对里面的对象做一次release操作代码如下 @autoreleasepool { Person *person = [[[Perso ...

  5. iOS 非ARC基本内存管理系列 2-多对象内存管理(2)

    /* 多对象内存管理: 以人拥有车为例涉及到@property底层set方法管理内存的实现 注意:人在换车的时候要进行当前传入的车和人所拥有的车进行判断 */ /******************* ...

  6. iOS 非ARC基本内存管理系列 1-引用计数器

    1.什么是内存管理 移动设备的内存极其有限,每个app所能占用的内存是有限制的 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间.比如回收一些不需要使用的对象.变量 ...

  7. IOS 非ARC开发内存管理的几条规则

    关于ios内存管理.在开发过程中,内存管理很重要,我简单说明一下. 1.正确用法 UIView *v = [[UIView alloc] init]; //分配后引用计数为1 [self.view a ...

  8. iOS 非ARC基本内存管理系列总结6 -设计微博模型

    设计简单的微博模型:用User类和Status类来模拟实现 在非ARC机制下有两种方式,两者没有太大的区别之所以写了两种只是为了方便学习和对比两种写法! 第一种:没有使用atuorelease和自动释 ...

  9. iOS 非ARC基本内存管理系列 4-autorelease方法和@autoreleasepool

    1.autorelease 基本用法 对象执行autorelease方法时会将对象添加到自动释放池中 当自动释放池销毁时自动释放池中所有对象作release操作 对象执行autorelease方法后自 ...

随机推荐

  1. Windows驱动开发技术详解HelloWDM例子win7下无法安装

    HelloWDM例子编译完成之后,在win7下安装显示 查看setupapi.dev看到如下信息 这个C:\MyDriver_Check目录完全不是我指定的,我放到c盘根目录下 查看inf [Sour ...

  2. Hbuilder开发HTML5 APP之向导页制作

    研究了下,向导页的制作还是比较简单的,主要使用的是mui控件中的”图片轮播“组件,组件的标签写法手册中有,中间发现个有趣的东西,如果要作全屏,可以加个样式mui-fullscreen 滑动图片时会自动 ...

  3. .NET 4.5.1 预览版新特性

    上个月的微软Build大会上宣布了.NET 4.5.1的推出,Heydarian的这个演讲题为".NET开发中的新内容",涵盖了.NET Framework中一些重要的新特性. H ...

  4. Expert 诊断优化系列-------------针对重点语句调索引

    上一篇我们说了索引的重要性,一个索引不仅能让一条语句起飞,也能大量减少系统对CPU.内存.磁盘的依赖.我想上一篇中的例子可以说明了.给出上一篇和目录文链接: SQL SERVER全面优化------- ...

  5. 【转载】十步完全理解SQL

    很多程序员视 SQL 为洪水猛兽.SQL 是一种为数不多的声明性语言,它的运行方式完全不同于我们所熟知的命令行语言.面向对象的程序语言.甚至是函数语言(尽管有些人认为 SQL 语言也是一种函数式语言) ...

  6. 初识ASP.NET Core 1.0

    本文将对微软下一代ASP.NET框架做个概括性介绍,方便大家进一步熟悉该框架. 在介绍ASP.NET Core 1.0之前有必要澄清一些产品名称及版本号.ASP.NET Core1.0是微软下一代AS ...

  7. Hadoop学习笔记—10.Shuffle过程那点事儿

    一.回顾Reduce阶段三大步骤 在第四篇博文<初识MapReduce>中,我们认识了MapReduce的八大步骤,其中在Reduce阶段总共三个步骤,如下图所示: 其中,Step2.1就 ...

  8. 迷你MVVM框架 avalonjs 实现上的几个难点

    经过两个星期的性能优化,avalon终于实现在一个页面绑定达到上万个的时候不卡顿的目标(angular的限制是2000).现在稍作休息,总结一下avalon遇到的一些难题. 首先是如何监控的问题.所有 ...

  9. Azure PowerShell (2) 修改Azure订阅名称

    <Windows Azure Platform 系列文章目录> Update: 2016-01-11 笔者文档主要都是用Azure PowerShell 0.x版本来实现的,比如0.98版 ...

  10. 《Entity Framework 6 Recipes》中文翻译系列 (34) ------ 第六章 继承与建模高级应用之多条件与QueryView

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-10  创建一个多条件过滤 问题 你想使用多个条件为实体过滤表中的行. 解决方案 ...