Cách sử dụng Swift @autoclenses


148

Tôi nhận thấy khi viết asserttrong Swift rằng giá trị đầu tiên được nhập là

@autoclosure() -> Bool

với một phương thức quá tải để trả về một Tgiá trị chung , để kiểm tra sự tồn tại thông qua LogicValue protocol.

Tuy nhiên, bám sát vào câu hỏi trong tầm tay. Có vẻ như muốn @autoclosuretrả về a Bool.

Viết một bao đóng thực tế không có tham số và trả về Bool không hoạt động, nó muốn tôi gọi bao đóng để làm cho nó biên dịch, như vậy:

assert({() -> Bool in return false}(), "No user has been set", file: __FILE__, line: __LINE__)

Tuy nhiên, chỉ cần truyền Bool hoạt động:

assert(false, "No user has been set", file: __FILE__, line: __LINE__)

Vậy chuyện gì đang xảy ra thế? Là @autoclosure

Chỉnh sửa: @auto_closure đã được đổi tên@autoclosure

Câu trả lời:


269

Hãy xem xét một hàm có một đối số, một bao đóng đơn giản không có đối số:

func f(pred: () -> Bool) {
    if pred() {
        print("It's true")
    }
}

Để gọi hàm này, chúng ta phải vượt qua trong một bao đóng

f(pred: {2 > 1})
// "It's true"

Nếu chúng ta bỏ qua dấu ngoặc nhọn, chúng ta sẽ chuyển một biểu thức và đó là một lỗi:

f(pred: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

@autoclosuretạo ra một đóng tự động xung quanh biểu thức. Vì vậy, khi người gọi viết một biểu thức như thế 2 > 1, nó sẽ tự động được đóng thành một bao đóng để trở thành {2 > 1}trước khi nó được chuyển đến f. Vì vậy, nếu chúng ta áp dụng điều này cho chức năng f:

func f(pred: @autoclosure () -> Bool) {
    if pred() {
        print("It's true")
    }
}

f(pred: 2 > 1)
// It's true

Vì vậy, nó hoạt động chỉ với một biểu thức mà không cần phải bọc nó trong một bao đóng.


2
Trên thực tế, cái cuối cùng, không hoạt động. Nó nên làf({2 >1}())
Rui Peres

@JoelFischer Tôi đang thấy điều tương tự như @JackyBoy. Gọi f(2 > 1)công trình. Gọi f({2 > 1})không thành công error: function produces expected type 'Bool'; did you mean to call it with '()'?. Tôi đã thử nghiệm nó trong một sân chơi và với Swift REPL.
Ole Begemann

Tôi bằng cách nào đó đọc câu trả lời thứ hai đến câu trả lời cuối cùng, tôi sẽ phải kiểm tra lại, nhưng sẽ rất có ý nghĩa nếu nó thất bại, vì về cơ bản bạn đang đặt một bao đóng trong một bao đóng, từ những gì tôi hiểu.
Joel Fischer

3
có một bài đăng trên blog về lý do họ đã làm cho nhà phát triển
đó.apple.com / swift / id = 4 mohamed-ted

5
Giải thích tuyệt vời. Cũng lưu ý rằng trong Swift 1.2 'autoclenses' hiện là một thuộc tính của khai báo tham số, vì vậy nófunc f(@autoclosure pred: () -> Bool)
Masa

30

Đây là một ví dụ thực tế - printghi đè của tôi (đây là Swift 3):

func print(_ item: @autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
    #if DEBUG
    Swift.print(item(), separator:separator, terminator: terminator)
    #endif
}

Khi bạn nói print(myExpensiveFunction()), phần printghi đè của tôi làm lu mờ Swift printvà được gọi. myExpensiveFunction()do đó được bọc trong một đóng cửa và không được đánh giá . Nếu chúng ta ở chế độ Phát hành, nó sẽ không bao giờ được đánh giá, vì item()sẽ không được gọi. Do đó, chúng tôi có một phiên bản printkhông đánh giá các đối số của nó trong chế độ Phát hành.


Tôi đến bữa tiệc muộn, nhưng tác động của việc đánh giá là myExpensiveFunction()gì? Nếu thay vì sử dụng chế độ tự động mà bạn truyền chức năng để in như thế print(myExpensiveFunction)nào thì tác động sẽ ra sao? Cảm ơn.
crom87

11

Mô tả về auto_clences từ các tài liệu:

Bạn có thể áp dụng thuộc tính auto_clenses cho loại hàm có loại tham số () và trả về loại biểu thức (xem Thuộc tính loại). Hàm autoclenses chụp một bao đóng ngầm trên biểu thức đã chỉ định, thay vì chính biểu thức đó. Ví dụ sau sử dụng thuộc tính auto_clenses trong việc xác định hàm khẳng định rất đơn giản:

Và đây là ví dụ táo sử dụng cùng với nó.

func simpleAssert(condition: @auto_closure () -> Bool, message: String) {
    if !condition() {
        println(message)
    }
}
let testNumber = 5
simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.")

Về cơ bản điều đó có nghĩa là bạn chuyển một biểu thức boolean như là đối số đầu tiên thay vì một bao đóng và nó tự động tạo ra một bao đóng cho nó. Đó là lý do tại sao bạn có thể truyền sai vào phương thức vì nó là biểu thức boolean, nhưng không thể vượt qua bao đóng.


15
Lưu ý rằng bạn không thực sự cần phải sử dụng @auto_closureở đây. Mã này hoạt động tốt mà không có nó : func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Sử dụng @auto_closurekhi bạn cần đánh giá một đối số nhiều lần (ví dụ: nếu bạn đang triển khai một whilechức năng tương tự) hoặc bạn cần trì hoãn việc đánh giá một đối số (ví dụ: nếu bạn đang thực hiện đoản mạch &&).
nathan

1
@nathan Xin chào, nathan. Bạn có thể vui lòng trích dẫn cho tôi một mẫu liên quan đến việc sử dụng autoclosurevới whilechức năng -like? Tôi dường như không tìm ra điều đó. Cảm ơn rất nhiều trước.
Unheilig

@connor Bạn có thể muốn cập nhật câu trả lời của mình cho Swift 3.
jarora

4

Điều này cho thấy một trường hợp hữu ích của @autoclosure https://airspeedvelocity.net/2014/06/11/extending-the-swift-lingu-is-cool-but-be-careful/

Bây giờ, biểu thức điều kiện được truyền dưới dạng tham số đầu tiên cho đến khi được tự động gói thành biểu thức đóng và có thể được gọi mỗi lần xung quanh vòng lặp

func until<L: LogicValue>(pred: @auto_closure ()->L, block: ()->()) {
    while !pred() {
        block()
    }
}

// doSomething until condition becomes true
until(condition) {
    doSomething()
}

2

Đó chỉ là một cách để loại bỏ các dấu ngoặc nhọn trong một cuộc gọi đóng cửa, ví dụ đơn giản:

    let nonAutoClosure = { (arg1: () -> Bool) -> Void in }
    let non = nonAutoClosure( { 2 > 1} )

    let autoClosure = { (arg1: @autoclosure () -> Bool) -> Void in }
    var auto = autoClosure( 2 > 1 ) // notice curly braces omitted

0

@autoclosurelà một tham số hàm chấp nhận hàm nấu (hoặc kiểu trả về) trong khi đó hàm chung closurechấp nhận hàm thô

  • Tham số loại đối số @autoclenses phải là '()'
    @autoclosure ()
  • @autoclenses chấp nhận bất kỳ chức năng nào chỉ với loại trả về thích hợp
  • Kết quả đóng cửa được tính theo nhu cầu

Hãy xem ví dụ

func testClosures() {

    //closures
    XCTAssertEqual("fooWithClosure0 foo0", fooWithClosure0(p: foo0))
    XCTAssertEqual("fooWithClosure1 foo1 1", fooWithClosure1(p: foo1))
    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: foo2))

    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: { (i1, i2) -> String in
        return "fooWithClosure2 " + "foo2 " + String(i1 + i2)
    }))

    //@autoclosure
    XCTAssertEqual("fooWithAutoClosure HelloWorld", fooWithAutoClosure(a: "HelloWorld"))

    XCTAssertEqual("fooWithAutoClosure foo0", fooWithAutoClosure(a: foo0()))
    XCTAssertEqual("fooWithAutoClosure foo1 1", fooWithAutoClosure(a: foo1(i1: 1)))
    XCTAssertEqual("fooWithAutoClosure foo2 3", fooWithAutoClosure(a: foo2(i1: 1, i2: 2)))

}

//functions block
func foo0() -> String {
    return "foo0"
}

func foo1(i1: Int) -> String {
    return "foo1 " + String(i1)
}

func foo2(i1: Int, i2: Int) -> String {
    return "foo2 " + String(i1 + i2)
}

//closures block
func fooWithClosure0(p: () -> String) -> String {
    return "fooWithClosure0 " + p()
}

func fooWithClosure1(p: (Int) -> String) -> String {
    return "fooWithClosure1 " + p(1)
}

func fooWithClosure2(p: (Int, Int) -> String) -> String {
    return "fooWithClosure2 " + p(1, 2)
}

//@autoclosure
func fooWithAutoClosure(a: @autoclosure () -> String) -> String {
    return "fooWithAutoClosure " + a()
}
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.