Khi nào nên sử dụng enum CảObjectsUsingBlock so với


150

Bên cạnh những khác biệt rõ ràng:

  • Sử dụng enumerateObjectsUsingBlockkhi bạn cần cả chỉ mục và đối tượng
  • Không sử dụng enumerateObjectsUsingBlockkhi bạn cần sửa đổi các biến cục bộ (Tôi đã sai về điều này, xem câu trả lời của bbum)

Được enumerateObjectsUsingBlockthường được coi tốt hơn hoặc tồi tệ hơn khi for (id obj in myArray)cũng sẽ làm việc? Những lợi thế / bất lợi (ví dụ như nó ít nhiều có hiệu suất)?


1
Tôi thích sử dụng nó nếu tôi cần chỉ số hiện tại.
Besi

Câu trả lời:


350

Cuối cùng, sử dụng bất kỳ mẫu nào bạn muốn sử dụng và xuất hiện tự nhiên hơn trong bối cảnh.

Mặc dù for(... in ...)khá thuận tiện và ngắn gọn về mặt cú pháp, enumerateObjectsUsingBlock:có một số tính năng có thể hoặc không thể chứng minh sự thú vị:

  • enumerateObjectsUsingBlock:sẽ nhanh hoặc nhanh hơn liệt kê nhanh ( for(... in ...)sử dụng sự NSFastEnumerationhỗ trợ để thực hiện phép liệt kê). Phép liệt kê nhanh yêu cầu dịch từ biểu diễn bên trong sang biểu diễn để liệt kê nhanh. Có chi phí trong đó. Phép liệt kê dựa trên khối cho phép lớp bộ sưu tập liệt kê nội dung nhanh nhất là truyền tải nhanh nhất của định dạng lưu trữ gốc. Có khả năng không liên quan cho mảng, nhưng nó có thể là một sự khác biệt rất lớn cho từ điển.

  • "Không sử dụng enum CảObjectsUsingBlock khi bạn cần sửa đổi các biến cục bộ" - không đúng; bạn có thể khai báo người dân địa phương của mình __blockvà họ sẽ có thể ghi trong khối.

  • enumerateObjectsWithOptions:usingBlock: hỗ trợ liệt kê đồng thời hoặc đảo ngược.

  • với từ điển, liệt kê dựa trên khối là cách duy nhất để truy xuất khóa và giá trị đồng thời.

Cá nhân, tôi sử dụng enumerateObjectsUsingBlock:thường xuyên hơn for (... in ...), nhưng - một lần nữa - lựa chọn cá nhân.


16
Wow, rất nhiều thông tin. Tôi ước tôi có thể chấp nhận cả hai câu trả lời này, nhưng tôi sẽ đi với Chuck vì nó cộng hưởng thêm một chút với tôi. Ngoài ra, tôi đã tìm thấy blog của bạn ( friday.com/bbum/2009/08/29/blocks-tips-tricks ) trong khi tìm kiếm __block và học được nhiều hơn nữa. Cảm ơn bạn.
Paul Wheeler

8
Đối với bản ghi, phép liệt kê dựa trên khối không phải lúc nào cũng "nhanh hay nhanh" mikeabdullah.net/slow-block-basing-dipedia-enumutions.html
Mike Abdullah

2
Các khối @VanDuTran chỉ được thực thi trên một luồng riêng biệt nếu bạn bảo chúng được thực thi trên một luồng riêng biệt. Trừ khi bạn sử dụng tùy chọn đồng thời liệt kê, thì nó sẽ được thực hiện trên cùng một chuỗi khi cuộc gọi được thực hiện
bbum

2
Nick Lockwood đã làm một bài viết thực sự hay về điều này, và dường như nó enumerateObjectsUsingBlockvẫn chậm hơn đáng kể so với việc liệt kê nhanh cho các mảng và bộ. Tôi tự hỏi tại sao? iosdevelopertips.com/objective-c/ Lời
Bob Spryn

2
Mặc dù đây là một chi tiết triển khai, nhưng nó nên được đề cập trong câu trả lời này trong số những khác biệt giữa hai cách tiếp cận enumerateObjectsUsingBlockbao gồm mỗi lần gọi khối với nhóm tự động phát hành (ít nhất là của OS X 10.10). Điều này giải thích sự khác biệt hiệu suất so với for inkhông làm điều đó.
Pol

83

Đối với phép liệt kê đơn giản, chỉ cần sử dụng phép liệt kê nhanh (tức là một for…in…vòng lặp) là tùy chọn thành ngữ hơn. Phương thức khối có thể nhanh hơn một chút, nhưng hầu như không có vấn đề gì trong hầu hết các trường hợp - một số chương trình bị ràng buộc bởi CPU và thậm chí sau đó hiếm khi chính vòng lặp thay vì tính toán bên trong sẽ bị nghẽn cổ chai.

Một vòng lặp đơn giản cũng đọc rõ ràng hơn. Đây là bản tóm tắt của hai phiên bản:

for (id x in y){
}

[y enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop){
}];

Ngay cả khi bạn thêm một biến để theo dõi chỉ mục, vòng lặp đơn giản vẫn dễ đọc hơn.

Vậy khi nào bạn nên sử dụng enumerateObjectsUsingBlock:? Khi bạn đang lưu trữ một khối để thực hiện sau hoặc ở nhiều nơi. Thật tốt khi bạn thực sự sử dụng một khối làm chức năng hạng nhất thay vì thay thế quá mức cho thân vòng lặp.


5
enumerateObjectsUsingBlock:sẽ có cùng tốc độ hoặc nhanh hơn tốc độ liệt kê nhanh trong mọi trường hợp. for(... in ...)sử dụng phép liệt kê nhanh yêu cầu bộ sưu tập cung cấp một số biểu diễn tạm thời của các cấu trúc dữ liệu nội bộ. Như bạn lưu ý, có khả năng không liên quan.
bbum

4
+1When you're storing a block to execute later or in multiple places. It's good for when you're actually using a block as a first-class function rather than an overwrought replacement for a loop body.
Steve

7
@bbum Các thử nghiệm của riêng tôi cho thấy rằng enumerateObjects...thực sự có thể chậm hơn sau đó liệt kê nhanh với một vòng lặp. Tôi đã chạy thử nghiệm này vài ngàn lần; Phần thân của khối và vòng lặp là cùng một dòng mã : [(NSOperation *)obj cancel];. Trung bình: vòng enum nhanh - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009và cho khối - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043. Thật kỳ lạ khi sự khác biệt thời gian là rất lớn và nhất quán, nhưng, rõ ràng, đây là một trường hợp thử nghiệm rất cụ thể.
chown

42

Mặc dù câu hỏi này đã cũ, mọi thứ vẫn không thay đổi, câu trả lời được chấp nhận là không chính xác.

Các enumerateObjectsUsingBlockAPI không có nghĩa là để thay thế for-in, nhưng đối với một trường hợp sử dụng hoàn toàn khác nhau:

  • Nó cho phép áp dụng logic tùy ý, không cục bộ. tức là bạn không cần biết khối này làm gì để sử dụng nó trên một mảng.
  • Liệt kê đồng thời cho các bộ sưu tập lớn hoặc tính toán nặng (sử dụng withOptions:tham số)

Bảng liệt kê nhanh for-invẫn là thành ngữ phương pháp để liệt kê một bộ sưu tập.

Lợi ích liệt kê nhanh từ tính ngắn gọn của mã, khả năng đọc và tối ưu hóa bổ sung làm cho nó nhanh một cách bất thường. Nhanh hơn một vòng lặp C cũ!

Một thử nghiệm nhanh kết luận rằng trong năm 2014 trên iOS 7, enumerateObjectsUsingBlocktốc độ chậm hơn 700% so với for-in (dựa trên các lần lặp 1mm của một mảng 100 mục).

Là hiệu suất thực sự là một mối quan tâm thực tế ở đây?

Chắc chắn là không, với ngoại lệ hiếm.

Vấn đề là chứng minh rằng có rất ít lợi ích khi sử dụng enumerateObjectsUsingBlock:hơnfor-in mà không có một lý do thực sự tốt. Nó không làm cho mã dễ đọc hơn ... hoặc nhanh hơn ... hoặc an toàn cho chuỗi. (một quan niệm sai lầm phổ biến khác).

Sự lựa chọn đi xuống sở thích cá nhân. Đối với tôi, tùy chọn thành ngữ và dễ đọc chiến thắng. Trong trường hợp này, đó là liệt kê nhanh bằng cách sử dụng for-in.

Điểm chuẩn:

NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 100; i++) {
    arr[i] = [NSString stringWithFormat:@"%d", i];
}
int i;
__block NSUInteger length;

i = 1000 * 1000;
uint64_t a1 = mach_absolute_time();
while (--i > 0) {
    for (NSString *s in arr) {
        length = s.length;
    }
}
NSLog(@"For-in %llu", mach_absolute_time()-a1);

i = 1000 * 1000;
uint64_t b1 = mach_absolute_time();
while (--i > 0) {
    [arr enumerateObjectsUsingBlock:^(NSString *s, NSUInteger idx, BOOL *stop) {
        length = s.length;
    }];
}
NSLog(@"Enum %llu", mach_absolute_time()-b1);

Các kết quả:

2014-06-11 14:37:47.717 Test[57483:60b] For-in 1087754062
2014-06-11 14:37:55.492 Test[57483:60b] Enum   7775447746

4
Tôi có thể xác nhận rằng việc chạy thử nghiệm tương tự trên MacBook Pro Retina 2014, enumerateObjectsUsingBlockthực sự chậm hơn 5 lần. Đó rõ ràng là do một nhóm tự động đóng gói mỗi lần gọi khối, điều này không xảy ra đối với for intrường hợp này.
Pol

2
Tôi xác nhận enumerateObjectsUsingBlock:vẫn chậm hơn 4 lần trên iPhone 6 iOS9 thật, sử dụng Xcode 7.x để xây dựng.
Cœur

1
Cảm ơn về lời xác nhận! Tôi ước câu trả lời này không bị chôn vùi ... một số người chỉ thích enumeratecú pháp bởi vì nó cảm thấy giống như FP và không muốn nghe phân tích hiệu suất.
Adam Kaplan

1
Nhân tiện, nó không chỉ là do hồ bơi tự động. Có rất nhiều stack đẩy và popping vớienumerate:
Adam Kaplan

24

Để trả lời câu hỏi về hiệu suất, tôi đã thực hiện một số thử nghiệm bằng dự án thử nghiệm hiệu suất của mình . Tôi muốn biết trong số ba tùy chọn để gửi tin nhắn đến tất cả các đối tượng trong một mảng là nhanh nhất.

Các tùy chọn là:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) liệt kê nhanh và gửi tin nhắn thường xuyên

for (id item in arr)
{
    [item _stubMethod];
}

3) enum CảObjectsUsingBlock & gửi tin nhắn thông thường

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 
 {
     [obj _stubMethod];
 }];

Nó chỉ ra rằng makeObjectsPerformSelector là chậm nhất cho đến nay. Phải mất gấp đôi thời gian liệt kê nhanh. Và enum CảObjectsUsingBlock là nhanh nhất, nó nhanh hơn khoảng 15-20% so với lặp nhanh.

Vì vậy, nếu bạn rất quan tâm đến hiệu suất tốt nhất có thể, hãy sử dụng enum CảObjectsUsingBlock. Nhưng hãy nhớ rằng trong một số trường hợp, thời gian cần thiết để liệt kê một bộ sưu tập bị lấn át bởi thời gian cần thiết để chạy bất kỳ mã nào bạn muốn mỗi đối tượng thực thi.


Bạn có thể chỉ ra bài kiểm tra cụ thể của bạn? Bạn dường như đã đưa ra một câu trả lời sai.
Cœur

3

Nó khá hữu ích khi sử dụng enum CảObjectsUsingBlock như một vòng lặp bên ngoài khi bạn muốn phá vỡ các vòng lặp lồng nhau.

ví dụ

[array1 enumerateObjectsUsingBlock:^(id obj1, NSUInteger idx, BOOL * _Nonnull stop) {
  for(id obj2 in array2) {
    for(id obj3 in array3) {
      if(condition) {
        // break ALL the loops!
        *stop = YES;
        return;
      }
    }
  }
}];

Thay thế là sử dụng báo cáo goto.


1
Hoặc bạn có thể quay lại từ phương thức giống như bạn đã thực hiện ở đây :-D
Adam Kaplan

1

Cảm ơn @bbum và @Chuck đã bắt đầu so sánh toàn diện về hiệu suất. Vui mừng khi biết nó tầm thường. Tôi dường như đã đi với:

  • for (... in ...)- như goto mặc định của tôi. Đối với tôi trực quan hơn, lịch sử lập trình ở đây nhiều hơn bất kỳ ưu tiên thực tế nào - tái sử dụng ngôn ngữ chéo, ít gõ cho hầu hết các cấu trúc dữ liệu do IDE tự động hoàn tất: P.

  • enumerateObject...- khi cần truy cập vào đối tượng và chỉ mục. Và khi truy cập các cấu trúc không phải là mảng hoặc từ điển (sở thích cá nhân)

  • for (int i=idx; i<count; i++) - đối với mảng, khi tôi cần bắt đầu với chỉ số khác không

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.