30

Tôi muốn có thể thực hiện một số block trên vòng lặp chạy lặp tiếp theo. Nó không quá quan trọng cho dù nó được thực hiện ở đầu hoặc cuối của vòng lặp chạy tiếp theo, chỉ việc thực hiện đó được hoãn lại cho đến khi tất cả mã trong vòng lặp chạy hiện tại đã hoàn thành việc thực thi.Làm thế nào để bạn lên lịch cho một khối chạy trên vòng lặp chạy lặp tiếp theo?

Tôi biết những điều sau đây không hoạt động vì nó được xen kẽ với vòng lặp chạy chính để mã của tôi có thể thực thi trên vòng lặp chạy tiếp theo nhưng có thể không.

dispatch_async(dispatch_get_main_queue(),^{ 
    //my code 
}); 

Sau đây tôi tin rằng bị vấn đề tương tự như trên:

dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void){ 
    //my code 
}); 

Bây giờ tôi tin sau đây sẽ làm việc như nó được đặt ở phần cuối của vòng lặp chạy hiện tại (chính xác cho tôi nếu Tôi sai), điều này thực sự có hiệu quả không?

[self performSelector:@selector(myMethod) withObject:nil afterDelay:0]; 

Điều gì về bộ hẹn giờ với khoảng thời gian 0? Các tài liệu tuyên bố: If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead. Điều này có dịch để đảm bảo thực thi trên vòng lặp chạy lặp tiếp theo không?

[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(myMethod) userInfo:nil repeats:NO]; 

Đó là tất cả các tùy chọn tôi có thể nghĩ nhưng tôi vẫn chưa thực hiện một khối (thay vì gọi phương thức) trên vòng lặp chạy tiếp theo với đảm bảo rằng sẽ không có bất kỳ sớm hơn.

Trả lời

66

Bạn có thể không nhận thức được mọi thứ mà vòng lặp chạy thực hiện trong mỗi lần lặp. (Tôi đã không được trước khi tôi nghiên cứu câu trả lời này!) Khi nó xảy ra, CFRunLoop là một phần của open-source CoreFoundation package, vì vậy chúng ta có thể xem chính xác những gì nó đòi hỏi. Vòng lặp chạy trông gần như thế này:

while (true) { 
    Call kCFRunLoopBeforeTimers observer callbacks; 
    Call kCFRunLoopBeforeSources observer callbacks; 
    Perform blocks queued by CFRunLoopPerformBlock; 
    Call the callback of each version 0 CFRunLoopSource that has been signalled; 
    if (any version 0 source callbacks were called) { 
     Perform blocks newly queued by CFRunLoopPerformBlock; 
    } 
    if (I didn't drain the main queue on the last iteration 
     AND the main queue has any blocks waiting) 
    { 
     while (main queue has blocks) { 
      perform the next block on the main queue 
     } 
    } else { 
     Call kCFRunLoopBeforeWaiting observer callbacks; 
     Wait for a CFRunLoopSource to be signalled 
      OR for a timer to fire 
      OR for a block to be added to the main queue; 
     Call kCFRunLoopAfterWaiting observer callbacks; 
     if (the event was a timer) { 
      call CFRunLoopTimer callbacks for timers that should have fired by now 
     } else if (event was a block arriving on the main queue) { 
      while (main queue has blocks) { 
       perform the next block on the main queue 
      } 
     } else { 
      look up the version 1 CFRunLoopSource for the event 
      if (I found a version 1 source) { 
       call the source's callback 
      } 
     } 
    } 
    Perform blocks queued by CFRunLoopPerformBlock; 
} 

Bạn có thể thấy rằng có nhiều cách để nối vào vòng lặp chạy. Bạn có thể tạo một CFRunLoopObserver để được gọi cho bất kỳ "hoạt động" nào bạn muốn. Bạn có thể tạo phiên bản 0 CFRunLoopSource và báo hiệu ngay lập tức. Bạn có thể tạo một cặp được kết nối là CFMessagePorts, quấn một trong một phiên bản 1 CFRunLoopSource và gửi một tin nhắn. Bạn có thể tạo một CFRunLoopTimer. Bạn có thể xếp hàng khối bằng cách sử dụng dispatch_get_main_queue hoặc CFRunLoopPerformBlock.

Bạn sẽ cần phải quyết định sử dụng API nào trong số này dựa trên thời điểm bạn lập lịch biểu khối và khi bạn cần nó được gọi.Ví dụ, chạm được xử lý trong nguồn phiên bản 1, nhưng nếu bạn xử lý chạm bằng cách cập nhật màn hình, bản cập nhật đó không thực sự được thực hiện cho đến khi giao dịch Core Animation được thực hiện trong máy quan sát kCFRunLoopBeforeWaiting.

Bây giờ giả sử bạn muốn lập lịch biểu khối trong khi bạn đang xử lý các liên lạc, nhưng bạn muốn nó được thực hiện sau khi giao dịch được cam kết.

Bạn có thể thêm CFRunLoopObserver của riêng mình cho hoạt động kCFRunLoopBeforeWaiting, nhưng người quan sát này có thể chạy trước hoặc sau người quan sát của Core Animation, tùy theo thứ tự bạn chỉ định và thứ tự Core Animation xác định. (Hoạt ảnh chính hiện chỉ định một đơn đặt hàng là 2000000, nhưng không được ghi lại để nó có thể thay đổi.)

Để đảm bảo khối của bạn chạy sau máy quan sát của Core Animation, ngay cả khi người quan sát của bạn chạy trước Người quan sát của Core Animation, don ' t gọi khối trực tiếp trong callback của người quan sát của bạn. Thay vào đó, hãy sử dụng dispatch_async tại thời điểm đó để thêm khối vào hàng đợi chính. Đặt khối trên hàng đợi chính sẽ buộc các vòng lặp chạy để thức dậy từ "chờ đợi" của nó ngay lập tức. Nó sẽ chạy bất kỳ kCFRunLoopAfterWaiting người quan sát nào, và sau đó nó sẽ rút hàng đợi chính, tại thời điểm đó nó sẽ chạy khối của bạn.

+0

Cảm ơn bạn đã thêm chi tiết CFRunLoop. – lal

+0

Đây là một bản tóm tắt tuyệt vời về vòng lặp chạy. Đối với câu hỏi của OP, tôi muốn nói rằng 'CFRunLoopPerformBlock()' sẽ là cách kosher nhất để đảm bảo rằng khối thực hiện trên vòng lặp tiếp theo của vòng lặp chạy. Các tài liệu không tuyên bố rõ ràng rằng khối sẽ không được thực hiện nếu nó được thêm vào giữa một vòng lặp, nhưng họ nói: "Phương thức này chỉ enqueues khối và không tự động thức dậy vòng lặp chạy quy định. việc thực thi khối này xảy ra vào lần tới khi vòng lặp chạy thức dậy để xử lý một nguồn đầu vào khác. " Bạn có thể kiểm tra bằng cách thêm khối trong lời gọi người quan sát. –

0

Tôi không tin rằng có bất kỳ API nào sẽ cho phép bạn đảm bảo mã được chạy trên vòng lặp sự kiện tiếp theo. Tôi cũng tò mò tại sao bạn cần một đảm bảo rằng không có gì khác đã chạy trên vòng lặp, một trong những chính đặc biệt.

Tôi cũng có thể xác nhận rằng sử dụng perforSelector: withObject: afterDelay sử dụng bộ đếm thời gian dựa trên runloop và sẽ có hành vi tương tự về chức năng để dispatch_async'ing trên dispatch_get_main_queue().

chỉnh sửa:

Trên thực tế, sau khi đọc lại câu hỏi của bạn, có vẻ như bạn chỉ cần hiện runloop biến để hoàn thành. Nếu điều đó là đúng, thì dispatch_async chính xác là những gì bạn cần. Trên thực tế, tất cả các mã ở trên đều đảm bảo rằng số lượt truy cập hiện tại hiện tại sẽ hoàn tất.

+0

Tôi không chắc đó là sự thật. Tài liệu tuyên bố: 'Hàng đợi công văn chính là hàng đợi nối tiếp sẵn có trên toàn cầu thực thi các tác vụ trên chuỗi chính của ứng dụng. Hàng đợi này làm việc với vòng lặp chạy của ứng dụng (nếu có) để xen kẽ việc thực hiện các tác vụ xếp hàng với việc thực thi các nguồn sự kiện khác gắn liền với vòng lặp chạy.' Xem https://developer.apple.com/library/mac/ # documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html # // apple_ref/doc/uid/TP40008091-CH102-SW2 – lms

-3

dispatch_async trên mainQueue là một gợi ý tốt nhưng nó không chạy trên vòng lặp chạy tiếp theo nó được chèn vào chạy hiện tại trong vòng lặp.

Để có được hành vi mà bạn đang theo đuổi bạn sẽ cần phải dùng đến theo cách truyền thống:

[self performSelector:@selector(myMethod) withObject:nil afterDelay:0]; 

này cũng cung cấp cho các lợi thế nhất là nó có thể được hủy bỏ bằng cancelPreviousPerforms NSObject của.

-3

Tôi tự viết một số NSObject category chấp nhận giá trị độ trễ thay đổi, dựa trên another stackoverflow question. Bằng cách chuyển một giá trị bằng không, bạn có hiệu quả làm cho mã chạy trên lần lặp lại runloop có sẵn tiếp theo.

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