概述

移动设备的内存极其有限,每个app所有占用的内存是有限的。当app所占用的内存比较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。

任何集成了NSObject的对象都需要手动进行内存管理。因为OC对象存放于堆里面。

引用计数器

  • 每一个OC对象都有内部有自己的引用计数器。该计数器占用4个字节。从字面可以理解为"多少人在使用这个对象"。
  • 当对象的引用计数器为0时。该对象会才会被释放。
  • 一个对象通过alloc、 copy 、new创建一个对象,该对象默认的引用计数器为1。

引用计数器的操作

* 给对象发送一条retain消息,对象的引用计数器+1
* 给对象发送一条release消息,对象的引用计数器-1
* 给对象发送一条retainCount消息,对获得当前对象的引用计数器值

dealloc方法

当一个对象的引用计数器值为0时,这个对象即将被销毁,器占用的内存被系统回收。对象即将被销毁前系统会自动给改对象发送一条dealloc消息。

通常重写对象的dealloc方法释放相关的资源。

* 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面

* dealloc不能直接调用

* 一旦对象被回收,它占用的内存就不再可用被称之为僵尸对象。指向僵尸对象的指针被称为野指针。给僵尸对象发消息会造坏访问报错。

多对象的内存管理

多个对象进行内存管理,并且对象之间是有联系的,那么管理就会变得比较复杂。

比如在内存中有一个聊天室对象,多个Person对象共同使用者聊天室对象。

对上面的实例我们如果想做好聊天室对象跟Person对象的内存管理。需要做到下面两点

* 只要有人在使用聊天室对象聊天室对象就不能销毁

* 没有人使用聊天室对象聊天室对象需要销毁

当有Person对象使用聊天室就需要对聊天室对象的引用计数器+1。且当Person对象销毁之前需要对使用的房间对象引用计数器-1,这样就可以保证以上两点。

因此可以这样定义一个Person类

#import <Foundation/Foundation.h>
#import "ChatRoom.h"
@interface Person : NSObject
{
ChatRoom *_cRoom;
}
- (void)setCRoom:(ChatRoom *)cRoom;
- (ChatRoom *)cRoom;
@end #import "Person.h" @implementation Person
- (void)setCRoom:(ChatRoom *)cRoom
{
if (_cRoom != cRoom) {
// 1.先对以前的对象计数器-1
[_cRoom release];
// 2.调用对象的retain返回对象本身
_cRoom = [_cRoom retain];
}
} - (ChatRoom *)cRoom
{
return _cRoom;
} - (void)dealloc
{
// 在Person对象即将销毁先对内部的聊天室对象计数器-1
[_cRoom release];
[super dealloc];
}
@end

Property修饰符

assign

默认Proerty修饰符为assig。比如当为Person声明一个成员属性@property int age; 编译器自动转换成@property(assign) int age;

retain

Proerty修饰符为retain在生成的set方法会自动生成set方法的内存管理代码

@property (retain) ChatRoom *cRoom;

retain修饰生成的set方法:

- (void)setCRoom:(ChatRoom *)cRoom
{
if (_cRoom != cRoom) {
// 1.先对以前的对象计数器-1
[_cRoom release];
// 2. 调用对象的retain返回对象本身
_cRoom = [_cRoom retain];
}
}

nonatomic与nonnull

nonatomic是保证set方法是线程安全的,nonnull则相反。iOS开发一般使用nonatomic性能高。

@class

@class主要用于简单的引用一个类。@class与#import有着本质的区别,#import是预处理指令在程序预处理时期对#import的文件进行拷贝,而@class只是告诉编译器@class后面引用的是一个类。

import有一个特点,只要引用的文件发生的变化,那么import就会重新拷贝一次。比如现在有三个类Person、 Car、 Wheel。Person.h文件import Car.h,Car.h文件import Wheel.h。一旦wheels Wheel.h发生变化,Car.h需要重新拷贝Wheel.h并且Person.h也重新拷贝Car.h。

@class具体使用

* 在.h文件中使用@class引用一个类

* 在.m文件中使用#import包含这个类的.h文件

@class其他使用场景

  • 对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类 这种嵌套包含的代码编译报错
  • 当使用@class在两个类相互声明,就不会出现编译报错。

retain的循环引用

现在有两个类Person与Car。Person中有个成员属性car,Car中有个成员属性person。

Person类

#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject @property (retain) Car *car;
@end #import "Person.h"
@implementation Person - (void)dealloc
{
[_car release]; [super dealloc];
NSLog(@"%s", __func__);
}
@end

Car类

#import <Foundation/Foundation.h>
@class Person;
@interface Car : NSObject @property (retain) Person *per;
@end #import "Car.h"
@implementation Car - (void)dealloc
{ [_per release]; [super dealloc]; NSLog(@"%s", __func__);
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool { Person *p = [[Person alloc] init];
Car *c = [[Car alloc] init]; // 人赋值一辆车对象
p.car = c;
// 车赋值人对象
c.per = p; [p release];
[c release];
}
return 0;
}

main.m代码执行完控制台并没有Person对象与Car对象dealloc方法的打印。因此内存两个对象并未销毁存在内存泄漏。下面画图展现main.m执行完毕内存的情况。

为什么执行p跟c的release方法,对象没有销毁。因为在给人对象赋值car对象的时候通过set方法会对car对象引用计数器+1,同理对car对象赋值人对象,人对象引用计数器也会+1。



分别执行两个对象release只是让对象应用计数器-1,因此两个对象都没有销毁。

那么我们可以尝试让其中一个类Property修饰符修改为assign。注意:修改为assign修饰一方dealloc千万不要在dealloc对对象属性进行release。否则会造成内存坏访问

此时的Person类

#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject
// 改为assign修饰
@property (assign) Car *car; @end #import "Person.h"
@implementation Person - (void)dealloc
{
// [_car release]; 此时Propery修饰符为assign不需要对car进行release
[super dealloc];
NSLog(@"%s", __func__);
}
@end

autorelease

通过上面讲解,OC多对象的内存管理主要有以下几点:

* 1.在外界通过alloc、copy、new关键字创建的对象需要释放的时候发送release消息

* 2.在set方法中对赋值的对象引用计数器+1,可以使用retain关键字自动生成set方法内存管理代码

* 3.在一个对象即将要销毁前在dealloc方法中对内部引用的对象进行release。

这里就会发现,我们需要时时刻刻关注对象什么时候需要销毁时发送release消息。接下来就可以通过autorelease的机制来解决手动编写大量的对象的release代码。不需要一直再关注对象什么时候需要release。只要可以访问到对象的作用域都可以使用对象。

autorelease是一种支持引用计数器的内存管理方式,只要给对象发送一条autorelease消息会将对象放到自动释放池中。当自动释放池销毁,会对池子中的所有对象发送一条release消息。autorelease操作对象的引用计数器。当一个对象发送release消息返回时对象的本身。

#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool { Person *p = [[Person alloc] init];
// 将对象p添加到autorelease释放池
p = [p autorelease]; // [p release]; 这里不需要在写release 否则坏访问
}// 自动释放池销毁会给对象中所有的对象发送一条release消息
return 0;
}

在iOS5.0之前autorelease的写法

int main(int argc, const char * argv[]) {

    // 创建一个自动释放池
NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; Person *p = [[Person alloc] init];
// 将对象p添加到autorelease释放池
p = [p autorelease]; [pool drain]; // 自动释放池销毁会给对象中所有的对象发送一条release消息
return 0;
}

MRC类工厂方法的编写

我们在初始化一个对象将它添加进自动释放池中

@autoreleasepool{
// 创建一个对象
Person *p = [[[Person alloc] init] autorelease];
}

可见每次创建一个Person对象都需要写很长的代码,于是我们可以封装内部细节提供一个类工厂方法

/**快速创建一个Person实例*/
+ (instancetype)person; + (instancetype)person
{
return [[[self alloc] init] autorelease];
}

外界调用类工厂对象初始化对象

int main(int argc, const char * argv[]) {

    @autoreleasepool{
// 创建一个对象
Person *p = [[[Person alloc] init] autorelease]; // 通过类工厂方法 创建的对象默认添加进自动释放池当中
Person *p1 = [Person person];
}
return 0;
}

我们想让Person在初始化后就有一个默认的名字。我们可以提供自定义构造方法。

#import <Foundation/Foundation.h>
@interface Person : NSObject @property(nonatomic, copy) NSString *name;
/**快速创建一个Person实例*/
+ (instancetype)person; /**自定义构造方法*/
-(instancetype)initWithName:(NSString *)name;
+(instancetype)personWithName:(NSString *)name;
@end #import "Person.h"
@implementation Person
/**自定义构造方法*/
-(instancetype)initWithName:(NSString *)name
{
if(self = [super init]){
self.name = name;
}
return self;
} + (instancetype)person
{
return [[[self alloc] init] autorelease];
} +(instancetype)personWithName:(NSString *)name
{
// 类方法中将对象添加进自动释放池中
return [[self initWithName:name] autorelease];
}
@end

上面的代码会发现在MRC类方法中生成的对象默认内部将其添加自动释放池当中,其实这也是苹果的规范。苹果Foundation框架中通过类方法创建的对象都是autorelease。

ARC的内存管理

ARC是编译器的特性,而不是运行时特性,跟其他语言的垃圾回收有着本质的区别。ARC原理就是在编译阶段为我们在必要的时候添加release这些代码。

ARC的判断原则

* 只要还有一个强指针指向对象,对象就会保持在内存中

ARC与MRC的混编



* 在ARC项目中执行某个文件使用MRC -fno-objc-arc

* 在MRC项目中执行某个文件使用ARC -fobjc-arc

使用Xcode将一个MRC工程转化成ARC项目

OC开发系列-内存管理的更多相关文章

  1. OC学习10——内存管理

    1.对于面向对象的语言,程序需要不断地创建对象.这些对象都是保存在堆内存中,而我们的指针变量中保存的是这些对象在堆内存中的地址,当该对象使用结束之后,指针变量指向其他对象或者指向nil时,这个对象将称 ...

  2. 高性能JAVA开发之内存管理

    这几天在找一个程序的bug,主要是java虚拟机内存溢出的问题,调研了一些java内存管理的资料,现整理如下: 一.JVM中的对象生命周期 对象的生命周期一般分为7个阶段:创建阶段,应用阶段,不可视阶 ...

  3. IOS开发的内存管理

    关于IOS开发的内存管理的文章已经很多了,因此系统的知识点就不写了,这里我写点平时工作遇到的疑问以及解答做个总结吧,相信也会有人遇到相同的疑问呢,欢迎学习IOS的朋友请加ios技术交流群:190956 ...

  4. IOS开发小记-内存管理

    关于IOS开发的内存管理的文章已经很多了,因此系统的知识点就不写了,这里我写点平时工作遇到的疑问以及解答做个总结吧,相信也会有人遇到相同的疑问呢,欢迎学习IOS的朋友请加ios技术交流群:190956 ...

  5. [转载]对iOS开发中内存管理的一点总结与理解

    对iOS开发中内存管理的一点总结与理解   做iOS开发也已经有两年的时间,觉得有必要沉下心去整理一些东西了,特别是一些基础的东西,虽然现在有ARC这种东西,但是我一直也没有去用过,个人觉得对内存操作 ...

  6. Code First开发系列之管理数据库创建,填充种子数据以及LINQ操作详解

    返回<8天掌握EF的Code First开发>总目录 本篇目录 管理数据库创建 管理数据库连接 管理数据库初始化 填充种子数据 LINQ to Entities详解 什么是LINQ to ...

  7. OC修饰词 - 内存管理

    <招聘一个靠谱的 iOS>—参考答案(上) 说明:面试题来源是微博@我就叫Sunny怎么了的这篇博文:<招聘一个靠谱的 iOS>,其中共55题,除第一题为纠错题外,其他54道均 ...

  8. IOS开发之内存管理--dealloc该写些什么

    在非ARC开发环境中,dealloc是类释放前,清理内存的最后机会.到底那些变量和属性该释放呢,一些特殊的类(nstimer,observer)该怎么释放.需要注意的是不释放会引起内存泄露,过度释放也 ...

  9. OC语法6——内存管理之引用计数器(retain,release)

    OC内存管理: 一.引用计数器: Java有垃圾回收机制(Garbage Collection,GC).也就是说当我们创建对象后,不需要考虑回收内存的事,Java的垃圾回收机制会自动销毁该对象,回收它 ...

随机推荐

  1. cnblogs博客主题原来可以弄得这么美观

    参考了网友 https://www.cnblogs.com/maybreath/p/5253824.html的做法,没想到真的可以耶. 总想弄个方便的.简洁的.可以被搜索引擎搜到的博客.以前用过wor ...

  2. 自定义checkbox(对勾)和radio样式

    checkbox: html: <div> <label class="unSelected selected" for="choose"&g ...

  3. PROXY——代理模式

    代理,说白了就是中介.假设有俩对象A和B,A想访问B,但是根据迪米特法则,我们不能喝陌生人说话,简而言之就是A要减少知道B的相关情况,要降低A与B的耦合度.这时我们使用中介C,而C拥有B的相关情况,A ...

  4. noip2010机器翻译

    以下题面摘自洛谷1540 题目背景 小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章. 题目描述 这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换. ...

  5. python利用ConfigParser读写配置文件

    ConfigParser 是Python自带的模块, 用来读写配置文件, 用法非常简单. 配置文件的格式是: []包含的叫section,    section 下有option=value这样的键值 ...

  6. 理解Java主函数中的"String[] args"

    public class Understand_String_args { public static void main(String[] args) { System.out.printf(&qu ...

  7. Android中通过进程注入技术修改系统返回的Mac地址

    致谢 感谢看雪论坛中的这位大神,分享了这个技术:http://bbs.pediy.com/showthread.php?t=186054,从这篇文章中学习到了很多内容,如果没有这篇好文章,我在研究的过 ...

  8. VIEW当中自定义属性的使用

    主要有三种方法可以实现自定义属性. 第一种方法,直接设置属性值,通过attrs.getAttributeResourceValue拿到这个属性值. (1)在xml文件中设置属性值 [html] vie ...

  9. PHP浮点计算结果返回异常问题

    php中如果直接小数点进行计算的话.比如16.8*3var_dump是50.4.但是return就变成了50.400000000000006.至于是什么原因本人尚不得而知.解决方法是用把计算放入下面的 ...

  10. 用MyEclipse将java文件转换成UML类图

    用MyEclipse将java文件转换成UML类图 参考: 用MyEclipse将java文件转换成UML类图 - 君临天下的博客 - CSDN博客  http://blog.csdn.net/dan ...