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 autorelease
mộ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 để tempObjectForData
có 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 DispatchQueue
hoặc NSOperationQueue
củ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.