概述

移动设备的内存极其有限,每个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. IDEA compile successfully many errors still occur

    Compile and install successfully with maven in IDEA, but error prompt still popup. Your local enviro ...

  2. Javascript基础五(BOM和DOM)

    1.BOM概念 什么是BOM?         BOM是Browser Object Model的缩写,简称浏览器对象模型.这个对象就是window         BOM提供了独立于内容而与浏览器窗 ...

  3. Nginx+Keepalived主从配置(双机主从热备)+Tomcat集群

    拓扑环境 以下表格是这次測试须要的拓扑环境,几台server.每台server上安装什么,都有介绍. server名称 系统版本号 预装软件 IP地址/VIP Nginx主server CentOS ...

  4. PHP 遍历数组for foreach while

    以下是使用foreach  while  for 三种循环展示遍历数组的概念 1:foreach( ) <?php $a = array('hank','mike','lucy'); forea ...

  5. Java线程的优先级设置遵循什么原则?

    Java线程的优先级设置遵从下述原则: (1) 线程创建时,子线程继承父线程的优先级 (2) 线程创建后,可在程序中通过调用setPriority( )方法改变线程的优先级 (3) 线程的优先级是1~ ...

  6. 【Http】队头阻塞(Head of line blocking)多路复用(Multiplexing)

        图中第一种请求方式,就是单次发送request请求,收到response后再进行下一次请求,显示是很低效的. 于是http1.1提出了管线化(pipelining)技术,就是如图中第二中请求方 ...

  7. delphi 监控文件系统

    elphi 监控文件系统 你是否想为你的Windows加上一双眼睛,察看使用者在机器上所做的各种操作(例如建立.删除文件:改变文件或目录名字)呢? 这里介绍一种利用Windows未公开函数实现这个功能 ...

  8. AcWing 227. 小部件厂 (高斯消元)打卡

    题目:https://www.acwing.com/problem/content/description/229/ 题意:有很多个零件,每个零件的生产时间都在3-9天之间,现在只知道每个工人的生产部 ...

  9. Blazor 组件库 Blazui 开发第一弹【安装入门】

    标签: Blazor Blazui文档 Blazui 传送门 Blazor 组件库 Blazui 开发第一弹[安装入门]https://www.cnblogs.com/wzxinchen/p/1209 ...

  10. vue绑值(表格)

    vue绑值(表格) <!DOCTYPE html> <html lang="zh-CN"> <head> <title>JSON取数 ...