Obj-C高级编程--内存管理

自动引用计数

  自动引用计数:指内存管理中对引用采取自动计数的技术。

内存管理/引用计数

  持有对象引起引用计数加一 释放对象引起引用计数减一 引用计数为零释放对象

内存管理的思考方式

1、自己生成的对象,自己持有

       已 alloc,new,copy,mutableCopy 开头的方法

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

       使用 retain 方法

3、不需要自己持有的对象时释放

      是有遍历构造器生成的对象,被加入自动释放池中的,使用 autorelease

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

    多次 release,释放已经释放过的对象


alloc/retain/release/dealloc 实现

alloc

+ (id)alloc {

       return [self allocWithZone:NSDefaultMallocZone()];

}

+ (id)allocWithZone:(NSZone *)z {

       return NSAllocateObject(self, 0 ,z);

}

类方法调用NSAllocateObject函数分配了对象。

struct obj_layout {

     NSUInteger ratained; // 用来记录引用计数

};

inline id NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone)  {

      int size = 计算容纳对象所需内存大小;

     id new = NSZoneMalloc(zone, size); //分配对象所需内存

     memset(new, 0, size)

     new = (id) & ((struct obj_layout *)new) [1]; //将指针指向对象大小内存首地址,

}

alloc类方法用struct obj_layout中的retained整数来保存引用计数,并将其结构体写入对象内存头部。

retainCount

- (NSUInteger) retainCount {

      return NSExtraRefCount(self) + 1;

}

inline NSUInteger NSExtraRefCount(id anObject) {

     return ((struct obj_layout *)anObject)[-1].retain; // 将指针向上移1,指向 struct obj_layout

}

由对象寻址找到对象内部头部,从而访问其中的retained变量。

retain

- (id) retain {

      NSIncrementExtraRefCount(self);

      return self;

}

inline void NSIncrementExtraRefCount(id anObject) {

         if(((struct obj_layout *)anObject[-1]).retained == UINT_MAX - 1) { // 是否超出最大值

             [NSException raise:NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];

        }

        ((struct obj_layout *)anObject)[-1].retained++;

}

retained变量超出最大值时发生异常,实际上只运行了使retained数量加 1

release

- (void)release {

      if (NSDecrementExtraRefCountWasZero(self)) {

            [self dealloc];

       }

}

BOOL NSDecrementExtraRefCountWasZero(id anObject) {

         if (((struct obj_layout *)anObject).retained == 0) {

               return YES; 

         } else {

             ((struct obj_layout *)anObject).retained--;

              return NO;

        }

}

retainde变量大于 0 时减 1,等于 0 时调用 dealloc 实例方法,废弃对象。

dealloc

- (void)dealloc {

          NSDeallocateObject(self);

}

inline void NSDeallocateObject(id anObject) {

       struct obj_layout *o = &((struct obj_layout *)anObject)[-1];

       free(o);

}

废弃由 alloc 分配的内存块。

① 在 Objective-C 的对象中存在引用计数这一整数值。 ② 调用 alloc 或是 retain 方法后,引用计数值加1。 ③ 调用 release 后,引用计数值减 1。 ④ 引用计数值为 0 时,调用 dealloc 方法废弃对象。

苹果的实现

NSObject 类的 alloc 类方法

+alloc

+allocWithZone:

class_createInstance

calloc

通过calloc来分配内存块

retainCount/retain/release实例方法实现

- retainCount

__CFDoExternRefOperation

CFBasicHashGetCountOfKey

- retain

__CFDoExternRefOperation

CFBasicHashAddValue

-release

__CFDoExternRefOperation

CFBasicHashRemoveValue

(CFBasicHashRemoveValue 返回0时,-release 调用 dealloc)

通过调用__CFDoExternRefOperation函数,来选择调用哪个函数

int __CFDoExternRefOperation(uintptr_t op, id obj) {

           CFBasicHashRef table = 取得对象对应的散列表(obj);

            int count;

            switch(op) {

              case OPERATION_retainCount:

                  count = CFBasicHashGetCountOfKey(table, obj);

                  return count;

              case OPERATION_retain:

                  CFBasicHashAddValue(table, obj)

                   return obj;

              case OPERATION_release:

                  count = CFBasicHashRemoveValue(table, obj);

                  return 0 == count;

               }

}

__CFDoExternRefOperation函数按retainCount/retain/release操作进行分发,调用不同的函数。

- (NSUInteger)retainCount {

          return (NSUInteger)__CFDoExternRefOperation(OPERATION_retainCount, self);

}

- (id)retain {

         return (id)__CFDoExternRefOperation(OPERATION_retain, self);

}

- (void)release {

        return __CFDoExternRefOperation(OPERATION_release, self);

}

苹果的实现是采用散列表(引用计数表)来管理引用计数。

GNUstep 将引用计数保存在对象占用内存块头部的变量中,而苹果的实现,则是保存在引用计数表的记录中。

通过内存块头部管理引用计数的好处: ① 少量代码即可完成 ② 能够统一管理引用计数用内存块与对象用内存块

通过引用计数表管理引用计数的好处如下: ① 对象用内存块的分配无需考虑内存块头部 ② 引用计数表各记录中存有内存块地址,可从各个记录追溯到个对象的内存 块

autorelease

autorelease会像 C 语言的自动变量那样来对待对象事例,当超出其作用于时,对象事例的release实例方法被调用。

autorelease的具体使用方法如下:

(1)生成并持有NSAutoreleasePool对象

(2)调用已分配对象的autorelease实例方法

(3)废弃NSAutoreleasePool对象

NSAutoreleasePool对象的生命周期相当于 C 语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release事例方法。

Cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可以运行的地方,对NSAutoreleasePool对象进行生成,持有和废弃处理。

尽管如此,但在大量产生autorelease的对象时,只要不废弃NSAutoreleasePook对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。

autorelease 实现

[obj autorelease];

- (id)autorelease {

        [NSAutoreleasePool addObject:self];

}

autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法

+ (void)addObject:(id)anObj {

        NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 对象;

       if (pool != nil) {

            [pool addObject:anObj];

       } else {

          NSLog(@"NSAutoreleasePool 对象非存在状态下调用 autorelease");

      }

}

addObject类方法调用正在使用的NSAutoreleasePool对象的addObject实例方法。

- (void)addObject:(id)anObj {

         [array addObject:anObj];

}

实际的是将对象添加到连接列表中。 如果调用NSObject类的autorelease实例方法,该对象被追加到正在使用的NSAutoreleasePool对象中的数组里。

[pool drain];

-----------------------

- (void)drain

{

      [self dealloc];

}

- (void)dealloc

{

     [self emptyPool];

      [array release];

}

- (void)emptyPool

{

    for (id obj in array) {

       [obj release];

     }

}

专栏 提高调用 Objective-C 方法的速度

IMP Caching:在进行方法调用时,为了解决类名/方法名以及取得方法运行时的函数指针,要在框架初始化时对其结果进行缓存。

苹果的实现

苹果中autorelease的实现

class AutoreleasePoolPage

{

       static inline void *push ()

      {

            相当于生成或持有 NSAutoreleasePool 类对象

       }

        static inline void *pop (void *token)

       {

             相当于废弃 NSAutoreleasePool 类对象;

              releaseAll();

          }

         static inline id autorelease(id obj)

        {

                相当于 NSAutoreleasePool 类的 addObject 类方法 

                AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的

                 AutoreleasePoolPage 实例;

                 autoreleasePoolPage -> add(obj);

            }

         id *add(id obj)

         {

               将对象追加到内部数组中;

          }

        void releaseAll()

       {

             调用内部数组中对象的 release 实例方法

        }

     void *objc_autoreleasePoolPush(void)

     {

             return AutoreleasePoolPage::push();

       }

     void objc_autoreleasePoolPop(void *ctxt)  

     {

            AutoreleasePoolPage::pop(ctxt);

      }

       id *objc_autorelease(id obj)

     { 

           return AutoreleasePoolPage::autorelease(obj);

        }

}

ARC 规则

所有权修饰符

ARC 有效时,所有权修饰符共有 4 种

__strong修饰符

__weak修饰符

__unsafe_unretained修饰符

_autoreleasing修饰符

__strong 修饰符

__strong修饰符是id类型和对象类型默认的所有权修饰符

ARC 有效时

id oj = [[NSObject alloc] init];

相当于

id __strong obj = [[NSObject alloc] init];

id和对象类型在没有明确指定所有权修饰符时,默认为__strong修饰符。

附有__strong修饰符的变量在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。

__strong修饰符表示对对象的“强引用”,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

ARC有效时

{

       /*

       * 自己生成并持有对象

        */

       id __strong obj = [[NSObject alloc] init];

      /*

       * 因为变量 obj 为强引用

       * 所以自己持有对象

       */

} /*

   * 因为变量 obj 超出其作用域,强引用失效,

   * 所以自动释放自己持有的对象。

   *  对象的所有者不存在,因此废弃该对象

   */

此源码明确指定了变量的作用域。ARC无效时,该源代码可记述:

ARC 无效

{

       id obj = [[NSObject alloc] init];

        [obj release];

}

取得非自己生成并持有的对象

{

     /*

     * 取得非自己生成并持有的对象

      */

       id __strong obj = [NSMutableArray array];

     /*

       * 因为变量 obj 为强引用

      * 所以自己持有对象

       */

} /*

* 因为变量 obj 为强引用,

* 所以自己持有对象

*/

__strong,__weak,__autoreleasing修饰的,可以保证将富有这些修饰符的自动变量初始化为 nil。

通过__strong修饰符,不必再次键入 retain 或者 release,完美满足“引用计数式内存管理的思考方式”

“自己生成的对象,自己持有"和”非自己生成的对象,自己也能持有“,只需要通过对待__strong修饰符的变量赋值便可达成。

通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所属对象废弃)或者对变量赋值,都可以做到”不在需要自己持有的对象时释放“

__weak 修饰符

用来解决循环引用。

循环引用容易发生内存泄漏,所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。

__weak修饰符的变量不持有对象。若该对象被废弃,则此弱引用将自动失效且处于 nil 被赋值的状态。

__unsafe_unretained 修饰符

这如其名,是不安全的所有权修饰符。附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。

__unsafe_unretained 修饰的变量通附有 __weak 修饰符的变量一样,因为自己生成并持有的对象不能继续自己所有,所以生成的对象会立即被释放。

weak会自动 nil,unsafe_unretained 不会自动 nil。

__autoreleasing 修饰符

ARC 有效时不能使用NSAutoreleasePool,也不能使用autorelease

ARC有效时使用

@autoreleasepool { // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

id __autoreleasing obj = [[NSObject alloc] init]; // [obj autorelease]

}// [pool drain]

非显示使用__autoreleasing修饰符,编译器会检查方法名是否已alloc/new/copy/mutableCopy开始,如果不是则自动将返回值得对象注册到autoreleasepool

__weak修饰符为了避免循环引用而使用的,但在访问富有__weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。

id __weak obj1 = obj0;

NSLog(@"class=%@", [obj1 class]);

相当于:

id __weak obj1 = obj0; // 使用__weak 相当于将对象放入自动释放池

id __autoreleasing tmp = obj1;

NSLog(@"class=%@",[tmp class]);

id的指针对象的指针在没有显示指定所有权修饰符时,会被默认附加上__autoreleasing修饰符。

- (BOOL) performOperationWithError:(NSError **)error;

默认是 (NSError *__autoreleasing *)

NSError *error = nil;

NSError **pError = &error; // 报错,原因:默认对象指针类型是 __autoreleasing ,而对象默认是 __strong 类型,两种所有权类型不匹配,不能直接复制。

对象指针型赋值时,其所有权修饰符必须一致。

NSError __strong *error = nil;

BOOL result = [obj performOperationWithError:&error]; //需要传一个 __autoreleasing 修饰的变量。但此时 __strong 为什么也可以呢?

编译器自动转化了:

NSError __strong *error = nil;

NSError __autroreleasing *tmp = error;

BOOL result = [obj performOperationWithError:&tmp];

error = tmp;

在显示指定__autoreleasing修饰符时,必须注意对象变量要为自动变量(包括局部变量,函数或方法参数)。

规则

不能使用retain/release/releaseCount/autorelease

不能使用NSAllocateObject/NSDeallocateObject

须遵守内存管理的方法命名规则

不要显示调用dealloc

使用@autoreleasepool块替代NSAutoreleasePool

不能使用区域NSZone

对象型变量不能作为 C 语言结构体的成员(C语言的规约上没有方法来管理结构体成员的生存周期)

显示转化”id“"void *"

ARC 有效时,通过__bridge来转换,idvoid *就能够相互转换了。

但是转换为void *的转换,其安全性与赋值给_unsafeunretained修饰符相近。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。

__bridge转换中海油另外两种转换,分别是"_bridgeretained"bridge_transfer转换。

ARC有效:

id obj = [[NSObject alloc] init];

void *p = (__bridge_retained void *)obj;

__bridge_retained 转换可使要转换赋值的变量也持有所赋值的对象。

ARC无效:

id obj = [[NSObject alloc] init];

void *p = obj;

[(id)p retain];

_bridgetransfer转换提供与bridgeretained相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。

ARC有效:

id obj = (__bridge_transfer id)p;

ARC 无效

id obj = (id)p;

[obj retain];

[(id)p release];

_bridgeretained转换与retain类似。_bridgetransfer转换与release相似。 在给 id obj 赋值时retain相当于__strong修饰符的变量。

属性


数组

strong/weak/__autoreleasing 修饰符保证其指定的变量初始化为nil。

{

       id objs[2];

       objs[0] = [[NSObject alloc] init];

       objs[1] = [NSMutableArray array];

}

数组超出其变量作用域时,数组中各个附有__strong 修饰符的变量也随之失效,其强引用消失,所赋值的对象也随之释放。

静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理。

ARC的实现

__strong 修饰符

{

      id __strong obj = [[NSObject alloc] init];

}

/*编译器的模拟代码*/

id obj = objc_msgSend(NSObject,@selector(alloc));

obj_msgSend(obj,@selector(init));

objc_release(obj);

// 变量作用域结束时通过 objc_release 释放对象。编译器自动插入 release

{

       id __strong obj = [NSMutableArray array];

}

/*编译器的模拟代码*/

id obj = objc_msgSend(NSMutableArray, @selector(array));

objc_retainAutoreleasedReturnValue(obj);

objc_release(obj);

objc_retainAutoreleasedReturnValue用于自己持有对象的函数,但它持有的对象应为返回注册在 autoreleasepool 中对象的方法,或是函数的返回值。

objc_retainAutoreleasedReturnValue函数是成对出现的,与之相对的函数是objc_autoreleaseReturnValue。用于 alloc/new/copy/mutableCopy 方法以外的方法。

+ (id)array {

         return [[NSMutableArray alloc] init];

}

/*编译器的模拟代码*/

+ (id)array {

        id obj = objc_msgSend(NSMutableArray, @selector(alloc));

       objc_msgSend(obj, @selector(init));

        return obj_autoreleaseReturnValue(obj);

}

obj_autoreleaseReturnValue返回注册到 autoreleasepool 中对象的方法使用了obj_autoreleaseReturnValue函数返回注册到 autoreleasepool 中的对象。

__weak 修饰符

附有 __weak 修饰符的变量所应用的对象被废弃,则将 nil 赋值给该变量。

{

    id __weak obj1 = obj;

}

/*编译器的模拟代码*/

id obj1;

objc_initWeak(&obj1, obj);

objc_destroyWeak(&obj1);

objc_initWeak函数如下:

obj1 = 0;

objc_storeWeak(&obj1//变量地址, obj//对象地址); //把第二参数的赋值对象的地址作为键值,将第一个参数的附有 __weak 修饰的变量的地址注册到 weak 表中。

objc_destroyWeak函数如下:

objc_storeWeak(&obj1, 0); // 如果第二个参数为 0,则把变量的地址从 weak 表中删除

weak 表与引用计数表相同,作为散列表被实现。如果使用 weak 表,将废弃对象的地址作为键值进行检索,就能高速地获取对应的附有 __weak 修饰符的变量的地址。

释放对象,程序的动作

(1)objc_release (调用 release 方法,减少引用计数至 0 ,对象正被销毁,声明周期即将结束。)

(2)因为引用计数为 0 所以执行 dealloc (调用 dealloc,继承关系中每一层都调用 dealloc)

(3)objcrootDealloc (调用最底层 dealloc, NSObject调用执行 object_dispose 方法)

(4)objc_dispose (解除 Associate 方法关联的对象)

(5)objc_destructInstance (为 C++ 实例变量调用)

(6)objccleardeallocating

最后调用 objccleardeallocating 的函数动作如下:

① 从 weak 表中获取废弃对象的地址为键值的记录。 ② 将包含在记录中的所有附有 __weak 修饰符变量的地址,赋值为 nil。 ③ 从 weak 表中删除该记录。 ④ 从引用计数表中删除废弃对象的地址为键值的记录。

附有 __weak 修饰符的变量,即是使用注册到 autoreleasepool 中的对象

{

     id __weak obj1 = obj;

     NSLog(@"%@", obj1);

}

/*编译器的模拟代码*/

id obj1;

objc_initWeak(&obj1,obj);

id tmp = objc_loadWeakRetained(&obj1);

objc_autorelease(tmp);

NSLog(@"%@", tmp);

objc_destoryWeak(&obj1);

(1)objc_loadWeakRetained 函数取出附有 __weak 修饰符变量所引用的对象并 retain。 (2)objc_autorelease 函数将对象注册到 autoreleasepool 中。

由此可知,因为附有 __weak 修饰符变量所引用的对象被注册到 autoreleasepool 中,大量使用附有 __weak 修饰符的变量,autoreleasepool 的对象也会大量地增加。

__autoreleasing 修饰符

在 ARC 无效时调用对象 autorelease 方法。

@autoreleasepool {

     id __autoreleaseing obj = [[NSObject alloc] init];  

}

/*编译器的模拟代码*/

id pool = objc_autoreleasePoolPush();

id obj = objc_msgSend(NSObject, @selector(alloc));

objc_msgSend(obj, @selector(init));

objc_autorelease(obj);

objc_autoreleasePoolPop(pool);

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

推荐阅读更多精彩内容