Objective-C 内存管理(上)学习笔记


一.开篇之初

  1. 内存管理解决的问题就是:

    1)防止野指针的生成
    (野指针:指向变量的指针还存在,但是所指向的内存已经被释放,此时的指针就变成了野指针 -- 没有指向 “ 内容 ” 的指针)

    2)防止出现内存泄漏
    (内存泄漏:指向内存空间的指针已经被释放,但是该指针指向的内存空间还在内存中存在(被占用) -- 没有 “ 地址 ” 的内存)

    3)合理使用内存,防止有限内存的大量消耗

  2. Objective-C的内存管理有三种,其中iOS中能用的,就是MRC(手动引用计数)和ARC(自动引用计数,官方推荐使用);而另外一个垃圾回收机制,只能用在OS X系统中。

  3. 内存管理管理的范围是,Objective-C 对象(基本数据类型由系统自动管理)。

  4. MRC是基于引用计数的内存管理,是否释放内存取决于引用计数是否为0;但注意,真正要研究并不是引用计数,而是对象是否被持有的问题。

  5. ARC是基于自动引用计数的内存管理,是否释放内存取决于对象是否还有强引用指向;真正研究的是,对象的所有权问题。(所有权的概念是ARC中引入的)


二.内存管理的思考方式

引自:《Objective-C高级编程 iOS与OS X多线程和内存管理》

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也能持有
  • 自己持有的对象不再需要时释放
  • 非自己持有的对象无法释放

换个方式来解读:

  • 自己申请的内存,自己所掌管(拥有)
  • 不是自己申请的内存,自己也可以掌管(拥有)
  • 自己掌管(拥有)的内存不再需要时就释放(free)
  • 不是自己掌管(拥有)的内存,无法释放(free)

三.MRC(Manual Reference Counting)内存管理

--> 小小结 <--

  • MRC模式下对象什么时候被销毁?引用计数值为0的时候。

  • MRC使用的管理内存的基本方法和属性:

    • 四个方法 --> retain/release/dealloc/autorelease/
    • 一个属性 --> retainCount(记录引用计数值)
      <strong><----均定义在 NSObject.h 中----></strong>
//define OBJC_ARC_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
  • 谁retain,谁release
  • retain既是把retainCount值加 1; release既是把retainCount值减 1
  • dealloc只有在 retainCount = 0 的时候,由系统自动调用
  • autorelease是把对象加进自动释放池中,由系统自动为池中的对象发送release消息

  • 问题 1:什么是引用计数(Reference Counting)?

  • 引用计数 ?

这里的“计数”表明必然会有一个东西(变量)来记录引用的变化,而在OC里这个变量就是retainCount;那么还有一个问题就是通过什么方式来操作这个变量,OC里就是retain(引用次数加 1),release(引用计数减 1 )方法。

  • 引用计数:就是分配的<strong> 内存区块 </strong>被<strong> 多少个 </strong>OC对象所持有(掌管;保持且拥有),间接表示就是retainCount值的大小。

注:对象,指人可以识别的东西,具备属性、收发信息、处理信息;而从系统的角度看,操作对象就是操作一块内存。(可能不是很准确......)

  • 问题 2 :引用计数如何管理OC对象?

  • 首先明确,引用计数的变化是被持有者的变化。

  • 那么问题就是怎样持有对象(持有内存)?

  • 持有:就是可以访问内存,且可以进行读写操作,而一般是通过内存的首地址进行内存的访问,就是指针访问。

  • 而OC中一般用来分配内存的的函数是alloc/new/copy/mutablecopy(当然还有clloc...等等),它们返回的都是指针,就是使用他们来生成对象并持有对象的

  • 问题 3:持有?释放?销毁?对象... , 请看下表:

OC操作方法 对象的操作 retainCount
alloc/new/copy/mutablecopy等 生成并持有对象 1?
retain 持有对象 +1
release 释放对象 -1
dealloc 销毁对象 此时该值没有意义
autorelease 在自动释放池结束时,为里面的对象发送一条release消息 (all object) -1

上面涉及的方法定义如下(NSOject.h):

//define OBJC_SWIFT_UNAVAILABLE(_msg) __attribute__((availability(swift, unavailable, message=_msg)))
+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");

- (id)copy;
- (id)mutableCopy;
  • 问题 4:什么是自动释放池?

  • 自动释放池:在自动释放池结束时,系统自动为里面的对象发送一条release消息(when the pool itself is drained)

  • 要使用自动释放池就要使用NSAutoreleasePool对象

NSAutoreleasePool
  • NSAutoreleasePool它的方法
  • 使用方法:

  • 创建一个NSAutoreleasePool对象
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  • 添加要释放的对象进NSAutoreleasePool对象中
    id obj = [NSString alloc] initWithstring:@"objective-c pool"];
    [obj autorelease];[pool addObject:obj]; --- 1

  • 释放NSAutoreleasePool对象
    [pool drain];等同于[pool release]; --- 2

注意:
1 --> 建议使用autorelease方法,因为后面的方法会导致同一个对象被多次加入自动释放池中。

addObject:方法

2 --> 虽然两个方法效果等同,但还是建议使用自动释放池专门的drain方法。
drain方法

  • 补充autorelease方法


    autorelease方法
  • 问题 5:MRC下如何防止野指针访问?

  • 野指针访问:指向的内存空间已经被释放了,但是指针还指向着已经被释放的内存,此时的指针就是野指针。

  • 访问了不存在的内存,当然会引起程序崩溃

  • 修改Xcode工程为MRC模式

  • 开启对应targets的僵尸对象检测,详细步骤如下:


    选择Edit Scheme
勾选僵尸对象检测
  • 情况 1:过快释放了对象(不要理retaiinCount把注意力放在对象被持有的个数上)

  • retainCount的补充:


    只能用在调试阶段,值是不可靠的
  • 程序代码和运行结果


    tesh.m
main.m
指向异常的代码
  • 问题 6:MRC下如何防止内存泄漏?

  • 自己生成的对象,自己所持有

  • 非自己生成的对象,自己也能持有

  • 自己持有的对象不再需要时释放

  • 非自己持有的对象无法释放

补充:


持有对象
运行结果

疑问:mArrayCopy的retainCount是2 ?被持有者有两个?

再来一次release

从这里就可以证明了,cope出来的新对象只是被mArrayCopy自己所持有而已,所以当release一次的时候对象已经被释放了,如果再release就是野指针访问了(注:直接看持有者有多少)。

代码:

    /**
     *  alloc就是分配内存的意思,返回了一个指向内存首地址的指针
     */
    NSMutableArray *mArrayAlloc = [[NSMutableArray alloc] init];  // mArrayAlloc 持有对象
    /**
     *  new 就相当于alloc+init,但是new有可能会返回同一个对象,所以并不建议使用
     */
    NSMutableArray *mArrayNew = [NSMutableArray new];   // mArrayNew 持有对象
    
    /**
     *  copy是一个实例方法,具体如下:
     *  - (id)copy
     *  Returns the object returned by copyWithZone:.
     *  - (id)copyWithZone:(NSZone *)zone
     *  Returns a new instance that’s a copy of the receiver.
     *  ---new instance 就表明了创建了一个新的内存,并返回首地址(id 相当于 void *)
     */
    NSMutableArray *mArrayCopy = [mArrayAlloc copy];   //mArrayCopy 持有了对象
    
    /**
     *  Returns the object returned by mutableCopyWithZone:.
     *  - (id)mutableCopy
     *  - (id)mutableCopyWithZone:(NSZone *)zone
     *  Returns a new instance that’s a mutable copy of the receiver.
     *  ---new instance 就表明了创建了一个新的内存,并返回首地址(id 相当于 void *)
     */
    NSMutableArray *mArrayMutablecopy = [mArrayNew mutableCopy];//mArrayMutablecopy 持有了对象
  • 持有对象
源代码
运行结果
明显的野指针访问了
  • 使用copy来独立管理内存
使用copy源代码
内容没有改变
  • 如果内存还在使用的话,当然不要把对象赋值为nil

  • 对象之间相互持有的情况

  • 程序代码


    Apple.h
Apple.m
Girl.h
Girl.m
main.m

如果要达到目的,apple让girl也持有,就要在girl得到apple的时候持有一下,而可以做持有操作的是retain,来看看:

内存泄漏

我们知道对象在最后销毁的时候是调用了dealloc方法的,那么girl既然持有了apple那么在销毁自己的时候是不是应该把自己持有的东西给交出来(释放掉),已死的对象不可能持有东西了吧,所以在girl的dealloc方法中加上apple释放的代码:

虽然上面的方法是可以的,但是有问题,问题如下:

retain

apple再持有一下[[Apple alloc] init],再给girl,直接翻译都是问题,而且从封装性来看,girl要持有apple应该是自己去持有,也就是要自己进行retain,而不是要apple先retain再给girl,

代码优化:


retain去掉
set方法中进行retain

还有,如果我们从现实生活中考虑问题(面向对象是现实世界的抽象),girl会不会只要一次apple呢?多要几个~~

为了防止内存泄漏,我得这么干,估计你看到这就想呵呵了:


正常释放

再次优化代码,目的是只要girl再次要一个新的apple就给它持有,如果是拿原来的apple当然不再次持有咯:

做if判断
正常释放

代码修改成下面这样就是真正的,girl直接的持有一个新的apple(新的内存空间)了,不过结果是一样的,正常释放:


ARC( Automatic Reference Counting)内存管理

  • 请期待下一篇......
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容