วิธีใช้ Swift @autoclosure


148

ฉันสังเกตเห็นเมื่อเขียนassertใน Swift ว่าค่าแรกพิมพ์เป็น

@autoclosure() -> Bool

มีวิธีการมากเกินไปที่จะกลับทั่วไปค่าต่อการดำรงอยู่การทดสอบผ่านทางTLogicValue protocol

อย่างไรก็ตามการเกาะติดกับคำถามในมืออย่างเคร่งครัด แต่ดูเหมือนว่ามันต้องการที่ส่งกลับ@autoclosureBool

การเขียนการปิดจริงที่ไม่มีพารามิเตอร์และส่งคืน Bool ไม่ทำงานฉันต้องการเรียกการปิดเพื่อให้คอมไพล์เช่น:

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

อย่างไรก็ตามเพียงผ่าน Bool ทำงาน:

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

แล้วเกิดอะไรขึ้น คือ@autoclosureอะไร

แก้ไข: @auto_closureถูกเปลี่ยนชื่อ@autoclosure

คำตอบ:


269

พิจารณาฟังก์ชั่นที่รับหนึ่งอาร์กิวเมนต์การปิดอย่างง่ายที่ไม่รับอาร์กิวเมนต์:

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

ในการเรียกใช้ฟังก์ชั่นนี้เราต้องปิดตัวลง

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

หากเราไม่ใส่เครื่องหมายวงเล็บเราจะส่งผ่านนิพจน์และนั่นเป็นข้อผิดพลาด:

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

@autoclosureสร้างการปิดอัตโนมัติรอบนิพจน์ ดังนั้นเมื่อโทรเขียนแสดงออกเหมือน2 > 1ก็ห่อโดยอัตโนมัติในการปิดจะกลายเป็นก่อนที่จะถูกส่งผ่านไปยัง{2 > 1} fดังนั้นถ้าเราใช้ฟังก์ชันนี้กับฟังก์ชันf:

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

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

ดังนั้นจึงใช้งานได้กับนิพจน์โดยไม่จำเป็นต้องปิดท้าย


2
อันที่จริงคนสุดท้ายไม่ทำงาน มันควรจะเป็นf({2 >1}())
รุยเปเรเรส

@JoelFischer ฉันเห็นสิ่งเดียวกันกับ @JackyBoy การโทรf(2 > 1)ทำงาน โทรล้มเหลวด้วยf({2 > 1}) error: function produces expected type 'Bool'; did you mean to call it with '()'?ฉันทดสอบในสนามเด็กเล่นและด้วย Swift REPL
Ole Begemann

ฉันอ่านคำตอบครั้งที่สองเป็นคำตอบสุดท้ายฉันจะต้องตรวจสอบอีกครั้ง แต่มันก็สมเหตุสมผลถ้ามันล้มเหลวเนื่องจากคุณต้องปิดตัวลงในการปิดจากสิ่งที่ฉันเข้าใจ
Joel Fischer

3
มีการโพสต์บล็อกเกี่ยวกับสาเหตุที่พวกเขาทำdeveloper.apple.com/swift/blog/?id=4
mohamed-ted

5
คำอธิบายที่ดี โปรดทราบว่าใน 'การแก้ไขอัตโนมัติ' ใน Swift 1.2 ตอนนี้เป็นคุณลักษณะของการประกาศพารามิเตอร์ดังนั้นfunc f(@autoclosure pred: () -> Bool)
Masa

30

นี่คือตัวอย่างที่ใช้งานได้จริง - การprintแทนที่ของฉัน(นี่คือ Swift 3):

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

เมื่อคุณพูดว่าการแทนที่print(myExpensiveFunction())ของฉันprintจะควบคุม Swift's printและถูกเรียก myExpensiveFunction()เป็นห่อดังนั้นในการปิดและไม่ได้รับการประเมิน หากเราอยู่ในโหมด Release จะไม่มีการประเมินเพราะitem()จะไม่ถูกเรียก ดังนั้นเราจึงมีเวอร์ชันprintที่ไม่ได้ประเมินอาร์กิวเมนต์ในโหมด Release


ฉันไปงานปาร์ตี้สาย แต่อะไรคือผลกระทบของการประเมินmyExpensiveFunction()? หากแทนที่จะใช้การเปิดเผยอัตโนมัติคุณผ่านฟังก์ชั่นการพิมพ์เช่น print(myExpensiveFunction)นั้นจะมีผลกระทบอะไรบ้าง ขอบคุณ
crom87

11

คำอธิบายของ auto_closure จากเอกสาร:

คุณสามารถใช้แอตทริบิวต์ auto_closure กับประเภทฟังก์ชั่นที่มีประเภทพารามิเตอร์เป็น () และที่ส่งกลับประเภทของการแสดงออก (ดูคุณสมบัติประเภท) ฟังก์ชั่น autoclosure จับการปิดโดยนัยเหนือการแสดงออกที่ระบุแทนการแสดงออกของตัวเอง ตัวอย่างต่อไปนี้ใช้คุณสมบัติ auto_closure ในการกำหนดฟังก์ชั่นยืนยันที่ง่ายมาก:

และนี่คือตัวอย่างแอปเปิ้ลที่ใช้ควบคู่กับมัน

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.")

โดยทั่วไปความหมายคือคุณส่งนิพจน์บูลีนเป็นอาร์กิวเมนต์แรกแทนการปิดและสร้างการปิดโดยอัตโนมัติสำหรับคุณ นั่นเป็นเหตุผลที่คุณสามารถส่งค่าเท็จเข้าสู่วิธีการได้เนื่องจากเป็นนิพจน์บูลีน แต่ไม่สามารถผ่านการปิดได้


15
โปรดทราบว่าคุณไม่จำเป็นต้องใช้@auto_closureที่นี่ func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }รหัสทำงานได้ดีโดยไม่ได้: ใช้@auto_closureเมื่อคุณต้องการประเมินข้อโต้แย้งซ้ำ ๆ (เช่นถ้าคุณใช้whileฟังก์ชันเหมือนกัน) หรือคุณจำเป็นต้องเลื่อนการประเมินผลการโต้แย้ง (เช่นถ้าคุณใช้การลัดวงจร&&)
ธาน

1
@ นาธานสวัสดีนาธาน คุณช่วยยกตัวอย่างให้ฉันฟังเกี่ยวกับการใช้ฟังก์ชันautoclosurea- whilelike ได้ไหม ฉันดูเหมือนจะไม่เข้าใจว่า ขอบคุณมากล่วงหน้า
Unheilig

@connor คุณอาจต้องการอัปเดตคำตอบของคุณสำหรับ Swift 3
jarora

4

นี่เป็นกรณีที่มีประโยชน์ของ@autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

ตอนนี้นิพจน์เงื่อนไขจะถูกส่งเป็นพารามิเตอร์แรกจนถึงจนกระทั่งจะถูกห่อเป็นนิพจน์การปิดโดยอัตโนมัติและสามารถเรียกแต่ละครั้งรอบลูปได้

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

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

2

เป็นเพียงวิธีกำจัดวงเล็บปีกกาในสายปิดตัวอย่างง่ายๆ:

    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

@autoclosureเป็นพารามิเตอร์ฟังก์ชันที่ยอมรับฟังก์ชั่นที่ปรุงแล้ว (หรือประเภทที่ส่งคืน) ในขณะที่คนทั่วไปclosureยอมรับฟังก์ชั่นดิบ

  • พารามิเตอร์ประเภทอาร์กิวเมนต์ @autoclosure ต้องเป็น '()'
    @autoclosure ()
  • @autoclosure ยอมรับฟังก์ชั่นใด ๆ ที่มีประเภทผลตอบแทนที่เหมาะสมเท่านั้น
  • ผลการปิดทำการคำนวณตามความต้องการ

ลองมาดูตัวอย่าง

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()
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.