2011-03-26 25 views
16

Xét đoạn mã sau:nên một khối đen giữ lại tham chiếu khối đống phân bổ

// t included so block1 is a stack block. See [1] below 
int t = 1; 
SimpleBlock block1 = ^{ NSLog(@"block1, %d", t); }; 

// copy block1 to the heap 
SimpleBlock block1_copied = [block1 copy]; 

// block2 is allocated on the stack, and refers to 
// block1 on the stack and block1_copied on the heap 
SimpleBlock block2 = ^{ 
    NSLog(@"block2"); 
    block1_copied(); 
    block1(); 
}; 
[block1_copied release]; 

// When the next line of code is executed, block2_copied is 
// allocated at the same memory address on on the heap as 
// block1_copied, indicating that block1_copied has been 
// deallocated. Why didn't block2 retain block1_copied? 

SimpleBlock block2_copied = [block2 copy]; 
block2_copied(); 
[block2_copied release]; 

đâu, cho đầy đủ, SimpleBlock được xác định bởi:

typedef void (^SimpleBlock)(void); 

Như đã nêu bởi những nhận xét trong mã , các thử nghiệm của tôi (sử dụng cả GCC 4.2 và LLVM 2.0) cho thấy block1_copied được deallocated bởi thời gian [block2 copy] được gọi, nhưng theo tài liệu mà tôi đã đọc [1,3], các khối là các đối tượng và khối mục tiêu-c giữ lại các đối tượng mục tiêu-c mà chúng tham chiếu [2] (trong biến phi cá thể c ase). Ngoài ra, lưu ý rằng khi block2 được sao chép, tham chiếu của nó tới block1 cũng được thay đổi thành tham chiếu đến một bản sao mới của block1 (khác với block1_copied), như mong đợi, vì khối sao chép bất kỳ khối nào mà chúng tham chiếu [ 2].

Vì vậy, những gì đang xảy ra ở đây?

A) Nếu khối giữ lại đối tượng mục tiêu-c mà chúng tham chiếu và chặn là đối tượng mục tiêu-c, tại sao block1_copied deallocated trước khối 2 vượt quá phạm vi?

B) Nếu chặn khối sao chép mà chúng tham chiếu và nếu gửi - (id) bản sao vào khối được phân bổ theo heap thực sự chỉ tăng số lần giữ lại, tại sao block1_copied deallocated trước block2 vượt quá phạm vi?

C) Nếu đây là hành vi mong đợi, tài liệu giải thích ở đâu?

[1] http://cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html
[2] http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html
[3] http://clang.llvm.org/docs/BlockLanguageSpec.txt

Footnote: Trong các thử nghiệm của tôi, kết quả của chạy mã này là một cuộc gọi vô hạn đệ quy để block2_copied(), vì block1_copied() có cùng địa chỉ bộ nhớ như block2_copied.

+1

+1 thú vị .... –

Trả lời

5

This is the specification. Đó là hơi cũ ngay bây giờ và không có hình thức của một spec thông thường. Tuy nhiên, các khối đã được đề xuất trong nhóm làm việc C và một đặc tả chính thức hơn đã được thảo luận trong bối cảnh đó.

Cụ thể, spec nói:

Nhà điều hành Block_copy giữ lại tất cả đối tượng tổ chức trong các biến của tự động lưu trữ tham chiếu trong biểu Block (hoặc tạo tài liệu tham khảo mạnh mẽ nếu chạy dưới thu gom rác thải). Biến đối tượng của __block lưu trữ loại được giả định là giữ các con trỏ bình thường không có điều kiện giữ lại và phát hành thư.

Do đó, hành vi bạn thấy là chính xác, mặc dù đó thực sự là một sự cố!

Một khối sẽ không giữ lại bất kỳ thứ gì cho đến khi khối được sao chép. Giống như các khối bắt đầu trên ngăn xếp, điều này chủ yếu là quyết định dựa trên hiệu suất.

Nếu bạn đã thay đổi mã của bạn để:

SimpleBlock block2_copied = [block2 copy]; 
[block1_copied release]; 

nó cư xử như mong đợi.

Máy phân tích tĩnh phải nắm bắt điều đó, nhưng không (Vui lòng file a bug).

+0

Cảm ơn câu trả lời. Tôi vừa nộp một lỗi trên máy phân tích tĩnh. Tôi nghĩ rằng đây cũng là một tài liệu phát triển không chính xác, cho văn bản từ "Blocks Programming Topics"> "Blocks and Variables"> "Object and Block Variables"> "Objective-C Objects". Không có văn bản nào ở đây cho biết rằng chỉ giữ lại xảy ra _after_ khối được sao chép. Tôi sẽ gửi một lỗi thứ hai về tài liệu dành cho nhà phát triển. –

1

Tôi lưu ý điều tương tự dường như xảy ra với các đối tượng thông thường. Mã này:

NSNumber *foo = [[NSNumber alloc] initWithInt:42]; 
void(^block)(void) = ^{ NSLog(@"foo = %@", foo); }; 
[foo release]; 
NSNumber *foo2 = [[NSNumber alloc] initWithInt:43]; 
void(^block_copy)(void) = [block copy]; 
block_copy(); 

In "foo = 43"

sức được dự kiến ​​hành vi. Để trích dẫn tài liệu của Apple:

Khi bạn sao chép một khối, bất kỳ tài liệu tham khảo để các khối khác từ bên trong khối được sao chép nếu cần thiết

Tại điểm block1_copy được phát hành, block2 đã không được sao chép chưa .

+0

Điều đó chắc chắn đúng. Vì vậy, đây có thể là hành vi mong đợi. Tham khảo [2] cũng nói "Trong một môi trường tính tham chiếu, theo mặc định khi bạn tham chiếu một đối tượng Objective-C trong một khối, nó được giữ lại. Điều này đúng ngay cả khi bạn chỉ đơn giản tham chiếu một biến cá thể của đối tượng. Tuy nhiên, với công cụ sửa đổi loại lưu trữ '__block', không được giữ lại." Điều này không nói gì về việc sao chép là cần thiết, mặc dù nó có thể được đưa ra những ví dụ này. Sau đó, họ đề cập đến _blocks_ được tham chiếu được sao chép nếu cần thiết. Đây có vẻ như họ có thể là vấn đề khác nhau mặc dù. –

+0

@AndrewHershberger: Đúng vậy. Vấn đề lớn là không có một đặc điểm kỹ thuật thực sự. – Anomie

+0

@anomie, Bạn không tham khảo foo2 ở bất kỳ đâu trong đoạn mã này, nhưng bạn cho rằng đầu ra là giá trị của foo2. Làm thế nào điều này thậm chí có thể? –

Các vấn đề liên quan