2013-07-23 16 views
7

Hãy nói rằng tôi đang cố gắng để truy cập self từ bên trong một khối:ARC __block và __weak

[someObject successBlock:^(NSArray *result) { 
    [self someSuccessMethod]; 
} failure:^(NSString *errorMessage, int status) { 
    [self someFailureMethod]; 
}]; 

Tôi hiểu rằng điều này tạo ra một chu trình duy trì và someObjectself không bao giờ có được de-alloced.

Điều khiến tôi khó hiểu là điều thực sự xảy ra với/không có từ khóa __block. Tôi có thể sửa chữa các chu kỳ giữ lại bằng cách làm cho một tham chiếu __weak tự:

__weak MyClass* me = self; 
[someObject successBlock:^(NSArray *result) { 
    [me someSuccessMethod]; 
} failure:^(NSString *errorMessage, int status) { 
    [me someFailureMethod]; 
}]; 

Tôi không cần phải sử dụng __block đây, bởi vì tôi không cố gắng để sửa đổi me từ bên trong khối. Từ những gì tôi hiểu, nếu tôi không sử dụng __block, một bản sao của me được tham chiếu bên trong khối. Câu hỏi của tôi là: nếu những gì đang được tham chiếu bên trong khối chỉ là một bản sao của đối tượng, tại sao khối mã ban đầu tạo chu trình giữ lại? Tôi đoán rằng tham chiếu đến self chỉ là một bản sao vì tôi không bao giờ sử dụng từ khóa __block. Tôi đang nghĩ về điều này không chính xác?

Trả lời

7

Trong trường hợp đầu tiên, khối chụp self, tức là nó lưu bản sao self làm một con trỏ mạnh khác. Điều đó làm tăng số lượng lưu giữ của đối tượng được trỏ tới và gây ra chu kỳ lưu giữ.

Trong trường hợp thứ hai, khối chụp me, tức là nó lưu một bản của me như một yếu con trỏ. Điều đó không làm tăng số lượng giữ lại và do đó gây ra không có chu kỳ giữ lại.

(Nếu bạn in địa chỉ của me bên ngoài và bên trong khối, bạn sẽ thấy rằng các địa chỉ khác nhau. Các khối có con trỏ yếu riêng của mình để đối tượng.)

Nếu pointed- đối tượng được deallocated, tất cả các tài liệu tham khảo yếu (bao gồm cả một tài liệu được lưu bởi khối) được thiết lập để nil bởi thời gian chạy Objective-C.

(Tôi chỉ hy vọng rằng tôi có quyền này.)

+0

giả sử rằng MyCLass thực hiện một bản sao là bản sao thực ... vì '-copyWithZone:' chỉ có thể giữ lại ... hoàn toàn hợp pháp và được thực hiện chỉ trong bất kỳ đối tượng bất biến nào. –

+0

@GradyPlayer: Có lẽ tôi đã thể hiện bản thân mình, nhưng ý tôi là khối sẽ lưu một con trỏ mạnh (hoặc yếu) trong bối cảnh khối của nó với * nội dung hiện tại * của 'self' (hoặc' me'). Phương thức 'copy' * * không liên quan. –

+0

Vâng đôi khi SO quay trở lại đầu trang khi ai đó làm điều gì đó với họ ... và đôi khi tôi phải có một vài tháng hoặc vài tháng sau đó ... nhưng các đối tượng có thể được sao chép khi chụp khối vì vậy tôi không nghĩ rằng đó là không chính xác ... –

0

Bạn có thể con đường tự như là đối số khối của, chính xác cho tên biến 'tự', điều này sẽ bảo vệ từ selfretaining trong khối.

Và bạn không phù hợp với 'someObject và tự không bao giờ bị phân bổ': bản thân sẽ được phát hành khi khối được deallocated. Các khối sẽ được deallocated với someObject. SomeObject sẽ được deallocated khi nó không có thêm tài liệu tham khảo. Vì vậy, nếu đối tượng của bạn sở hữu someObject, chỉ cần giải phóng someObject khi bạn không cần nó nữa.

4

A giữ lại chu kỳ xảy ra khi hai đối tượng lưu trữ tham chiếu mạnh mẽ với nhau. Trường hợp đơn giản nhất là đối tượng a lưu trữ tham chiếu mạnh đến đối tượng bb thực hiện ngược lại [1]. Chu kỳ lưu giữ là một vấn đề trong Objective-C bởi vì chúng làm cho ARC tin rằng các đối tượng này luôn được sử dụng ngay cả khi các đối tượng này không được tham chiếu từ bất kỳ nơi nào khác.

Hãy xem lại một số ví dụ. Bạn có đối tượng z phân bổ ab, sử dụng chúng và sau đó xử lý chúng.Nếu ab tạo chu kỳ giữ chân giữa chúng ở địa điểm đầu tiên, ab sẽ không bị phân phối lại. Nếu bạn làm điều đó nhiều lần bạn sẽ bị rò rỉ nghiêm trọng bộ nhớ.

Một ví dụ thế giới thực sự của một chu kỳ giữ lại là nếu a giao đất và mạnh mẽ tham chiếu đến một đối tượng b, nhưng bạn cũng lưu trữ một tham chiếu mạnh b-a (nhiều vật thể nhỏ trong đồ thị đối tượng có thể cần phải truy cập vào cha mẹ).

Các giải pháp thông thường nhất trong các trường hợp này là đảm bảo rằng các đối tượng chứa chỉ có tham chiếu yếu đối với các đối tượng chứa của nó và cũng đảm bảo rằng các đối tượng anh chị em không chứa tham chiếu mạnh mẽ với nhau.

Một giải pháp khác (thường ít thanh lịch hơn, nhưng có thể thích hợp trong một số trường hợp) có thể có một số phương thức tùy chỉnh cleanup trong a mà tham chiếu đến b. Do đó, b sẽ bị phân phối khi cleanup được gọi (nếu b không được tham chiếu mạnh ở nơi khác). Điều này là cồng kềnh vì bạn không thể làm điều này từ a 's dealloc (nó không bao giờ được gọi là nếu có một chu kỳ giữ lại) và bởi vì bạn phải nhớ gọi cleanup vào thời điểm thích hợp.

  1. Lưu ý rằng duy trì chu kỳ cũng là bắc cầu (ví dụ, đối tượng a mạnh tham chiếu b mà mạnh mẽ tham chiếu c mà mạnh mẽ tham chiếu a).

Với tất cả điều này cho biết: quản lý bộ nhớ của khối là khá khó khăn để hiểu được.

Ví dụ đầu tiên của bạn có thể tạo tạm thời chu kỳ lưu giữ (và chỉ khi đối tượng self của bạn lưu trữ tham chiếu mạnh mẽ đến someObject). Chu kỳ lưu giữ tạm thời này sẽ biến mất khi khối kết thúc thực thi và được deallocated.

Trong thực hiện, self sẽ lưu trữ một tham chiếu đến someObject, someObject đến block, và block-self một lần nữa. Nhưng một lần nữa, nó chỉ là tạm thời vì khối không được lưu trữ vĩnh viễn ở bất cứ nơi nào (trừ khi thực hiện [someObject successBlock:failure:] thực hiện điều đó, nhưng điều đó không thường xuyên cho các khối hoàn thành).

Vì vậy, chu kỳ lưu giữ không phải là vấn đề trong ví dụ đầu tiên của bạn.

Nói chung, giữ lại các chu kỳ trong các khối chỉ là vấn đề nếu một số đối tượng lưu trữ khối thay vì thực thi nó trực tiếp. Sau đó, thật dễ dàng để thấy rằng self tham chiếu mạnh mẽ đến blockblock có tham chiếu mạnh mẽ đến self. Lưu ý rằng truy cập bất kỳ ivar nào từ bên trong một khối sẽ tự động tạo tham chiếu mạnh mẽ đến self trong khối đó.

Tương đương để đảm bảo rằng đối tượng chứa không tham chiếu mạnh mẽ vùng chứa của nó là sử dụng __weak SelfClass *weakSelf = self để truy cập cả hai phương pháp và ivars (tốt hơn nếu bạn truy cập ivars thông qua accessors, như khi sử dụng thuộc tính).Tham chiếu của khối của bạn đến self sẽ yếu (đó là không phải là bản sao, đó là một tài liệu tham khảo yếu yếu) và điều đó sẽ cho phép self không được phân phối.

Có thể lập luận rằng thực hành tốt là luôn luôn sử dụng weakSelf bên trong tất cả các khối, được lưu trữ hay không, chỉ trong trường hợp. Tôi tự hỏi tại sao Apple không đưa ra hành vi mặc định này. Làm điều này thường không làm bất cứ điều gì có hại cho mã khối, ngay cả khi thực sự không cần thiết.


__block hiếm khi được sử dụng trên các biến trỏ đến đối tượng, vì Mục tiêu-C không thực thi bất biến của các đối tượng như vậy.

Nếu bạn có con trỏ đến đối tượng, bạn có thể gọi phương thức của nó và các phương pháp này có thể sửa đổi, có hoặc không có __block. __block là nhiều hơn (chỉ?) Hữu ích trên các biến của các loại cơ bản (int, float, vv). Xem here để biết điều gì sẽ xảy ra khi bạn sử dụng __block với biến con trỏ đối tượng. Bạn cũng có thể đọc thêm về __block trong số Blocks Programming Topics của Apple.

Chỉnh sửa: Lỗi cố định liên quan đến việc sử dụng __block trên con trỏ đối tượng. Nhờ @KevinDiTraglia để chỉ nó.

+1

Câu trả lời hay, nhưng bạn có chắc chắn về câu nói cuối cùng đó không? Tôi đang xem xét một vấn đề của việc sử dụng __block thay vì __weak cho một kiểu tham chiếu và chúng có các hành vi khác nhau, tham chiếu __weak trở thành không trong khi tham chiếu __block thì không. Tôi nghĩ rằng nó gần gũi hơn với một con trỏ mạnh mẽ cho các tham chiếu đối tượng. –

+0

Cảm ơn nhận xét của bạn, bạn đã đúng. Tôi đã sửa một chút câu trả lời. –

+0

Không chắc chắn nếu làm cho tham chiếu luôn luôn yếu để tự là chính xác. Đôi khi tôi nghĩ rằng bạn có thể muốn khối đó giữ lại tham chiếu, vì vậy nó sẽ không để cho nó được deallocated. Theo như tôi hiểu nó chỉ nên được sử dụng khi sử dụng một tham chiếu mạnh mẽ sẽ gây ra một chu kỳ giữ lại. – Ixx

3

Ví dụ đầu tiên của bạn sẽ không tạo ra không bao giờ kết thúc chu kỳ lưu giữ. Sẽ có chu kỳ giữ lại, được rồi, nhưng một khi các khối được thực hiện, mẫu tham chiếu các khối đến someObject sẽ bị xóa. Vì vậy, các someObject sẽ sống ít nhất cho đến khi các khối được thực hiện. Chu trình giữ chân tạm thời như vậy có thể là một điều tốt hay xấu, tùy thuộc vào những gì bạn muốn:

Nếu bạn cần someObject còn sống ít nhất là cho đến khi hoàn thành các khối. Tuy nhiên, nếu không có lý do gì để giữ đối tượng đó, bạn nên thực hiện nó bằng cách sử dụng tham chiếu 'yếu'.

Ví dụ: myObject là một bộ điều khiển khung nhìn trong các khối đó lấy một hình ảnh từ mạng. Nếu bạn bật biểu tượng điều hướng điều hướng someObject, bộ điều khiển sẽ không thể hiển thị hình ảnh sau khi tìm nạp, vì vậy không cần phải giữ nó. Thành công hoặc lỗi không liên quan, người dùng không còn quan tâm đến hình ảnh someObject được cho là tìm nạp. Trong trường hợp như vậy, việc sử dụng yếu là lựa chọn tốt hơn, tuy nhiên mã trong các khối sẽ được mong đợi hơn self có thể là không.

+1

Không chính xác hơn khi nói rằng khi các khối được thực hiện, tham chiếu đến * chúng * sẽ bị xóa? – Ixx

+0

Điều này thực sự là chính xác. 1 bởi vì nó giải thích lý do tại sao nó không tạo ra một chu kỳ giữ lại perm. Nhiều lập trình viên mới luôn sử dụng weakSelf vì chúng bị hiểu sai về các chu kỳ giữ lại như được liệt kê trong câu trả lời được chấp nhận. Mặc dù điều này là hoàn toàn phù hợp với hầu hết các ứng dụng, các ứng dụng phức tạp hơn sẽ thấy các vấn đề với các tham chiếu đang được deallocated trước khi khối được thực hiện đang được thực hiện gây ra sự cố nếu bạn cố gắng tham khảo các đối tượng này sau. Tôi nghĩ bạn muốn nói câu cuối cùng rằng 'weakSelf' có thể là không. – Bot