自动引用计数(ARC Automatic Reference Counting)

ARC简介:内存管理中对引用采取自动引用计数的技术;

在Apple LLVM 编译器中设置ARC为有效状态,就无需再次输入retain或release编码

降低了程序崩溃、内存泄露的风险,减少了开发者的工作量。

使用条件:使用Xcode4.2以上版本,使用LLVM编译器3.0以上版本,编译器中设置ARC为有效


引用计数

内存管理思考方式:

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

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

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

非自己持有的对象没法释放


对象操作与Objective-C中方法的回应

生成并持有对象 alloc/new/copy/mutableCopy等方法

持有对象 retain方法

释放对象 release方法

废气对象 dealloc方法

 

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

alloc/new/copy/mutableCopy这些方法名意味着自己生成的对象只有自己持有

注:[NSObjective new] = [[NSObjective alloc] init],两种方法都为生成并持有对象

copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本

mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现的mutableCopyWithZone:方法生成并持有对象的副本

说法不一样但意义是一样的


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

上述方法以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者,例:

//取得非自己生成并持有的对象
        id obj = [NSMutableArrayarray];
        //取得的对象存在,但自己不持有对象
//此时使用retain方法便可以持有该对象
        [obj retain];
        //自己持有对象


通过retain方法,非自己持有的对象跟用alloc/new/copy/mutableCopy方法生成并持有的对象一样,成为了自己所持有的


不再需要自己持有的对象时释放:自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法

不论是用alloc/new/copy/mutableCopy生成并持有 还是用retain方法持有的对象都用release释放

autorlease:此方法与release方法的区别是release会立即释放,而autorelease方法不立即释放,会将其注册到autoreleasepool中,pool结束时自动调用

此方法可以使取得的对象存在,但不自己持有,例:

id obj = [[NSObjectalloc] init];
        //自己持有对象
        [obj autorelease];
        //取得对象存在,但自己不持有
        return obj;


也可以用retain方法将调用autorelease方法取得的对象变为自己持有


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

释放了非自己持有的对象应用程序会崩溃




alloc  /  retain  /  release  /  delloc实现

alloc:

GNUstep中的实现:

+(instancetype)alloc
{
    return [selfallocWithZone:NSDefaultMallocZone()];
}

+(instancetype)allocWithZone:(struct_NSZone *)zone
{
    returnNSAllocateObject(self,0,zone);
}



NSAllocateObject函数实现

struct obj_layout{
        NSUInteger retained;
    };
    inlineid
    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];
    }


说明:NSZone:为防止内存碎片化而引入的结构,对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高内存管理的效率。

苹果现在的运行时系统忽略了区域的概念,运行时系统本身对内存的管理已极具效率,使用区域管理反而会降低效率。


去掉NSZone后的源代码:

struct obj_layout{
      NSUInteger retained;
};
+(instancetype)alloc
{
    int size =sizeof(struct obj_layout) +对象的大小;
    structobj_layout *p = (structobj_layout *) calloc(1, size);
    return (id)(p +1);
}



alloc 类方法用struct obj_layout中的retained整数来保存引用计数

对象的引用计数也可以用retainCount方法获得

id obj = [[NSObject alloc] init];

NSLog(@"retainCount = %d",[obj retainCount]);


初始化完成时对象的引用计数为1;

- (NSUInteger)retainCount
{
    return NSExtraRefCount(self) + 1;
}

NSUInteger NSExtraRefCount(id object)
{
    return (()struct obj_layout *) anObject)[-1].retained;
}



retained方法可以使retained变量加1,release方法使retained变量减1;


retained方法:

- (instancetype)retain
{
    NSIncrementExtraRefCount(self);
    return self;
}

inline void 
void NSIncrementExtraRefCount(id object)
{
    if (((struct obj_layout *) anObject) [-1].retained = UINT_MAX - 1)
        [NSException raise:NSInternalInconsistencyException format:@"NSInternalInconsistencyException() asked to increament too far"];
    
    ((struct obj_layout *) anObject)[-1].retained++;
}

release方法:

- (oneway void)release
{
    if(NSDecrementExtraRefCountWasZero(self))
        [self dealloc];
}

bool
NSDecrementExtraRefCountWasZero(id object)
{
    if (((struct obj_layout *) anObject) [-1].retained == 0){
        return YES;
    } else {
        ((struct obj_layout *) anObject)[-1].retained--;
        return NO;
    }
    
}

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

dealloc

- (void)dealloc
{
    NSDeallocateObject(self);
}

inline void
void NSDeallocateObject(id object)
{
    struct obj_layout *o = &((struct obj_layout *) anObject)[-1];
    free(o);
}


以上就是alloc / retain / release / dealloc在GNUstep中的实现。具体总结如下:

在objective-C的对象中存有引用计数这一整数值

调用alloc或是retain ,引用计数值加1;

调用release后,引用计数值减1;

引用计数值为0时,调用dealloc方法废弃对象



苹果的实现

各个方法分别调用的方法和函数

+alloc
+allocWithZone:
class_createInstance
calloc

-retainCount
__CFDoExterRefOperation
CFBasicHashGetCountOfKey

-retain
__CFDoExternRefOperation
CFBasicHashAddValue

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

alloc类方法流程:allocWithZone:  -->class_createInstance--> calloc分配内存块;

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 count;
        case OPERATION_release:
            count = CFBasicHashRemoveValue(table,obj);
            return 0 == count;
    }
}

retainCount / retain / release实例方法

- (NSUInteger)retainCount
{
    return (NSInteger)__CFDoExternRefOperation(OPERATION_retainCount, self);
}

- (instancetype)retain
{
    return (NSInteger)__CFDoExternRefOperation(OPERATION_retain, self);
}

- (oneway void)release
{
    return __CFDoExternRefOperation(OPERATION_release, self);
}


苹果用散列表管理引用计数,

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

好处:

CGUstep:

少量代码即可完成

能够统一管理引用计数用内存块与对象用内存块

苹果:

对象用内存块的分配无需考虑内存块头部

引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块


autorelease

使用方法:

(1)生成并持NSAutoreleasePool对象;

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

(3)废弃NSAutoreleasePool对象。


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

<span style="white-space:pre">	</span>//实例化NSAutoreleasePool对象
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        //实例化一个id对象
        id obj = [[NSObject alloc] init];
        //将实例化的id对象obj放入pool中
        [obj autorelease];
        //释放pool对象
        [pool drain];

在cocoa框架中,相当于主程序循环的NSRunLoop或者在其他程序可执行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。因此,应用程序开发者不一定非得使用NSAutoreleasePool对象进行开发工作。

NSRunLoop每次循环过程中NSAutoreleasePool对象被生成或废弃:生成NSAutoreleasePool对象 --> 应用程序主线程处理 --> 废弃NSAutoreleasePool对象


注意:在大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。

例:读入大量图像的同时改变其尺寸。图像文件读入到NSData对象,并从中生成UIImage对象,改变该对象尺寸后生成新的UIImage对象。由于没有废弃NSAutoreleasePool对象,最终导致内存不足。

<span style="white-space:pre">	</span>for (int i = 0; i < 100000000; ++i) {
            //读入图像
            //大量产生autorelease的对象
            //由于没有废弃NSAutoreleasePool对象
            //最终导致内存不足
        }


在此情况下,有必要在适当的地方生成、持有或废弃NSAutoreleasePool对象。

<span style="white-space:pre">	</span>for (int i = 0; i < 100000000; ++i) {
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
            //读入图像
            //大量产生autorelease的对象
            
            //在对象数量达到一定一定量时释放pool对象
            [pool drain];
            
            //通过[pool drain]将autorelease的对象一起release
        }


cocoa框架中返回autorelease的对象

id array = [NSMutableArray arrayWithCapacity:1];
<span style="font-family: Arial, Helvetica, sans-serif;">等同于</span>
id array1 = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];


autorelease的实现:

GNUstep中:

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

<span style="white-space:pre">	</span>[obj autorelease];
        
        
        - (instancetype)autorelease
        {
            [NSAutoreleasePool addObject:self];
        }

假想一个简单的源代码:

调用正在使用的NSAutoreleasePool对象的addObject方法

+ (void)addobject:(id)anOj
{
    NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;
    if (pool != nil) {
        [pool addObject:anOj];
    } else {
        NSLog(@"NSAutoreleasePool非存在状态下调用autorelease");
    }
}

下面代码中被赋予pool变量的即为正在使用的NSAutoreleasePool对象

<span style="white-space:pre">	</span>NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        id obj = [[NSObject alloc] init];
        [obj autorelease];

嵌套的NSAutoreleasePool,使用的是最内侧的对象

    NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];
    
    NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];
    
    NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
    
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    
    [pool2 drain];
    
    [pool1 drain];
    
    [pool0 drain];

GNUstep实现使用的是连接列表

drain方法废弃正在使用的NSAutoreleasePool对象的过程

- (void)drain
{
    [self dealloc];
}

- (void)dealloc
{
    [self emptyPool];
    [array release];
}

- (void) emptyPool
{
    for (id obj in array) {
        [obj release];
    }
}


苹果的实现

Class AutoreleasePoolPage
{
    static inline void *push()
    {
        //相当于生成或持有NSAutoreleasePool对象;
    }
    static inline void *pop()
    {
        //相当于废弃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);
}


查看NSAutoreleasePool类方法和autorelease方法的运行过程

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    //等同于obj_autoreleasePoolPush()
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    //等同于objc_autorelease(obj);
    [pool drain];
    //等同于objc_autoreleasePoolPage(pool)

注:在autorelease NSAutoreleasePool对象时会发生异常;


ARC规则

ARC只是自动的帮开发者填写retain和release,并没有改变引用计数的规则,

在ARC环境下可以设置某些类不使用ARC,-fno-objc-arc


所有权修饰符:ARC有效时所有id类型或对象类型都需要加所有权修饰符:

所有权修饰符(四种):

__strong 修饰符

__weak 修饰符

__unsafe_unretained 修饰符

__autoreleasing 修饰符


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

    id obj = [[NSObject alloc] init];
    //相当于
    id __strong obj1 = [[NSObject alloc] init];
    
    //在非ARC环境中
    id obj = [[NSObject alloc] init];
    [obj release];

    {
    //自己生成并持有对象
    id __strong obj = [[NSObject alloc] init];
    
    //因为变量obj为强引用,所以自己持有对象
    }
    //因为对象超出了范围,强引用失效,所以自动释放自己持有的对象,对象的所有者不存在,因此废弃改对象


取得非自己持有的对象时:

    id __strong obj = [NSMutableArray array];

    id __strong obj = [NSMutableArray array];
    
    {
        //取得非自己生成并持有的对象、
        id __strong obj = [NSMutableArray array];
        
        //因为变量obj为强引用,所以自己持有对象
    }
    //变量超出作用域,强引用失效,自动释放自己持有的对象,对象的所有者不存在,因此废弃该对象

附有__strong修饰符的变量之间可以互相赋值

    id __strong obj0 = [[NSObject alloc] init];//对象A,obj0持有对象A的强引用
    id __strong obj1 = [[NSObject alloc] init];//对象B,obj1持有对象B的强引用
    id __strong obj2 = nil;     //不持有任何对象
    
    obj0 = obj1;
    //obj0持有obj1所持有的对象B的强引用,因此原先持有的A对象的强引用失效,对象A的所有者不存在,因此释放对象A;此时持有对象B的强引用变量为obj0和obj1
    
    obj2 = obj0;
    //obj2持有obj0所持有的对象B的强引用,此时持有对象B的强引用的变量为:obj0,obj1,obj2
    
    obj1 = nil;
    //obj1对B的强引用失效,此时持有对象B的强引用的变量为:obj0,obj2
    obj0 = nil;
    //obj0对B的强引用失效,此时持有对象B的强引用的变量为:obj2
    obj2 = nil;
    //obj0对B的强引用失效,对象B的所有者不存在了,所以B被废弃


方法参数上也能使用__strong修饰符

#import "TestObject.h"

@interface TestObject ()
{
    id __strong obj_;
}

- (void)setObject:(id __strong)obj;

@end


@implementation TestObject

- (instancetype)init
{
    self = [super init];
    return self;
}

- (void)setObject:(id __strong)obj
{
    obj_ = obj;
}

@end


该类的使用

{
    id __strong test = [[TestObject alloc] init];
    //test持有TestObject对象
    
    [test setObject:[[NSObject alloc] init]];
    //test对象的obj_成员持有对NSObject对象的强引用
}

//超出test变量的作用域,强引用失效,自动释放TestObject对象
//TestObject对象的强引用不存在,销毁该对象

//废弃TestObject对象的同时,TestObject对象的obj_成员也被废弃,NSObject的强引用失效,自动释放NSObject对象,NSObject对象的所有者不存在,因此废弃该对象


__weak修饰符:解决循环引用的问题

使用上述TestObject的例子:

- (void)test2
{
    id test0 = [[TestObject alloc] init];
    //test0持有对象A的强引用
    
    id test1 = [[TestObject alloc] init];
    //test1持有对象B的强引用
    
    [test0 setObject:test1];
    //test0持有的对象A的obj_成员变量持有TestObject对象B的强引用
    
    [test1 setObject:test0];
    //test1持有的对象B的obj_成员变量持有TestObject对象A的强引用
    
}

//超出作用域,强引用都失效,自动释放TestObject对象A,和TestObject对象B,但发现TestObject对象A、B都有强引用没法释放,发生内存泄露

单个对象也会发生循环引用

    [test0 setObject:test0];


用__weak修饰符来修饰,使之成为弱引用:弱引用不能持有对象实例

id __weak obj = [[NSObject alloc] init];
这样写会出问题,编译器会发警告:实例化的对象得不到强引用会直接被释放掉应改为下面的写法:

<p class="p1"><span class="s1">    {</span></p><p class="p2"><span class="s2">        </span><span class="s1">//</span><span class="s3">自己生成并持有对象</span></p><p class="p1"><span class="s1">        </span><span class="s4">id</span><span class="s1"> obj1 = [[</span><span class="s5">NSObject</span><span class="s1"> </span><span class="s6">alloc</span><span class="s1">] </span><span class="s6">init</span><span class="s1">];</span></p><p class="p3"><span class="s7">        </span><span class="s8">//</span><span class="s1">因为</span><span class="s8">obj1</span><span class="s1">变量为强引用,所以自己持有对象</span></p><p class="p1"><span class="s1">        </span><span class="s4">id</span><span class="s1"> </span><span class="s4">__weak</span><span class="s1"> obj = obj1;</span></p><p class="p2"><span class="s2">        </span><span class="s1">//obj</span><span class="s3">变量为弱引用</span></p><p class="p1"><span class="s1">    }</span></p><p class="p3"><span class="s7">    </span><span class="s8">//</span><span class="s1">超出作用域,强引用失效,自动释放持有的对象,因为对象的持有者不存在,所以废弃该对象</span></p>

改写上面循环引用的例子,使之比避免循环引用:将成员变量的obj_用__weak修饰符修饰便可避免循环引用

@interface TestObject ()
{
    id __weak obj_;
}

- (void)setObject:(id __strong)obj;

@end

__weak修饰符修饰的变量持有的对象超出作用域时会被释放,__weak修饰符修饰的变量会变成空弱引用

id __weak obj1 = nil;
    {
        //自己生成并持有对象
        id __strong obj0 = [[NSObject alloc] init];
        //因为obj0变量为强引用,所以自己持有对象
        obj1 = obj0;
        //obj1变量持有对象的弱引用
        NSLog(@"1--%@",obj1);
        //输出obj1变量持有的弱引用对象
    }
    //因超出作用域,强引用失效,所以自动释放自己持有的对象,因为对象无持有者,所以废弃该对象。废弃的同时,持有该对象弱引用的obj1变量弱引用失效,nil赋值给obj1
    NSLog(@"2--%@",obj1);
    //输出赋值给obj1变量中的nil
    
    //输出:《NSObject:。。。。。。》
    //输出:(null)


__unsafe_unretained修饰符:不安全的所有权修饰符,用__unsafe_unretained修饰符的变量不属于编译器的内存管理对象

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


__unsafe_unretained  修饰的变量同附有__weak一样,上述代码会报警告,不能直接将自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放

所以要改写成

    id __unsafe_unretained obj1 = nil;
    {
        //自己生成并持有对象
        id __strong obj0 = [[NSObject alloc] init];
        //因为obj0变量为强引用,所以自己持有对象
        obj1 = obj0;
        //虽然obj0变量赋值给obj1,但是obj1既不持有对象的强引用也不持有弱引用
        NSLog(@"1--%@",obj1);
        //输出obj1变量表示的对象
    }
    //因超出作用域,强引用失效,所以自动释放自己持有的对象,因为对象无持有者,所以废弃该对象。
    NSLog(@"2--%@",obj1);
    //输出obj1变量表示的对象
    //obj1变量表示的对象,已经被废弃,错误访问
    
    //输出:《NSObject:。。。。。。》
    //输出:《NSObject:。。。。。。》

此时碰巧改对象对应的内存还没有被立即释放,但是在使用 __unsafe_unretained修饰符时,赋值给附有 __strong修饰符的变量时有必要确保被赋值的对象确实存在,否则会导致程序崩溃


__autoreleasing修饰符

在ARC有效和无效环境中的用法区别

    //ARC无效时
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];
    
    //ARC有效时
    @autoreleasepool {
        id __autoreleasing obj = [[NSObject alloc] init];
    }

可以理解为:在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法


取得非自己生成并持有的对象时,虽然可以使用alloc/new/copy/mutablecopy 以外的方法来取得对象,但该对象已被注册到了autoreleasepool。这是由于编译器会检查方法名是否以alloc/new/copy/mutablecopy 开始,如果不是则自动将返回值的对象注册到autoreleasepool

    @autoreleasepool {
        //取得非自己生成并持有的对象
        
        id  __strong obj = [NSMutableArray array];
        
        //因为变量obj为强引用,所以自己持有对象,并且该对象由编译器判断其方法名后自动注册到autoreleasepool
        
    }
    
    //超出作用域,强引用失效,所以自己释放自己持有的对象;
    //同时随着@autoreleasepool块的结束,注册到autoreleasepool中的对象被释放,因为对象的所有者不存在,所以废弃对象

@autoreleasepool也可以嵌套使用,最常见到@autoreleasepool的地方为main方法里

NSRunLoop等实现不论ARC有效还是无效,均能够随时释放注册到autoreleasepool中的对象

如果编译器版本为LLVM3.0以上,即使ARC无效@autoreleasepool块也能够使用。


因为autoreleasepool范围以块级源代码表示,提高了程序的可读性,多以今后在ARC无效时也推荐使用@autoreleasepool块

无论ARC是否有效,调试用的非公开函数_objc_autoreleasepoolPrint都可使用

利用这个函数可以有效地帮助我们调试注册到autoreleasepool上的对象



ARC使用规则

1、不能使用retain/release/retainCount/autorelease

2、不能使用NSAllocateObject/NSDeallocateObject

3、须遵守内存管理的方法命名规则:alloc,new,copy,mutablecopy;ARC环境中又加了一个init

4、不要显示调用dealloc      :不管ARC是否有效,对象的所有者不持有该对象时该对象都要废弃,对象被废弃时不管ARC是否有效,都会调用对象的dealloc方法;dealloc方法在大多数情况下还适用于删除已注册的代理或观察者对象

5、使用@autoreleasepool块替代NSAutoreleasePool

6、不能使用区域(NSZone):运行时系统中区域已被单纯的忽略了

7、对象型变量不能作为C语言结构体(struct/union)的成员:C语言的结构体成员中,如果存在Objective-C对象型变量,便会引起编译错误;不得已情况下可以将对象型变量强转成void * 或是附加前面所述的__unsafe_unretained修饰符

8、显示转换id和void* :id类型或对象型变量赋值给void * 或者逆向赋值时都需要进行特定的转化,如果只想单纯的赋值,则可以使用“__bridge转换”

 //非ARC状态下
    id obj = [[NSObject alloc] init];
    void *p = obj;
    
    id o = p;
    [o release];
    
    //ARC状态下
    id obj = [[NSObject alloc] init];
    void *p = (__bridge void *)obj;
    id o = (__bridge id)p;

转换为void*的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符很相近,甚至会更低。如果管理时不注意复制对象的所有者,就会因悬垂指针导致程序崩溃

__bridge转换中有两种转换,分别是“__bridge_retained转换” 和 "__bridge_transfer转换"

__bridge_retained转换可使要转换的变量也持有所赋值的对象,与retain类似

__bridge_transfer转换提供了与此相反的动作被转换的变量所持有的对象在该变量被赋值给转换目标变量后立即释放,与release类似

这些转换多数使用在Objective-C对象与core Foundation对象之间的相互转换,在这两种对象中转换不需要使用额外的CPU资源,因此也被成为“免费桥”


Foundation和 core Foundation(可根据个人习惯选择)

__bridge_retained CFBridgingRetained

__bridge_transfer CFBridgingRelease

retain CFRetain

release CFRelease

__bridge 不能替换__bridge_retained和CFBridgeRetain


属性

属性 所有权修饰符

assign __unsafe_unretained修饰符

copy _strong修饰符(但是赋值的是被复制的对象)

retain _strong修饰符

strong _strong修饰符

unsafe_unretained __unsafe_unretained修饰符

weak __weak修饰符

同一个属性,声明时必须保证属性一致,如:


数组

附有__strong/__weak/__autoreleasing修饰符变量的数组也保证其初始化为nil,

id *类型  默认为  id __autoreleasing *类型

__strong 修饰符的id型变量被初始化为nil,但不保证附有__strong修饰符的id指针型变量被初始化为nil


使用calloc函数确保想分配的附有__strong修饰符变量的容量占有的内存块




ARC的实现

__strong修饰符

赋值给附有__strong修饰符的变量在实际的程序中的运行细节:

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

    //编译器的模拟代码
    id obj = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    objc_release(obj);

此段代码两次调用objc_msgSend方法(alloc,init),变量作用域结束时通过objc_release释放对象


alloc / new / copy / mutableCopy以外的方法:

    {
        id __strong obj = [NSMutableArray array];
    }
    
    //编译器模拟代码
    id obj = objc_msgSend(NSMutableArray,@selector(array));
    objc_retainAutoreleasedRetureValue(obj);
    objc_release(obj);

 
objc_retainAutoreleasedRetureValue<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">函数主要用于最优化程序运行,用于自己持有对象的函数,持有的对象应为返回注册在autoreleasepool中的对象的方法,或是函数的返回值</span>

objc_retainAutoreleasedRetureValue 函数是成对的,与之对应的函数是objc_autoreleaseReturnValue,用于alloc / new / copy / mutableCopy以外的类似于NSArray的array方法返回对象的实现上

+ (id)array
{
    return [[NSMutableArray alloc] init];
}
//编译器模拟代码
+(id)array
{
    id obj = objc_msgSend(NSMutableArray,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    return objc_autoreleaseReturnValue(obj);
}

返回注册到autoreleasingpool中对象的方法使用了objc_retainAutoreleasedRetureValue函数范湖注册到autoreleasingpool中的对象。但是objc_retainAutoreleasedRetureValue函数同objc_autorelease函数不同,一半不仅限于注册对象到autoreleasepool中。

objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedRetureValue()函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedRetureValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。通过objc_autoreleaseReturnValue函数和objc_retainAutoreleasedRetureValue函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。



__weak修饰符

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

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

    {
        id __weak obj1 = obj;
    }
    
    //假设变量obj附加__strong修饰符且对象被赋值
    
    //编译器的模拟代码
    id obj1;
    objc_initWeak(&objq1,obj);
    objc_destroyWeak(&obj1);

释放对象时,废弃谁都不持有的对象,程序的动作:

1)objc_release

2)因为引用计数为0,所以执行dealloc

3)_objc_rootDealloc

4)objc_dispose

5)objc_destructInstance

6)objc_clear_deallocating

objc_clear_deallocating函数的动作如下:

1)从weak表中获取废气对象的地址为键值记录

2)将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil

3)从weak表中删除记录

4)从引用计数表中删除废弃对象的地址为键值的记录


综上所述:__weak修饰符修饰的变量所引用的对象被废弃,则将nil赋值给该变量,如果大量使用附有__weak修饰符的变量,则会消耗响应的CPU资源。良策是只在需要避免循环引用时使用__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);


与赋值时相比,多了两个函数的调用:

objc_loadWeakRetained :取出附有__weak修饰符变量所引用的对象并retain

objc_autorelease:函数将对象注册到autoreleasepool中


由此可知。因为附有__weak修饰符变量所引用的对象像这样被注册到autoreleasepool中,所以在@autoreleasepool块结束之前都可以放心使用。但是,如果大量地使用附有__weak修饰符的变量,注册到autoreleasepool的对象也会大量地增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后使用


有些类不支持__weak修饰符,但在cocoa框架类中极为罕见

还有一种情况也不能使用__weak修饰符

当allowsWeakReference 和 retainWeakReference实例方法返回no的情况

在赋值给__weak修饰符的变量时,如果赋值对象的allowsWeakReference方法返回NO,程序将异常终止

在使用__weak修饰符的变量时,当被赋值对象的retainWeakReference方法返回NO的情况下,该变量将使用“nil”



__autoreleasing修饰符

将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法

    @autoreleasepool {
        id __autoreleasing 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);

alloc / new / copy / mutableCopy方法群之外的方法中使用注册到autoreleasepool中的对象

    @autoreleasepool {
        id __autoreleasing obj = [NSMutableArray array];
    }
    
    //编译器模拟代码
    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSMutableArray,@selector(array));
    objc_retainAutoreleasedRetureValue(obj);
    objc_autorelease(obj);
    objc_autoreleasePoolPop(pool);



引用计数

获取引用计数的数值

    uintptr_t _obj_rootRetainCount(id obj)

    //__strong修饰符
    {
        id __strong obj = [[NSObject alloc] init];
        NSLog(@"retain count = %d",_objc_rootRetainCount(obj));
    }
    //输出1
    
    //__weak修饰符:weak为弱引用并不持有对象
    {
        id __strong obj = [[NSObject alloc] init];
        id __weak o = obj;
        NSLog(@"retain count = %d",_objc_rootRetainCount(obj));
    }
    //输出1
    
    //__autoreleasing修饰符:对象被强引用,并注册到autoreleasepool中
    @autoreleasepool {
        id __strong obj = [[NSObject alloc] init];
        id __autoreleasing o = obj;
        NSLog(@"retain count = %d",_objc_rootRetainCount(obj));
    }
    //输出2


_objc_rootRetainCount:次方法不能完全信任,有些已经释放完待销毁的对象在用该方法返回引用计数也会的到1,在多线程中也不一定可信










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值