2015-12-10 15 views
10

Những câu trả lời tôi đã nhìn thấy cho đến nay (1, 2, 3) khuyên bạn sử dụng GCD của dispatch_once như sau:Làm cách nào để thực thi mã một lần và chỉ một lần trong Swift?

var token: dispatch_once_t = 0 
func test() { 
    dispatch_once(&token) { 
     print("This is printed only on the first call to test()") 
    } 
    print("This is printed for each call to test()") 
} 
test() 

Output:

This is printed only on the first call to test() 
This is printed for each call to test() 

Nhưng chờ một phút. token là một biến, vì vậy tôi có thể dễ dàng làm điều này:

var token: dispatch_once_t = 0 
func test() { 
    dispatch_once(&token) { 
     print("This is printed only on the first call to test()") 
    } 
    print("This is printed for each call to test()") 
} 
test() 

token = 0 

test() 

Output:

This is printed only on the first call to test() 
This is printed for each call to test() 
This is printed only on the first call to test() 
This is printed for each call to test() 

Vì vậy dispatch_once là không sử dụng nếu chúng ta tôi có thể thay đổi giá trị của token! Và biến token thành hằng số không đơn giản vì nó cần loại UnsafeMutablePointer<dispatch_once_t>.

Vì vậy, chúng ta có nên từ bỏ dispatch_once trong Swift? Có cách nào an toàn hơn để thực thi mã chỉ một lần không?

+0

Mục tiêu-C có cùng một vấn đề. Ý tưởng là đặt 'token' trong cùng phạm vi với khối' dispatch_once' (và đặt cho nó một tên tốt hơn như 'onceToken' và đặt nó ở phía trên khối' dispatch_once' để nó rất rõ ràng). – nhgrif

+0

sau đó 'dispatch_once' không an toàn hơn là sử dụng biến boolean thông thường. – Eric

+0

http://stackoverflow.com/q/25354882/2792531 – nhgrif

Trả lời

13

tĩnh khởi tạo bởi một đóng cửa đang chạy uể oải và cùng một lúc nhất, vì vậy bản in này chỉ một lần, trong Mặc dù được gọi hai lần:

/* 
run like: 

    swift once.swift 
    swift once.swift run 

to see both cases 
*/ 
class Once { 
    static let run: Void = { 
     print("Behold! \(__FUNCTION__) runs!") 
     return() 
    }() 
} 

if Process.arguments.indexOf("run") != nil { 
    let _ = Once.run 
    let _ = Once.run 
    print("Called twice, but only printed \"Behold\" once, as desired.") 
} else { 
    print("Note how it's run lazily, so you won't see the \"Behold\" text now.") 
} 

Ví dụ chạy:

~/W/WhenDoesStaticDefaultRun> swift once.swift 
Note how it's run lazily, so you won't see the "Behold" text now. 
~/W/WhenDoesStaticDefaultRun> swift once.swift run 
Behold! Once runs! 
Called twice, but only printed "Behold" once, as desired. 
+0

Công trình này chắc chắn. Nó chỉ là một chút cồng kềnh để có bọc nó trong một loại, nhưng điều đó tương ứng với rất nhiều cách sử dụng anyway. – Eric

+3

Bạn không cần phải bọc một thuộc tính tĩnh trong một loại. Bạn có thể khai báo một biến toàn cầu hoặc hằng số và khởi tạo nó với một đóng. Việc đóng cửa sẽ được gọi là lười biếng một lần. Ngoại lệ là nếu biến toàn cục/hằng số được định nghĩa trong main.swift, nó sẽ vẫn được thực thi một lần nhưng theo thứ tự định nghĩa (tức là không lười biếng). –

+1

Là một sang một bên, điều này thực sự sử dụng 'dispatch_once' trong mã được tạo :). Nó chỉ ẩn nó trong ngữ nghĩa ngôn ngữ. –

17

Một người đàn ông đi khám bác sĩ và nói "Bác sĩ, đau khi tôi đóng dấu chân của tôi". Bác sĩ trả lời, "Vậy đừng làm nữa".

Nếu bạn cố ý thay đổi mã thông báo của mình, thì có - bạn sẽ có thể thực thi mã hai lần. Nhưng nếu bạn làm việc xung quanh logic được thiết kế để ngăn không cho thực hiện nhiều trong bất kỳ cách nào, bạn sẽ có thể thực hiện điều đó. dispatch_once vẫn là phương pháp tốt nhất để đảm bảo mã chỉ được thực hiện một lần, vì nó xử lý tất cả các trường hợp góc (rất) phức tạp xung quanh điều kiện khởi tạo và điều kiện cuộc đua mà một boolean đơn giản sẽ không bao hàm.

Nếu bạn lo lắng rằng ai đó có thể vô tình đặt lại mã thông báo, bạn có thể kết hợp nó theo một phương thức và làm rõ ràng vì nó có thể là hậu quả. Một cái gì đó như phạm vi sau đây sẽ token để phương pháp này, và ngăn chặn bất cứ ai từ việc thay đổi nó mà không cần nỗ lực nghiêm túc:

tính
func willRunOnce() ->() { 
    struct TokenContainer { 
     static var token : dispatch_once_t = 0 
    } 

    dispatch_once(&TokenContainer.token) { 
     print("This is printed only on the first call") 
    } 
} 
+2

Dòng đầu tiên một mình có lẽ là tất cả những gì bạn cần. Phần còn lại của câu trả lời là thừa. ;) – nhgrif

+0

LOL. Giải thích tốt nhất. –

+0

Tôi về cơ bản không đồng ý. Tài liệu cho 'dispatch_once' nói rằng nó" Thực hiện một đối tượng khối một lần và chỉ một lần cho toàn bộ thời gian của một ứng dụng. " Không có sự mơ hồ ở đây, API đang phá vỡ hợp đồng. Và bác sĩ đó không đủ năng lực. – Eric

2

tôi nghĩ rằng cách tiếp cận tốt nhất là chỉ cần xây dựng nguồn lực uể oải khi cần thiết. Swift làm cho việc này trở nên dễ dàng.

Có một số tùy chọn. Như đã đề cập, bạn có thể khởi tạo một thuộc tính tĩnh trong một kiểu bằng cách sử dụng một đóng.

Tuy nhiên, lựa chọn đơn giản nhất là để xác định một biến toàn cục (hoặc đổi) và khởi tạo nó với một đóng cửa sau đó tham khảo rằng biến bất cứ nơi nào các mã khởi tạo là cần thiết để xảy ra một lần:

let resourceInit : Void = { 
    print("doing once...") 
    // do something once 
}() 

lựa chọn khác là để bọc loại trong một hàm để nó đọc tốt hơn khi gọi.Ví dụ:

func doOnce() { 
    struct Resource { 
     static var resourceInit : Void = { 
      print("doing something once...") 
     }() 
    } 

    let _ = Resource.resourceInit 
} 

Bạn có thể thực hiện các biến thể khi cần thiết. Ví dụ: thay vì sử dụng loại nội bộ cho hàm, bạn có thể sử dụng hàm toàn cầu và nội bộ hoặc công khai riêng khi cần.

Tuy nhiên, tôi nghĩ cách tiếp cận tốt nhất là chỉ để xác định tài nguyên nào bạn cần để khởi tạo và tạo chúng một cách lười biếng như thuộc tính tổng thể hoặc tĩnh.

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