Tại sao @autoreleasepool vẫn cần thiết với ARC?


191

Đối với hầu hết các phần với ARC (Đếm tham chiếu tự động), chúng ta không cần phải suy nghĩ về việc quản lý bộ nhớ với các đối tượng Objective-C. Không được phép tạo NSAutoreleasePools nữa, tuy nhiên có một cú pháp mới:

@autoreleasepool {
    
}

Câu hỏi của tôi là, tại sao tôi lại cần điều này khi tôi không được yêu cầu phát hành / tự động phát hành thủ công?


EDIT: Để tổng hợp những gì tôi nhận được từ tất cả các anwers và bình luận ngắn gọn:

Cú pháp mới:

@autoreleasepool { … } là cú pháp mới cho

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool drain];

Quan trọng hơn:

  • ARC sử dụng autoreleasecũng như release.
  • Nó cần một nhóm phát hành tự động tại chỗ để làm như vậy.
  • ARC không tạo nhóm phát hành tự động cho bạn. Tuy nhiên:
    • Chủ đề chính của mọi ứng dụng Cacao đã có một nhóm tự động điền trong đó.
  • Có hai lần bạn có thể muốn sử dụng @autoreleasepool:
    1. Khi bạn ở trong một luồng thứ cấp và không có nhóm phát hành tự động, bạn phải tự tạo để tránh rò rỉ, chẳng hạn như myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Khi bạn muốn tạo một nhóm địa phương hơn, như @mattjgalloway đã thể hiện trong câu trả lời của anh ấy.

1
Ngoài ra còn có một dịp thứ ba: Khi bạn phát triển thứ gì đó không liên quan đến UIKit hoặc NSFoundation. Một cái gì đó sử dụng các công cụ dòng lệnh hoặc như vậy
Garnik

Câu trả lời:


214

ARC không loại bỏ việc giữ lại, phát hành và tự động phát hành, nó chỉ bổ sung những yêu cầu cần thiết cho bạn. Vì vậy, vẫn có các cuộc gọi để giữ lại, vẫn có các cuộc gọi để phát hành, vẫn có các cuộc gọi để tự động phát hành và vẫn có các nhóm phát hành tự động.

Một trong những thay đổi khác mà họ đã thực hiện với trình biên dịch Clang 3.0 mới và ARC là họ đã thay thế NSAutoReleasePoolbằng @autoreleasepoolchỉ thị của trình biên dịch. NSAutoReleasePoolDù sao luôn luôn là một "đối tượng" đặc biệt và họ đã làm cho nó để cú pháp sử dụng không bị nhầm lẫn với một đối tượng để nó nói chung đơn giản hơn một chút.

Vì vậy, về cơ bản, bạn cần @autoreleasepoolbởi vì vẫn còn các nhóm phát hành tự động để lo lắng. Bạn không cần phải lo lắng về việc thêm autoreleasecác cuộc gọi.

Một ví dụ về việc sử dụng nhóm phát hành tự động:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

Một ví dụ cực kỳ khó khăn, chắc chắn, nhưng nếu bạn không có @autoreleasepoolbên trong lớp ngoài forthì bạn sẽ phát hành 100000000 đối tượng sau đó thay vì 10000 mỗi lần vòng quanh vòng ngoài for.

Cập nhật: Cũng xem câu trả lời này - https://stackoverflow.com/a/7950636/1068248 - tại sao @autoreleasepoolkhông liên quan gì đến ARC.

Cập nhật: Tôi đã xem xét nội bộ của những gì đang diễn ra ở đây và viết nó lên blog của tôi . Nếu bạn nhìn vào đó thì bạn sẽ thấy chính xác ARC đang làm gì và phong cách mới @autoreleasepoolvà cách nó giới thiệu một phạm vi được trình biên dịch sử dụng để suy luận thông tin về những gì giữ lại, phát hành và tự động yêu cầu.


11
Nó không được loại bỏ giữ lại. Nó thêm chúng vào cho bạn. Việc đếm tham chiếu vẫn đang diễn ra, nó chỉ tự động. Do đó Đếm tham chiếu tự động :-D.
mattjgalloway

6
Vậy tại sao nó không thêm vào @autoreleasepoolcho tôi? Nếu tôi không kiểm soát những gì được tự động phát hành hoặc phát hành (ARC thực hiện điều đó cho tôi), làm sao tôi biết khi nào nên thiết lập một nhóm tự động phát hành?
mk12

5
Nhưng bạn có quyền kiểm soát nơi các nhóm phát hành tự động của bạn vẫn đi. Có một cái bao quanh toàn bộ ứng dụng của bạn theo mặc định, nhưng bạn có thể muốn nhiều hơn nữa.
mattjgalloway

5
Câu hỏi hay. Bạn chỉ cần "biết". Hãy nghĩ đến việc thêm một cái giống như lý do tại sao người ta có thể, bằng ngôn ngữ GC, thêm một gợi ý cho người thu gom rác để tiếp tục và chạy một chu trình thu thập ngay bây giờ. Có thể bạn biết có rất nhiều đối tượng đã sẵn sàng để bị xóa, bạn có một vòng lặp phân bổ một loạt các đối tượng tạm thời, vì vậy bạn "biết" (hoặc Dụng cụ có thể cho bạn biết :) rằng việc thêm một nhóm phát hành xung quanh vòng lặp sẽ là một ý tưởng tốt.
Graham Perks

6
Ví dụ lặp hoạt động hoàn toàn tốt mà không cần autorelease: mỗi đối tượng được giải phóng khi biến đi ra khỏi phạm vi. Việc chạy mã mà không có autorelease sẽ chiếm một lượng bộ nhớ không đổi và hiển thị các con trỏ đang được sử dụng lại, và đặt một điểm dừng trên dealloc của đối tượng cho thấy nó được gọi một lần mỗi lần qua vòng lặp, khi objc_storeStrong được gọi. Có thể OSX làm điều gì đó ngớ ngẩn ở đây, nhưng autoreleasepool hoàn toàn không cần thiết trên iOS.
Glenn Maynard

16

@autoreleasepoolkhông autorelease bất cứ điều gì. Nó tạo ra một nhóm tự động phát hành, do đó, khi kết thúc khối, bất kỳ đối tượng nào được ARC tự động phát hành trong khi khối đang hoạt động sẽ được gửi thông báo phát hành. Hướng dẫn lập trình quản lý bộ nhớ nâng cao của Apple giải thích như vậy:

Ở cuối khối pool autorelease, các đối tượng nhận được tin nhắn autorelease trong khối được gửi một tin nhắn phát hành. Một đối tượng nhận được một tin nhắn phát hành cho mỗi lần nó được gửi một tin nhắn autorelease trong khối.


1
Không cần thiết. Đối tượng sẽ nhận được một releasetin nhắn nhưng nếu số lần giữ lại> 1 thì đối tượng sẽ KHÔNG bị hủy.
andybons

@andybons: đã cập nhật; cảm ơn. Đây có phải là một sự thay đổi từ hành vi trước ARC?
outis

Điều này là không đúng. Các đối tượng được ARC phát hành sẽ được gửi thông báo phát hành ngay khi chúng được ARC phát hành, có hoặc không có nhóm tự động phát hành.
Glenn Maynard

7

Mọi người thường hiểu nhầm ARC về một số loại bộ sưu tập rác hoặc tương tự. Sự thật là, sau một thời gian, mọi người tại Apple (nhờ các dự án llvm và clang) nhận ra rằng quản trị bộ nhớ của Objective-C (tất cả retainsreleases, v.v.) có thể được tự động hóa hoàn toàn vào thời gian biên dịch . Đây là, chỉ bằng cách đọc mã, ngay cả trước khi nó được chạy! :)

Để làm như vậy, chỉ có một điều kiện: Chúng ta PHẢI tuân theo các quy tắc , nếu không trình biên dịch sẽ không thể tự động hóa quy trình tại thời điểm biên dịch. Vì vậy, để đảm bảo rằng chúng tôi không bao giờ phá vỡ các quy tắc, chúng ta không được phép một cách rõ ràng ghi release, retainvv Những cuộc gọi được tự động tiêm vào mã của chúng tôi bởi trình biên dịch. Do đó trong nội bộ chúng tôi vẫn có autoreleases, retain, release, vv Nó chỉ là chúng ta không cần phải viết họ nữa.

A của ARC là tự động tại thời gian biên dịch, tốt hơn nhiều so với thời gian chạy như thu gom rác.

Chúng tôi vẫn có @autoreleasepool{...}vì nó không vi phạm bất kỳ quy tắc nào, chúng tôi có thể tự do tạo / thoát hồ bơi của chúng tôi bất cứ khi nào chúng tôi cần :).


1
ARC là tham chiếu đếm GC, không phải là GC đánh dấu và quét như bạn có trong JavaScript và Java, nhưng đó chắc chắn là bộ sưu tập rác. Điều này không giải quyết câu hỏi - "bạn có thể" không trả lời câu hỏi "tại sao bạn nên". Bạn không nên.
Glenn Maynard

3

Đó là bởi vì bạn vẫn cần cung cấp cho trình biên dịch các gợi ý về thời điểm an toàn cho các đối tượng tự động phát hành ngoài phạm vi.


Bạn có thể cho tôi một ví dụ về khi nào bạn sẽ cần phải làm điều này?
mk12


Vì vậy, trước ARC, chẳng hạn, tôi đã có CVDisplayLink chạy trên luồng thứ cấp cho ứng dụng OpenGL của mình, nhưng tôi đã không tạo nhóm tự động chạy trong runloop của nó vì tôi biết tôi không tự động phát hiện bất cứ điều gì (hoặc sử dụng các thư viện làm như vậy). Điều đó có nghĩa là bây giờ tôi cần phải thêm @autoreleasepoolvì tôi không biết liệu ARC có thể quyết định tự động xóa thứ gì đó không?
mk12

@ Mk12 - Không. Bạn sẽ luôn có một nhóm phát hành tự động bị cạn kiệt mỗi lần vòng quanh vòng chạy chính. Bạn chỉ cần thêm một cái khi bạn muốn đảm bảo rằng các đối tượng đã được tự động thoát sẽ bị thoát trước khi chúng có thể - ví dụ, lần sau, vòng tiếp theo sẽ chạy vòng lặp.
mattjgalloway

2
@DougW - Tôi đã xem xét trình biên dịch thực sự đang làm gì và viết blog về nó ở đây - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- . Hy vọng giải thích những gì đang diễn ra ở cả thời gian biên dịch và thời gian chạy.
mattjgalloway

2

Được trích dẫn từ https://developer.apple.com/l Library / mac / documentation / Coloa / Conceptionual / MemoryMgmt / Articles / mmAutoreleasePools.html :

Autorelease Pool Blocks và Chủ đề

Mỗi luồng trong một ứng dụng Cacao duy trì chồng khối khối tự động tự động. Nếu bạn đang viết một chương trình chỉ dành cho Foundation hoặc nếu bạn tách một chuỗi, bạn cần tạo khối pool tự động cho riêng mình.

Nếu ứng dụng hoặc luồng của bạn tồn tại lâu và có khả năng tạo ra nhiều đối tượng tự động phát hành, bạn nên sử dụng các khối pool tự động (như AppKit và UIKit thực hiện trên luồng chính); mặt khác, các đối tượng tự động tích lũy và dấu chân bộ nhớ của bạn tăng lên. Nếu chuỗi tách rời của bạn không thực hiện cuộc gọi Ca cao, bạn không cần phải sử dụng khối nhóm tự động điền.

Lưu ý: Nếu bạn tạo các luồng thứ cấp bằng API luồng POSIX thay vì NSThread, bạn không thể sử dụng Ca cao trừ khi Ca cao ở chế độ đa luồng. Ca cao chỉ vào chế độ đa luồng sau khi tách đối tượng NSThread đầu tiên của nó. Để sử dụng Cacao trên các luồng POSIX thứ cấp, trước tiên, ứng dụng của bạn phải tách ra ít nhất một đối tượng NSThread, có thể thoát ngay lập tức. Bạn có thể kiểm tra xem Cốc Cốc có ở chế độ đa luồng hay không với phương thức lớp NSThread làMultiThreaded.

...

Trong Đếm tham chiếu tự động, hoặc ARC, hệ thống sử dụng cùng một hệ thống đếm tham chiếu như MRR, nhưng nó chèn vào phương thức quản lý bộ nhớ phù hợp gọi cho bạn tại thời gian biên dịch. Bạn được khuyến khích sử dụng ARC cho các dự án mới. Nếu bạn sử dụng ARC, thông thường không cần phải hiểu triển khai cơ bản được mô tả trong tài liệu này, mặc dù trong một số trường hợp có thể hữu ích. Để biết thêm về ARC, xem Chuyển đổi sang Ghi chú phát hành ARC.


2

Các nhóm tự động chạy được yêu cầu để trả về các đối tượng mới được tạo từ một phương thức. Ví dụ, hãy xem xét đoạn mã này:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

Chuỗi được tạo trong phương thức sẽ có số giữ lại là một. Bây giờ ai sẽ cân bằng mà giữ lại số lượng với một bản phát hành?

Phương pháp tự? Không thể, nó phải trả về đối tượng đã tạo, vì vậy nó không được giải phóng nó trước khi quay trở lại.

Người gọi phương thức? Người gọi không mong muốn truy xuất một đối tượng cần giải phóng, tên phương thức không ngụ ý rằng một đối tượng mới được tạo, nó chỉ nói rằng một đối tượng được trả về và đối tượng được trả về này có thể là một đối tượng mới yêu cầu phát hành nhưng nó có thể như cũng là một cái hiện có mà không. Phương thức nào trả về thậm chí có thể phụ thuộc vào một số trạng thái bên trong, vì vậy người gọi không thể biết liệu nó có phải giải phóng đối tượng đó hay không và nó không cần phải quan tâm.

Nếu người gọi phải luôn giải phóng tất cả các đối tượng được trả lại theo quy ước, thì mọi đối tượng không được tạo mới sẽ luôn phải được giữ lại trước khi trả về từ một phương thức và nó sẽ phải được người gọi giải phóng khi nó ra khỏi phạm vi, trừ khi nó được trả lại một lần nữa Điều này sẽ rất kém hiệu quả trong nhiều trường hợp vì người ta hoàn toàn có thể tránh thay đổi số lượng giữ lại trong nhiều trường hợp nếu người gọi sẽ không luôn luôn giải phóng đối tượng trả lại.

Đó là lý do tại sao có các bể tự động, vì vậy phương thức đầu tiên trên thực tế sẽ trở thành

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

Gọi autoreleasemột đối tượng sẽ thêm nó vào nhóm autorelease, nhưng điều đó thực sự có nghĩa gì, thêm một đối tượng vào pool autorelease? Chà, điều đó có nghĩa là nói với hệ thống của bạn " Tôi muốn bạn giải phóng đối tượng đó cho tôi nhưng vào một lúc nào đó, không phải bây giờ, nó có một số giữ lại cần được cân bằng bởi một bản phát hành nếu không bộ nhớ sẽ bị rò rỉ nhưng tôi không thể tự làm điều đó ngay bây giờ, vì tôi cần đối tượng tồn tại ngoài phạm vi hiện tại của mình và người gọi của tôi sẽ không làm điều đó cho tôi, nên tôi không biết điều này cần phải được thực hiện. Vì vậy, hãy thêm nó vào nhóm của bạn và một khi bạn dọn sạch nó hồ bơi, cũng làm sạch đối tượng của tôi cho tôi. "

Với ARC, trình biên dịch sẽ quyết định cho bạn khi nào nên giữ lại một đối tượng, khi nào phát hành một đối tượng và khi nào thêm nó vào một nhóm tự động phát hành nhưng nó vẫn yêu cầu sự hiện diện của các nhóm tự động để có thể trả về các đối tượng mới được tạo từ các phương thức mà không bị rò rỉ bộ nhớ. Apple vừa thực hiện một số tối ưu hóa tiện lợi cho mã được tạo, đôi khi sẽ loại bỏ các nhóm tự động phát hành trong thời gian chạy. Những tối ưu hóa này yêu cầu cả hai, người gọi và callee đều đang sử dụng ARC (hãy nhớ trộn ARC và không ARC là hợp pháp và cũng được hỗ trợ chính thức) và nếu đó thực sự chỉ có thể được biết trong trường hợp chạy.

Hãy xem xét Mã ARC này:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

Mã mà hệ thống tạo ra, có thể hoạt động giống như mã sau (đó là phiên bản an toàn cho phép bạn tự do trộn mã ARC và mã không ARC):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Lưu ý việc giữ lại / giải phóng trong người gọi chỉ là giữ an toàn phòng thủ, không bắt buộc, mã sẽ hoàn toàn chính xác nếu không có nó)

Hoặc nó có thể hoạt động như mã này, trong trường hợp cả hai được phát hiện để sử dụng ARC khi chạy:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

Như bạn có thể thấy, Apple loại bỏ atuorelease, do đó cũng giải phóng đối tượng bị trì hoãn khi hồ bơi bị phá hủy, cũng như giữ lại sự an toàn. Để tìm hiểu thêm về cách có thể và những gì thực sự xảy ra đằng sau hậu trường, hãy xem bài đăng trên blog này.

Bây giờ đến câu hỏi thực tế: Tại sao một người sẽ sử dụng @autoreleasepool?

Đối với hầu hết các nhà phát triển, ngày nay chỉ còn một lý do để sử dụng cấu trúc này trong mã của họ và đó là để giữ cho dung lượng bộ nhớ nhỏ khi áp dụng. Ví dụ, hãy xem xét vòng lặp này:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Giả sử rằng mọi cuộc gọi để tempObjectForDatacó thể tạo một cuộc gọi mới TempObjectđược trả lại tự động. Vòng lặp for sẽ tạo ra một triệu các đối tượng tạm thời được thu thập trong autoreleasepool hiện tại và chỉ khi hồ bơi đó bị phá hủy, tất cả các đối tượng tạm thời cũng bị phá hủy. Cho đến khi điều đó xảy ra, bạn có một triệu đối tượng tạm thời trong bộ nhớ.

Nếu bạn viết mã như thế này thay vào đó:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Sau đó, một nhóm mới được tạo mỗi khi vòng lặp for chạy và bị hủy ở cuối mỗi lần lặp. Bằng cách đó, nhiều nhất một đối tượng tạm thời đang lảng vảng trong bộ nhớ bất cứ lúc nào mặc dù vòng lặp chạy một triệu lần.

Trước đây, bạn cũng thường phải tự quản lý các autoreleasepools khi quản lý các chủ đề (ví dụ như sử dụng NSThread) vì chỉ có chủ đề chính tự động có một nhóm tự động phát hành cho ứng dụng Cacao / UIKit. Tuy nhiên, điều này là khá nhiều di sản ngày hôm nay vì ngày nay bạn có thể sẽ không sử dụng chủ đề để bắt đầu. Bạn sẽ sử dụng GCD DispatchQueuehoặc NSOperationQueuecủa cả hai và cả hai đều quản lý nhóm tự động cấp cao nhất cho bạn, được tạo trước khi chạy một khối / tác vụ và bị hủy khi hoàn thành.


-4

Dường như có rất nhiều nhầm lẫn về chủ đề này (và ít nhất 80 người có lẽ hiện đang bối rối về điều này và nghĩ rằng họ cần phải rắc @autoreleasepool xung quanh mã của họ).

Nếu một dự án (bao gồm cả phụ thuộc của nó) chỉ sử dụng ARC, thì @autoreleasepool không bao giờ cần phải được sử dụng và sẽ không có gì hữu ích. ARC sẽ xử lý việc giải phóng các đối tượng vào đúng thời điểm. Ví dụ:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

hiển thị:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Mỗi đối tượng Kiểm tra được giải quyết ngay khi giá trị vượt quá phạm vi, không cần chờ nhóm tự động thoát ra. (Điều tương tự cũng xảy ra với ví dụ NSNumber; điều này chỉ cho phép chúng ta quan sát dealloc.) ARC không sử dụng autorelease.

Lý do @autoreleasepool vẫn được cho phép là dành cho các dự án hỗn hợp ARC và không ARC, chưa hoàn toàn chuyển sang ARC.

Nếu bạn gọi vào mã không ARC, có thể trả về một đối tượng tự động phát hành. Trong trường hợp đó, vòng lặp trên sẽ bị rò rỉ, vì nhóm tự động thoát hiện tại sẽ không bao giờ được thoát. Đó là nơi bạn muốn đặt một @autoreleasepool xung quanh khối mã.

Nhưng nếu bạn đã hoàn toàn thực hiện quá trình chuyển đổi ARC, thì hãy quên đi autoreleasepool.


4
Câu trả lời này là sai và cũng đi ngược lại tài liệu ARC. bằng chứng của bạn là giai thoại bởi vì bạn đang sử dụng một phương thức phân bổ mà trình biên dịch quyết định không tự động chạy. Bạn có thể dễ dàng thấy điều này không hoạt động nếu bạn tạo một trình khởi tạo tĩnh mới cho lớp tùy chỉnh của mình. Tạo trình khởi tạo này và sử dụng nó trong vòng lặp của bạn : + (Testing *) testing { return [Testing new] }. Sau đó, bạn sẽ thấy dealloc sẽ không được gọi cho đến sau này. Điều này được cố định nếu bạn bọc bên trong vòng lặp trong một @autoreleasepoolkhối.
Dima

@Dima Đã thử trên iOS10, dealloc được gọi ngay sau khi in địa chỉ đối tượng. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC

@KudoCC - Tôi cũng vậy và tôi đã thấy hành vi tương tự như bạn đã làm. Nhưng, khi tôi ném [UIImage imageWithData]vào phương trình, đột nhiên, tôi bắt đầu thấy autoreleasehành vi truyền thống , đòi hỏi @autoreleasepoolphải giữ bộ nhớ cao nhất ở mức hợp lý.
Cướp

@Rob Tôi không thể giúp mình thêm liên kết .
KudoCC
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.