秃萝卜的技术博客

A iOS Developer Newer

欢迎来到我的技术博客,我是秃萝卜(@Tuluobo),一名 iOS 开发菜鸟。


希望能在 iOS 开发的道路上越走越远,拥有自己开发的千万级用户的 App 。

OC基本语法总结(四)——内存管理MRC和ARC

最近一直在坚持写博客,这些学习笔记主要是自己以前学习OC的过程中记下的笔记,现在整理的过程中就想,把这个写成博客。大牛同志写博客一年好几百篇,我的几年了才30篇不到,心里也鄙视了自己一把。最近却写的停不下来了,准备把这个系列的博客写完,一般两天基本出一篇,虽然比较基础,但是一篇下来大概也是需要两个小时左右整理。现在才知道写文章的人有多么辛苦。后面也希望自己能一直坚持写下去。

今天我总结的是Objective-C的手动内存管理和现在Apple已经全面支持的自动引用计数(自动内存管理)。好,下面我们进入正题。

准备工作

刚才说到,现在的iOS项目已经全面支持ARC,而且,Apple一向的强制原则就是,我推荐的,你就只能按着我的来。所以我们现在在Xcode7上建立的新项目,已经看不到原来的可以勾选的ARC选项,换句话说就是,我们现在只能开发ARC的项目,像Apple的新语言Swift根本就不支持MRC,那么是不是说我们已经彻底不能看到MRC的项目了?答案是NO。那么如何建立一个MRC项目?

首先,我们按着一般建立项目的步骤建立一个新项目,然后查看项目是否为ARC项目:

      TARGETS—项目名称——Build Settings—-Objective-C A.. R.. C..YES

如果我们将YES修改为NO,那么项目就会变成MRC项目,这时候我们就要自己管理引用计数了。

还有就是,我们以前的项目可能是MRC,现在,MRC已经一去不复返,我们想将我们的项目转换成ARC项目,这个怎么办?难道一个文件一个文件修改?答案也是NO。Xcode提供一键将非ARC项目改为ARC项目:

      XcodeEdit—>Refactor—->Convert to Objective-C ARC…

那么,如果我们需要在我们的项目中引入第三方框架,而我们的项目是ARC,第三方框架因为太老而使用的MRC,这时候我们应该怎么办?答案是,Xcode提供对项目中的不同文件使用不同的编译策略。就是我们常说的MRC和ARC混合编程。这里使用到一个编译指令。项目中单文件ARC或者MRC设置位置:

      TARGETS—项目名称——Build Phases—-Compile Sources

ARC项目不需要ARC的文件设置参数:-fno-objc-arc,如下图(图片来自网络);

no-objc-arc

对非ARC项目需要ARC的文件设置参数:-fobjc-arc。如下图(图片来自网络):

objc-arc

接下来,我们首先总结一下手动引用计数(MRC)的知识。

MRC内存管理 {.p1}

我们说的引用计数管理,其实就是对对象的内存管理。那么我们的内存管理的对象是哪些呢?任何继承了NSObject的对象。在OC中,每一个OC对象都有自己的引用计数器,是一个整数,对象被引用的次数。存在于对象的内部,占四个字节

当使用alloc,new和copy创建一个新对象时,新对象的引用计数就默认为1。当引用计数为0,就回收内存,对象被销毁。

  1. 给对象发送一条retain消息,引用计数+1。
  2. 给对象发送一条release消息,引用计数-1。
  3. 可以通过retainCount方法获取引用计数次数。

当对象引用计数为0时,对象被销毁时,系统发送一条dealloc消息。

一般开发中我们将重写dealloc方法,释放相关资源。同时一旦调用了dealloc方法,就必须在最后调用[super dealloc];

注意:

release为0的对象称为僵尸对象,指向僵尸对象的指针成为野指针。

一个对象引用计数为0后不能再调用release方法。会出现野指针错误(EXC_BAD_ACCESS)。OC中没有空指针错误,一个空指针调用任何方法,都会被忽略,不会出错。

retain方法有返回值,返回其对象本身。

在OC中,局部变量和动态产生的对象在内存中是这样的。局部对象存储在栈中,动态产生的对象,首先在堆中申请内存,然后初始化。蓝色的线,相当于指针指向OC对象。此时的堆对象中会有一个引用计数器,它的数值为1。

stackdeam

下面是一段MRC代码:

#import "People.h"

int main()
{
    People *p = [[People alloc] init];    //假设有一个People对象,创建对象,
                                          //引用计数+1,变为1

    [p release]; //在使用完p对象之后要release一次,引用计数-1,变为0
    return 0;
}
  • MRC内存原则

1>需要引用时,+1;不需要引用了,-1;

2>谁创建,谁release;

3>谁retain,谁release;

4>只要调用了alloc,必须有release(autorelease)

5>成员变量的set方法中需要注意的:基本数据不用管,对象数据需要注意。

//虽然@property可以省略setter和getter方法,但是在MRC中,成员变量如果是对象
//我们应该在setter方法中将原来的对象进行一次release
//将新赋值的对象进行一次retain
- (void)setBook: (Book)book        
{
    if(_book != book)           //如果原来的_book和新赋值的不是同一对象,就需要更新引用计数
    {
        [_book release];       //如果第一次赋值,_book为nil,此句代码将没有人和执行效果
        _book = [_book retain];//新的对象计数+1
    }
}

上面的代码如果没有学习过OC的可能看不懂,建议在看书系统学习一下。

当然,上面的setter方法中,成员变量引用计数+1,那么我们是不是在释放People对象时,将_book变量也销毁了,此时应该将_book所指的对象引用计数-1。那么在dealloc方法中需要注意对成员对象计数-1。

- (void)dealloc
{
    [_book release];

    [super release];
}
  • @property的参数

上面我们讲到@property可以将基本数据类型的成员变量的getter和setter方法生成,我们不需要人工干预,但是对于对象类型的成员变量,我们必须自己写setter方法,而且对于一个对象类型的变量,代码几乎一样,此时,Apple给@property有添加了一些编译器特性。就是@property的参数。

@property(retain) Book *book; //retain:生成set代码中的release旧值,retain新值

//不能代替dealloc方法,还是需要在dealloc中release对象

其实@property的参数还有很多,主要分为四类:

1、内存管理

  • retain : release旧值,retain新值

  • assign :直接复制(缺省值,适用于非OC对象类型)

  • copy : release旧值,copy新值

2、读写属性,是否要生成set方法

  • readaonly :只读,只生成getter的声明和实现

  • readwrite :可读可写,生成setter和getter的生命和实现(缺省值)

3、多线程管理

  • nonatomic :性能高,不加锁,线程不安全(建议使用,开发常写)

  • atomic:性能低,加锁,线程安全(缺省值)

4、setter和getter方法的名称

  • @property (setter = setXxx: ,getter = xxx) int weight;

  • 一般使用在BOOL类型的成员变量 getter = isXxx

四种参数可以组合使用,只要不是冲突的参数,就可以组合,一般我们开发中的组合为:

// 对象类型
@property(nonatomic, retain) Book *book;
// 基本类型
@property(nonatomic, assign) int age;

// 如果设置读写属性,也可以加上
@property(nonatomic, assign, readonly) double weight;
  • @class

如果A文件头文件包含了B文件的头文件,同时B文件有包含了A文件的头文件,此时就会出现无法编译的情况,两个头文件循环导入,最后进入死循环。此时,我们就要用到@class关键字。

@class Car;

仅仅是告诉编译器这是一个类。能解决循环引用时不能import的问题。

一般的开发规范:

  1. 一般情况下,在.h文件中我们使用@class Xxx;
  2. 在.m文件中import “Car.h”,提高编译效率。
  • autorelease

autorelease关键字,字面意思好像是自动释放,其实它不是自动释放,它的正确理解是:半自动的。就是在自动释放池销毁的时候release一次

autorelease准确的说是一个函数,返回对象本身id。调用autorelease方法后,将对象放入自动释放池,对象计数不变,当自动释放池销毁时,池中的对象会自动调用release一次。

  • 自动释放池

ios程序运行过程中会创建无数多的池子,这些池子都是栈这种数据结构存在(后进先出),当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池。

自动释放池有两种创建方式。

// ios5以前的方式:
NSAutoreleasePool *pool = [NSAutoreleasePool alloc] init];

//中间是自动释放池代码

[pool release];

//下面的方式是ios5.0以后的方式
@autoreleasepool {

    //创建自动释放池
    //使用方式:
    Person *p = [[[Person alloc] init] autorelease];

}   //销毁自动释放池,池中所有对象进行release一次。

 

自动释放池的一些特点:

  1. 优点:可以延迟对象释放时间,不用关心对象释放时间。
  2. 缺点:不能准确控制对象释放时间,占用内存较大的对象不要使用autorelease。

注意:

1.自动释放池在内存中的存放数据结构是栈,后进先出。同时自动释放池可以嵌套使用。

2.不能在调用autorelease之后再次调用release,其实autorelease就是在销毁自动释放池时,池中对象进行release一次。

3.不能多次调用autorelease方法,每一个自动释放池对应一个autorelease。

4.一般情况我们自己创建的对象放入栈顶的池子。

  • 开发中的技巧
  1. 调用系统中的对象,系统的对象实例化中没有alloc,new,copy等关键字,说明返回的对象都是autorelease,不需要关心它的内存管理。
  2. 可以在模型类中设计一些类方法,返回一个带有autorelease方法的对象。

注意:在设计自己的带有autorelease方法的对象的类方法时,方法中的类名使用self代替直接写类名。

ARC内存管理 {.p1}

ARC属于编译器特性。自动生成引用计数的代码,并插入到特定位置,不需要我们关心其中的细节,大大提高程序员的工作效率。那么它是如何判断的呢?

ARC的判断准则:只要没有强指针指向对象,就会释放对象。

  • 指针分为两类分类:
  • A:强指针:默认情况下,所有的指针都是强指针, 可以使用关键字__strong 进行修饰一个变量。

    B:弱指针:__weak 关键字修饰的变量。

    在ARC中,弱指针指向的对象如果被销毁,此时弱指针值自动修改为nil

    注意:

    所有关于内存的代码releaseautoreleaseretainretainClount等不能调用。

    允许重写dealloc方法,但是不能调用[super dealloc];

    在ARC中,我们需要将@property的参数retain改为strong,意味着成员变量为强指针;

     此时,weak代替assign,相当于assign,意味着成员变量为弱指针。

    循环引用 {.p1}

    在内存管理中,我们要注意的就是循环引用,循环引用是两个对象循环强引用。此时,两个对象都不能release,就不会被释放。

    我们俩可以将其中一个对象的@property参数设置为weak。

    如何检测循环引用,其实也是一个开发中的技巧。已经有很多人介绍过循环引用这儿的技巧和解决方案。Apple也是将view中的控件都默认为weak弱引用。

    总结 {.p2}

    今天总结的内容相对较多,同时内存管理也是iOS开发中很重要的一节内容,在这篇文章中,我并没有展开写,只是总结归纳了一下。如果堆内存管理不是很清楚的同学,建议多在此话费一点时间。在面试中,绝对不会逃过内存管理不问。下一篇准备总结OC中的又一个特色——Block代码块。

    最近的文章

    iOS基本语法总结(五)——Block

    这一篇,我将总结一下OC语言的一个亮点——Block类型。Block是iOS4 和Mac OS X 10.6以后引进的对C语言的扩展。Block在OC中其实也是对象,它封装了一段代码,这段代码在后面任何时机都能被调用。在Apple最新的Swift语言中也有一个相似的特性,叫闭包,也就是说现代的高级语言,基本都会有这种闭包式编程。下面,我们先详细说一说Block类型。Block数据类型block是用来保存一段代码,在后面的编码中可以使用中它完成一些功能。block可以作为函数的参数或者函数的...…

    BlockiosObjective-C继续阅读
    更早的文章

    OC基本语法总结(三)——类的细节

    前面的两篇已经算是把基本的语法规则总结完了,我准备在这篇里面总结一下OC关于类的一些细节信息,比如id关键字,类的加载,初始化等。写到这里,我想起了前几天微博上看到的有一位同学总结的iOS学习路线图,在这里我给出需要的同学链接:iOS学习路线图(曾宪华)。我这几天总结的几篇博客,其实只是iOS路线图上的一个点。希望大家能根据自己的情况把这个路线图上自己感兴趣的方向的技能都学习一遍。id类型 在Objective-C 中,id关键字是一种特殊类型,我们称之为id类型,同时也叫做万能的指针,...…

    iosobjc类继续阅读