天天看點

Objective-C進階程式設計筆記一(自動引用計數)

示例代碼下載下傳

手動引用計數

MRC記憶體管理的思考方式

  • 自己生成的對象自己持有
  • 不是自己生成的對象,自己也能持有
  • 不在需要自己持有的對象時釋放
  • 不是自己持有的對象無法釋放

對象操作與Objective-C方法的對應

對象操作 Objective-C方法
生成并持有對象 alloc/new/copy/mutableCopy等方法
持有對象 retain方法
釋放對象 release方法
廢棄對象 dealloc方法

實作一個MRCObject類:

@implementation MRCObject
- (void)dealloc {
    NSLog(@"%@(%@)銷毀了", NSStringFromClass(self.class), self);
    
    [super dealloc];
}
+ (instancetype)object {
    MRCObject *obj = [self allocObject];
    [obj autorelease];
    return obj;
}

+ (instancetype)allocObject {
    MRCObject *obj = [[MRCObject alloc] init];
    NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);
    
    return obj;
}

@end
           

自己生成并持有對象:

MRCObject *obj = [MRCObject allocObject];
           

不是自己生成的對象也能持有:

MRCObject *obj = [MRCObject object];
[obj retain];
           

不在需要自己持有的對象時釋放:

MRCObject *obj = [self allocObject];
[obj release];
           

無法釋放自己沒有持有的對象:

MRCObject *obj = [self allocObject];
[obj release];
[obj release];//會奔潰
           

autorelease

autorelease像c語言的自動變量來對待對象執行個體,當超出其作用域(相當于變量作用域),對象執行個體的release方法被調用。與c語言自動變量不同的是,可以autorelease的作用域。

autorelease的使用方法:

  • 生成NSAutoreleasePool對象
  • 調用已配置設定對象執行個體的autorelease方法
  • 廢棄NSAutoreleasePool對象

在應用程式中,由于主線程的NSRunloop對NSAutoreleasePool對象進行生成、持有和廢棄處理。是以開發者不一定非得使用NSAutoreleasePool對象來進行開發工作。如下圖:

Objective-C進階程式設計筆記一(自動引用計數)

在大量産生autorelease對象時,隻要不廢棄NSAutoreleasePool對象,autorelease對象就不會被釋放,是以會産生記憶體不足的現象。如下兩段代碼:

for (int index = 0; index < 1000; index++) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"1553667540126" ofType:@"jpeg"];
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
        [image autorelease];
    }
           
for (int index = 0; index < 1000; index++) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        NSString *path = [[NSBundle mainBundle] pathForResource:@"1553667540126" ofType:@"jpeg"];
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
        [image autorelease];
        [pool drain];
    }
           

ARC

ARC規則

ARC有效時,id類型和對象類型同c語言其他類型不同,必須添加所有權修飾符。共如下4種所有權修飾符:

  • __strong修飾符
  • __weak修飾符
  • __unsafe_unretained修飾符
  • __outoreleasing修飾符

#import “ARCObject.h”

實作一個ARCObject類:

@interface ARCObject ()
{
    __strong id _strongObj;
    __weak id _weakObj;
}

@end

@implementation ARCObject

- (void)dealloc {
    NSLog(@"%@(%@)銷毀了", NSStringFromClass(self.class), self);
}
+ (instancetype)allocObject {
    ARCObject *obj = [[ARCObject alloc] init];
    NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);
    return obj;
}
- (void)setStrongObject:(id)obj {
    _strongObj = obj;
}
- (void)setWeakObject:(id)obj {
    _weakObj = obj;
}

@end
           

__strong修飾符

__strong修飾符是所有id類型和對象類型預設的所有權修飾符,表示對對象的強引用,在超出其作用域或被重新指派時被廢棄。

{
    ARCObject *obj = [ARCObject allocObject];
    NSLog(@"作用域最後一行%@", obj);
}
NSLog(@"作用域已經結束");
           
ARCObject *obj = [ARCObject allocObject];
NSLog(@"重新指派前%@", obj);
obj = [ARCObject allocObject];
NSLog(@"重新指派前後%@", obj);
           

__strong、__weak、__outoreleasing修飾符的自動變量預設初始化為nil。

__weak修飾符

__weak修飾符與__strong修飾符相反,提供弱引用,弱引用不持有對象執行個體。

循環引用容易發生記憶體洩漏,記憶體洩漏就是應當廢棄的對象在超出其生存周期後依然存在。可以使用__weak修飾符來避免。

ARCObject *aObj = [ARCObject allocObject];
ARCObject *bObj = [ARCObject allocObject];
[aObj setStrongObject:bObj];
[bObj setStrongObject:aObj];
           
ARCObject *obj = [ARCObject allocObject];
[obj setStrongObject:obj];
           
ARCObject *aObj = [ARCObject allocObject];
ARCObject *bObj = [ARCObject allocObject];
ARCObject *cObj = [ARCObject allocObject];
[aObj setWeakObject:bObj];
[bObj setWeakObject:aObj];
[cObj setWeakObject:cObj];
           

__weak修飾符有一個優點就是:在持有某對象的弱引用時,如果該對象被廢棄,則該對象弱引用自動失效且被置為nil。

__unsafe_unretained修飾符

__unsafe_unretained修飾符,正如其名一樣是不安全的所有權修飾符。盡管ARC的記憶體管理是編譯器的工作,但是這一點需要注意特别注意,__unsafe_unretained修飾符的變量不屬于編譯器記憶體管理的對象。

__unsafe_unretained修飾符和__weak修飾符的變量一樣不會持有對象,但是__unsafe_unretained修飾符的變量在銷毀時并不會自動置為nil,在其位址被覆寫後就會因為反問垂懸指正而造成奔潰。是以__unsafe_unretained修飾符變量指派給__strong修飾符變量時要確定對象的真實存在。因為__weak修飾符是在iOS5中實作的,__unsafe_unretained修飾符存在的意義就是在iOS4中代替__weak修飾符的作用。

ARCObject __unsafe_unretained *obj = nil;
{
    ARCObject *obj1 = [ARCObject allocObject];
    obj = obj1;
}
NSLog(@"%@(%@)", NSStringFromClass(obj.class), obj);
           

__outoreleasing修飾符

ARC有效時不能使用outorelease方法,也不能使用NSAutoreleasePool類。這樣就導緻outorelease無法直接使用,但實際上outorelease功能是起作用的。使用@outoreleasepool{}塊代碼來代替NSAutoreleasePool類對象的生成持有以及廢棄。通過指派給__outoreleasing修飾符的變量來代替調用outorelease方法,也就是說對象被注冊到autoreleasepool中。

@autoreleasepool {
    ARCObject __autoreleasing *obj1 = [ARCObject allocObject];
    NSLog(@"autoreleasepool塊最後一行%@", obj1);
}
NSLog(@"autoreleasepool塊已經結束");
           

ARC有效時,cocoa中由于編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開始,如果不是則自動将傳回值的對象注冊到outoreleasepool中。是以非顯示的使用__outoreleasing修飾符也是可以的。

NSMutableArray __weak *array = nil;
NSLog(@"作用域塊開始前%@", array);
{
    NSMutableArray *arr = [NSMutableArray arrayWithObject:@(1)];
    array = arr;
    NSLog(@"作用域塊最後一行%@", array);
}
NSLog(@"作用域塊已經結束%@", array);
           

列印結果:

2019-03-28 11:56:52.316360+0800 ProfessionalExample[82984:16680615] 作用域塊開始前(null)
2019-03-28 11:56:52.316538+0800 ProfessionalExample[82984:16680615] 作用域塊最後一行(
    1
)
2019-03-28 11:56:52.316627+0800 ProfessionalExample[82984:16680615] 作用域塊已經結束(
    1
)
           

id的指針和對象的指針在沒有顯式指定修飾符時會被附加上__outoreleasing修飾符。

- (BOOL)performOperationWithError:(ARCObject **)obj {
    *obj = [ARCObject object];
    return NO;
}
           

調用方法則為如下所示,自動轉化為__autoreleasing修飾符:

[self performOperationWithError:<#(ARCObject *__autoreleasing *)#>];
           

id的指針和對象的指針變量必須指明所有權修飾符,并且指派的所有權修飾符必須一緻:

NSObject **pObj;//編報錯,沒有所有權修飾符
           
NSObject *obj = [[NSObject alloc] init];
NSObject *__autoreleasing*pObj = &obj;//編譯報錯,會更改所有權屬性
           

糾正一個比較普遍的錯誤認知,for循環中并不是循環結束才釋放循環内的局部變量,并不是所有産生大量對象的for循環中都需要加NSAutoreleasePool,而是産生大量autorelease對象時才需要添加。如下示例代碼:

for (int index = 0; index < 2; index++) {
        if (index == 0) {
            NSLog(@"-------------begin");
            ARCObject *obj = [[ARCObject alloc] init];
            NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);
        }
        if (index == 1) {
            NSLog(@"-------------end");
        }
    }
           

下面是這段代碼的列印内容:

2019-03-28 15:27:19.179194+0800 ProfessionalExample[85692:16955598] -------------begin
2019-03-28 15:27:19.179366+0800 ProfessionalExample[85692:16955598] ARCObject(<ARCObject: 0x600001ded3a0>)生成了
2019-03-28 15:27:19.179449+0800 ProfessionalExample[85692:16955598] ARCObject(<ARCObject: 0x600001ded3a0>)銷毀了
2019-03-28 15:27:19.179521+0800 ProfessionalExample[85692:16955598] -------------end
           

ARC編碼規則

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 須遵守記憶體管理的方法命名規則
  • 不能顯式調用dealloc方法
  • 使用@autoreleasepool{}代替NSAutoreleasePool
  • 不能使用NSZone
  • 對象變量不能作為c語言結構體的成員
  • 顯式轉換id和void *

記憶體管理的方法命名規則

以alloc/new/copy/mutableCopy開頭的方法傳回對象時,必須傳回給調用方應當持有的對象。這在ARC有效時是一樣的,不同的是以init開頭的方法必須是執行個體方法且需要傳回對象,該傳回對象并不注冊到autoreleasepool上。

對象變量不能作為c語言結構體的成員

要把對象類型變量加入到結構體中,需強制轉為void *或者前面附加__unsafe_unretained修飾符。

顯式轉換id和void *

可以使用(__bridge)轉換void *和OC對象,但是其安全性和指派給__unsafe_unretained修飾符相近或者更低。如果管理時不注意指派對象的所有者就會因為垂懸指針而奔潰或者記憶體洩漏。

NSObject *obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
obj = (__bridge NSObject *)p;
           

__bridge_retained轉換可使要指派的變量持有所指派的變量。__bridge_transfer則與之相反。

NSObject *obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
obj = (__bridge_transfer NSObject *)p;
           

NSObject對象與Core Fundation對象之間的互相轉換,即免費橋(Toll-Freee-Bridge)轉換。CFBridgingRetain函數(等價于__bridge_retained轉換),CFBridgingRelease函數(等價于__bridge_transfer)。

NSObject *obj = [[NSObject alloc] init];
CFTypeRef ref = CFBridgingRetain(obj);
obj = CFBridgingRelease(ref);
           

屬性

屬性聲明的屬性與所有權修飾符對應關系

屬性聲明的屬性 所有權修飾符
assign __unsafe_unretained修飾符
copy __strong修飾符(但是指派的是被指派過來的)
retain __strong修飾符
unsafe_unretained __unsafe_unretained修飾符
weak __weak修飾符

c數組

c靜态數組,各修飾符的使用OC對象一樣沒有差別。

以__strong為例,其初始化為nil,超過作用域銷毀:

{
    ARCObject *array[2];
    array[0] = [ARCObject allocObject];
    NSLog(@"array第一個元素:%@", array[0]);
    NSLog(@"array第二個元素:%@", array[1]);
    array[1] = nil;
    NSLog(@"array第二個元素:%@", array[1]);
}
NSLog(@"作用域塊已經結束");
           

列印結果:

2019-03-28 19:19:26.697408+0800 ProfessionalExample[88859:17353905] ARCObject(<ARCObject: 0x6000005f8500>)生成了
2019-03-28 19:19:26.697661+0800 ProfessionalExample[88859:17353905] array第一個元素:<ARCObject: 0x6000005f8500>
2019-03-28 19:19:26.697761+0800 ProfessionalExample[88859:17353905] array第二個元素:(null)
2019-03-28 19:19:26.697845+0800 ProfessionalExample[88859:17353905] array第二個元素:(null)
2019-03-28 19:19:26.697930+0800 ProfessionalExample[88859:17353905] ARCObject(<ARCObject: 0x6000005f8500>)銷毀了
2019-03-28 19:19:26.697995+0800 ProfessionalExample[88859:17353905] 作用域塊已經結束
           

c動态數組,c語言中動态數組聲明用指針即id *array(NSObject **array)。需要注意如下幾點:

  • _strong/__weak修飾符的OC變量初始化為nil,并不代表其指針初始化為nil。是以配置設定記憶體後,需要對其初始化為nil,否則非常危險。calloc函數配置設定的就是nil初始化後的記憶體,malloc函數配置設定記憶體後必須使用memset将記憶體填充為0(nil)。
  • 必須置空_strong修飾符的态數數組内的元素,使其強引用失效,元素才能釋放。因為動态數組的生命周期有開發者管理,編譯器不能确定銷毀動态數組内元素的時機。
{
    ARCObject *__strong *array;
    array = (ARCObject *__strong *)calloc(2, sizeof(ARCObject *));
    NSLog(@"array第一個元素:%@", array[0]);
    NSLog(@"array第二個元素:%@", array[1]);
    array[0] = [ARCObject allocObject];
    array[1] = [ARCObject allocObject];
    array[0] = nil;
    NSLog(@"array第一個元素:%@", array[0]);
    NSLog(@"array第二個元素:%@", array[1]);
    free(array);
}
NSLog(@"作用域塊已經結束");
           

列印結果:

2019-03-28 19:29:26.162245+0800 ProfessionalExample[89048:17394552] array第一個元素:(null)
2019-03-28 19:29:26.162586+0800 ProfessionalExample[89048:17394552] array第二個元素:(null)
2019-03-28 19:29:26.162763+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a32b40>)生成了
2019-03-28 19:29:26.162867+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a395c0>)生成了
2019-03-28 19:29:26.162945+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a32b40>)銷毀了
2019-03-28 19:29:26.163011+0800 ProfessionalExample[89048:17394552] array第一個元素:(null)
2019-03-28 19:29:26.163083+0800 ProfessionalExample[89048:17394552] array第二個元素:<ARCObject: 0x600001a395c0>
2019-03-28 19:29:26.163160+0800 ProfessionalExample[89048:17394552] 作用域塊已經結束