คุณทดสอบฟังก์ชันและการปิดเพื่อความเท่าเทียมกันอย่างไร?


88

หนังสือเล่มนี้ระบุว่า "ฟังก์ชันและการปิดเป็นประเภทอ้างอิง" แล้วคุณจะทราบได้อย่างไรว่าข้อมูลอ้างอิงเท่ากัน? == และ === ไม่ทำงาน

func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments

5
เท่าที่ฉันสามารถบอกได้คุณไม่สามารถตรวจสอบความเท่าเทียมกันของ metaclasses ได้ (เช่นMyClass.self)
Jiaaro

ไม่จำเป็นต้องเปรียบเทียบการปิดสองรายการเพื่อระบุตัวตน คุณช่วยยกตัวอย่างได้ไหมว่าคุณจะทำสิ่งนี้ได้ที่ไหน? อาจมีทางเลือกอื่น
บิล

1
การปิดหลายผู้รับ a la C # พวกเขาจำเป็นต้องอัปลักษณ์ใน Swift เนื่องจากคุณไม่สามารถโอเวอร์โหลดตัวดำเนินการ "(T, U)" มากเกินไป แต่เรายังสามารถสร้างขึ้นเองได้ อย่างไรก็ตามหากไม่สามารถลบการปิดออกจากรายการเรียกใช้โดยการอ้างอิงเราจำเป็นต้องสร้างคลาส Wrapper ของเราเอง นั่นคือการลากและไม่ควรจำเป็น
Jessy

2
คำถามที่ดี แต่สิ่งที่แยกจากกันโดยสิ้นเชิง: การใช้ตัวกำกับเสียงåเพื่ออ้างอิงaนั้นน่าสนใจจริงๆ มีการประชุมที่คุณกำลังสำรวจอยู่ที่นี่หรือไม่? (ฉันไม่รู้ว่าชอบจริงหรือเปล่า แต่ดูเหมือนว่ามันจะทรงพลังมากโดยเฉพาะอย่างยิ่งในการเขียนโปรแกรมที่ใช้งานได้จริง)
Rob Napier

2
@Bill ฉันจัดเก็บการปิดใน Array และไม่สามารถใช้ indexOf ({$ 0 == closure} เพื่อค้นหาและลบออกตอนนี้ฉันต้องปรับโครงสร้างโค้ดของฉันใหม่เนื่องจากการเพิ่มประสิทธิภาพซึ่งฉันเชื่อว่าเป็นการออกแบบภาษาที่ไม่ดี
Zack Morris

คำตอบ:


72

Chris Lattner เขียนในฟอรัมนักพัฒนา:

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

https://devforums.apple.com/message/1035180#1035180

ซึ่งหมายความว่าคุณไม่ควรพยายามเปรียบเทียบการปิดเพื่อความเท่าเทียมกันเนื่องจากการเพิ่มประสิทธิภาพอาจส่งผลต่อผลลัพธ์


19
สิ่งนี้ทำให้ฉันรู้สึกแย่มากเพราะฉันเก็บการปิดไว้ใน Array และตอนนี้ไม่สามารถลบออกด้วย indexOf ได้ ({$ 0 == closure} ดังนั้นฉันจึงต้อง refactor การเพิ่มประสิทธิภาพ IMHO ไม่ควรมีผลต่อการออกแบบภาษา ดังนั้นหากไม่มีการแก้ไขอย่างรวดเร็วเช่น @objc_block ที่เลิกใช้แล้วในคำตอบของ Matt ฉันขอยืนยันว่า Swift ไม่สามารถจัดเก็บและเรียกข้อมูลการปิดได้อย่างถูกต้องในขณะนี้ดังนั้นฉันไม่คิดว่าเหมาะสมที่จะสนับสนุนการใช้ Swift ในรหัสเรียกกลับจำนวนมาก เช่นเดียวกับที่พบในการพัฒนาเว็บซึ่งเป็นเหตุผลทั้งหมดที่เราเปลี่ยนมาใช้ Swift ตั้งแต่แรก ...
Zack Morris

4
@ZackMorris จัดเก็บตัวระบุบางประเภทด้วยการปิดเพื่อให้คุณสามารถลบออกได้ในภายหลัง หากคุณใช้ประเภทการอ้างอิงคุณสามารถจัดเก็บข้อมูลอ้างอิงไปยังออบเจ็กต์มิฉะนั้นคุณสามารถสร้างระบบตัวระบุของคุณเองได้ คุณสามารถออกแบบประเภทที่มีตัวปิดและตัวระบุเฉพาะที่คุณสามารถใช้แทนการปิดธรรมดาได้
drewag

5
@drewag ใช่มีวิธีแก้ปัญหา แต่ Zack พูดถูก นี่มันง่อยจริงๆ ฉันเข้าใจว่าต้องการเพิ่มประสิทธิภาพ แต่หากมีโค้ดบางส่วนที่นักพัฒนาต้องการเปรียบเทียบการปิดบางส่วนก็ให้คอมไพเลอร์ไม่ปรับแต่งส่วนเฉพาะเหล่านั้นให้เหมาะสม หรือสร้างฟังก์ชันเพิ่มเติมบางอย่างของคอมไพเลอร์ที่ช่วยให้สามารถสร้างลายเซ็นความเท่าเทียมกันที่ไม่ทำลายด้วยการเพิ่มประสิทธิภาพที่ผิดปกติ นี่คือ Apple ที่เรากำลังพูดถึงที่นี่ ... หากพวกเขาสามารถใส่ Xeon เข้ากับ iMac ได้ก็สามารถทำการปิดได้อย่างแน่นอน ให้ฉันหยุดพัก!
CommaToast

10

ฉันค้นหามาก ดูเหมือนจะไม่มีวิธีเปรียบเทียบตัวชี้ฟังก์ชัน ทางออกที่ดีที่สุดที่ฉันได้รับคือการห่อหุ้มฟังก์ชันหรือการปิดในวัตถุที่ล้างทำความสะอาดได้ ชอบ:

var handler:Handler = Handler(callback: { (message:String) in
            //handler body
}))

2
นี่คือแนวทางที่ดีที่สุด มันแย่มากที่ต้องห่อและแกะการปิด แต่ก็ดีกว่าความเปราะบางที่ไม่เป็นที่ยอมรับและไม่ได้รับการสนับสนุน

8

วิธีที่ง่ายที่สุดคือการกำหนดประเภทบล็อกเป็น@objc_blockและตอนนี้คุณสามารถโยนมันทิ้งไป AnyObject ===ซึ่งเปรียบกับ ตัวอย่าง:

    typealias Ftype = @objc_block (s:String) -> ()

    let f : Ftype = {
        ss in
        println(ss)
    }
    let ff : Ftype = {
        sss in
        println(sss)
    }
    let obj1 = unsafeBitCast(f, AnyObject.self)
    let obj2 = unsafeBitCast(ff, AnyObject.self)
    let obj3 = unsafeBitCast(f, AnyObject.self)

    println(obj1 === obj2) // false
    println(obj1 === obj3) // true

เฮ้ฉันกำลังลองถ้า unsafeBitCast (ผู้ฟัง AnyObject.self) === unsafeBitCast (f, AnyObject.self) แต่ได้รับข้อผิดพลาดร้ายแรง: ไม่สามารถ unsafeBitCast ระหว่างประเภทของขนาดต่างๆได้ แนวคิดคือการสร้างระบบตามเหตุการณ์ แต่เมธอด removeEventListener ควรตรวจสอบตัวชี้ฟังก์ชันได้
freezing_

2
ใช้ @convention (block) แทน @objc_block บน Swift 2.x ตอบโจทย์มาก!
Gabriel.Massana

6

ฉันกำลังมองหาคำตอบอยู่เช่นกัน และฉันได้พบมันในที่สุด

สิ่งที่คุณต้องการคือตัวชี้ฟังก์ชันจริงและบริบทที่ซ่อนอยู่ในวัตถุฟังก์ชัน

func peekFunc<A,R>(f:A->R)->(fp:Int, ctx:Int) {
    typealias IntInt = (Int, Int)
    let (hi, lo) = unsafeBitCast(f, IntInt.self)
    let offset = sizeof(Int) == 8 ? 16 : 12
    let ptr  = UnsafePointer<Int>(lo+offset)
    return (ptr.memory, ptr.successor().memory)
}
@infix func === <A,R>(lhs:A->R,rhs:A->R)->Bool {
    let (tl, tr) = (peekFunc(lhs), peekFunc(rhs))
    return tl.0 == tr.0 && tl.1 == tr.1
}

และนี่คือการสาธิต:

// simple functions
func genericId<T>(t:T)->T { return t }
func incr(i:Int)->Int { return i + 1 }
var f:Int->Int = genericId
var g = f;      println("(f === g) == \(f === g)")
f = genericId;  println("(f === g) == \(f === g)")
f = g;          println("(f === g) == \(f === g)")
// closures
func mkcounter()->()->Int {
    var count = 0;
    return { count++ }
}
var c0 = mkcounter()
var c1 = mkcounter()
var c2 = c0
println("peekFunc(c0) == \(peekFunc(c0))")
println("peekFunc(c1) == \(peekFunc(c1))")
println("peekFunc(c2) == \(peekFunc(c2))")
println("(c0() == c1()) == \(c0() == c1())") // true : both are called once
println("(c0() == c2()) == \(c0() == c2())") // false: because c0() means c2()
println("(c0 === c1) == \(c0 === c1)")
println("(c0 === c2) == \(c0 === c2)")

ดู URL ด้านล่างเพื่อค้นหาสาเหตุและวิธีการทำงาน:

อย่างที่คุณเห็นมันสามารถตรวจสอบตัวตนเท่านั้น (ผลการทดสอบครั้งที่ 2 false) แต่นั่นก็น่าจะดีพอ


5
วิธีนี้จะไม่น่าเชื่อถือกับการเพิ่มประสิทธิภาพคอมไพเลอร์devforums.apple.com/message/1035180#1035180
drewag

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

8
โปรดทราบว่าสิ่งนี้อาศัยข้อมูลที่ไม่มีเอกสารและรายละเอียดการใช้งานที่ไม่เปิดเผยซึ่งอาจทำให้แอปของคุณขัดข้องได้ในอนาคตหากมีการเปลี่ยนแปลง ไม่แนะนำให้ใช้ในรหัสการผลิต
Cristik

นี่คือ "โคลเวอร์" แต่ใช้งานไม่ได้โดยสิ้นเชิง ฉันไม่รู้ว่าทำไมถึงได้รับรางวัลเป็นค่าหัว ภาษาโดยเจตนาไม่มีความเท่าเทียมกันของฟังก์ชันเพื่อจุดประสงค์ที่แน่นอนในการปลดปล่อยคอมไพเลอร์เพื่อทำลายความเท่าเทียมกันของฟังก์ชันอย่างอิสระเพื่อให้ได้การปรับแต่งที่ดีขึ้น
Alexander

... และนี่คือแนวทางที่ Chris Lattner ต่อต้าน (ดูคำตอบด้านบน)
pipacs

4

นี่เป็นคำถามที่ยอดเยี่ยมและในขณะที่ Chris Lattner ตั้งใจไม่ต้องการสนับสนุนคุณลักษณะนี้ฉันก็เหมือนกับนักพัฒนาหลายคน แต่ก็ไม่สามารถละทิ้งความรู้สึกของฉันที่มาจากภาษาอื่นซึ่งเป็นงานที่ไม่สำคัญ มีunsafeBitCastตัวอย่างมากมายส่วนใหญ่ไม่แสดงภาพเต็มนี่คือตัวอย่างรายละเอียดเพิ่มเติม :

typealias SwfBlock = () -> ()
typealias ObjBlock = @convention(block) () -> ()

func testSwfBlock(a: SwfBlock, _ b: SwfBlock) -> String {
    let objA = unsafeBitCast(a as ObjBlock, AnyObject.self)
    let objB = unsafeBitCast(b as ObjBlock, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

func testObjBlock(a: ObjBlock, _ b: ObjBlock) -> String {
    let objA = unsafeBitCast(a, AnyObject.self)
    let objB = unsafeBitCast(b, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

func testAnyBlock(a: Any?, _ b: Any?) -> String {
    if !(a is ObjBlock) || !(b is ObjBlock) {
        return "a nor b are ObjBlock, they are not equal"
    }
    let objA = unsafeBitCast(a as! ObjBlock, AnyObject.self)
    let objB = unsafeBitCast(b as! ObjBlock, AnyObject.self)
    return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}

class Foo
{
    lazy var swfBlock: ObjBlock = self.swf
    func swf() { print("swf") }
    @objc func obj() { print("obj") }
}

let swfBlock: SwfBlock = { print("swf") }
let objBlock: ObjBlock = { print("obj") }
let foo: Foo = Foo()

print(testSwfBlock(swfBlock, swfBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testSwfBlock(objBlock, objBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false

print(testObjBlock(swfBlock, swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testObjBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

print(testAnyBlock(swfBlock, swfBlock)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

print(testObjBlock(foo.swf, foo.swf)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testSwfBlock(foo.obj, foo.obj)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testAnyBlock(foo.swf, foo.swf)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(foo.swfBlock, foo.swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true

ส่วนที่น่าสนใจคือการเหวี่ยง SwfBlock ไปยัง ObjBlock อย่างรวดเร็วได้อย่างไร แต่ในความเป็นจริงบล็อก SwfBlock สองบล็อกจะมีค่าต่างกันเสมอในขณะที่ ObjBlocks จะไม่ เมื่อเราร่าย ObjBlock ไปที่ SwfBlock สิ่งเดียวกันนี้ก็เกิดขึ้นกับพวกมันพวกมันจะกลายเป็นค่าสองค่า ดังนั้นเพื่อรักษาข้อมูลอ้างอิงควรหลีกเลี่ยงการหล่อแบบนี้

ฉันยังคงเข้าใจเรื่องนี้ทั้งหมด แต่สิ่งหนึ่งที่ฉันต้องการก็คือความสามารถในการใช้@convention(block)วิธีการเรียน / โครงสร้างดังนั้นฉันจึงยื่นคำขอคุณลักษณะที่ต้องการการโหวตเพิ่มหรืออธิบายว่าเหตุใดจึงเป็นความคิดที่ไม่ดี ฉันยังรู้สึกว่าแนวทางนี้อาจไม่ดีด้วยกันถ้าเป็นเช่นนั้นใครสามารถอธิบายได้ว่าทำไม?


1
ฉันไม่คิดว่าคุณเข้าใจเหตุผลของ Chris Latner ว่าทำไมถึงไม่รองรับ (และไม่ควร) "ฉันยังเข้าใจว่าแนวทางนี้อาจไม่ดีด้วยกันถ้าเป็นเช่นนั้นใครสามารถอธิบายได้ว่าทำไม" เนื่องจากในโครงสร้างที่ปรับให้เหมาะสมคอมไพลเลอร์จึงมีอิสระที่จะยุ่งเหยิงโค้ดในหลาย ๆ วิธีที่ทำลายแนวคิดเรื่องความเท่าเทียมกันของจุดของฟังก์ชัน ตัวอย่างพื้นฐานหากเนื้อหาของฟังก์ชันหนึ่งเริ่มต้นในลักษณะเดียวกับที่ฟังก์ชันอื่นทำคอมไพลเลอร์มีแนวโน้มที่จะซ้อนทับทั้งสองในรหัสเครื่องโดยจะรักษาจุดออกที่ต่างกันเท่านั้น สิ่งนี้ช่วยลดความซ้ำซ้อน
Alexander

1
โดยพื้นฐานแล้วการปิดเป็นวิธีการเริ่มต้นออบเจ็กต์ของคลาสที่ไม่ระบุตัวตน (เช่นเดียวกับใน Java แต่มีความชัดเจนมากกว่า) อ็อบเจ็กต์การปิดเหล่านี้ได้รับการจัดสรรฮีปและจัดเก็บข้อมูลที่จับโดยการปิดซึ่งทำหน้าที่เหมือนพารามิเตอร์โดยนัยของฟังก์ชันการปิด วัตถุปิดมีการอ้างอิงถึงฟังก์ชันที่ทำงานบนอาร์กิวเมนต์ที่ชัดเจน (ผ่านทาง func) และโดยปริยาย (ผ่านบริบทการปิดที่ถูกจับ) ในขณะที่สามารถแชร์เนื้อหาฟังก์ชันเป็นจุดเฉพาะจุดเดียวได้ แต่ตัวชี้ของวัตถุปิดไม่สามารถเป็นได้เนื่องจากมีวัตถุปิดหนึ่งรายการต่อชุดของค่าที่ปิดล้อม
Alexander

1
ดังนั้นเมื่อคุณมีStruct S { func f(_: Int) -> Bool }คุณจริงมีฟังก์ชั่นของชนิดซึ่งมีประเภทS.f (S) -> (Int) -> Boolฟังก์ชันนี้สามารถใช้ร่วมกันได้ เป็นพารามิเตอร์ที่กำหนดโดยพารามิเตอร์ที่ชัดเจนเท่านั้น โดยเมื่อคุณใช้เป็นวิธีการอินสแตนซ์ (โดยการผูกselfพารามิเตอร์โดยปริยายโดยการเรียกใช้เมธอดกับอ็อบเจ็กต์เช่นS().fหรือโดยการโยงอย่างชัดเจนเช่นS.f(S())) คุณจะสร้างอ็อบเจ็กต์ปิดใหม่ วัตถุนี้เก็บตัวชี้ไปที่S.f(ซึ่งสามารถแชร์ได้) , but also to your instance (self , the S () `)
Alexander

1
Sวัตถุปิดนี้จะต้องไม่ซ้ำกันต่อตัวอย่างของ หากความเท่าเทียมกันของตัวชี้การปิดเป็นไปได้คุณจะต้องประหลาดใจเมื่อพบว่าs1.fตัวชี้นั้นไม่ใช่ตัวชี้เดียวกับs2.f(เนื่องจากตัวชี้เป็นวัตถุปิดซึ่งอ้างอิงs1และfอีกชิ้นหนึ่งเป็นวัตถุปิดซึ่งอ้างอิงs2และf)
Alexander

ยอดเยี่ยมมากขอบคุณ! ใช่ตอนนี้ฉันมีภาพของสิ่งที่เกิดขึ้นและสิ่งนี้ทำให้ทุกอย่างเป็นมุมมอง! 👍
Ian Bytchek

4

นี่คือวิธีแก้ปัญหาที่เป็นไปได้ทางหนึ่ง (แนวคิดเหมือนกับคำตอบของ 'tuncay') ประเด็นคือการกำหนดคลาสที่ครอบคลุมฟังก์ชันการทำงานบางอย่าง (เช่น Command):

รวดเร็ว:

typealias Callback = (Any...)->Void
class Command {
    init(_ fn: @escaping Callback) {
        self.fn_ = fn
    }

    var exec : (_ args: Any...)->Void {
        get {
            return fn_
        }
    }
    var fn_ :Callback
}

let cmd1 = Command { _ in print("hello")}
let cmd2 = cmd1
let cmd3 = Command { (_ args: Any...) in
    print(args.count)
}

cmd1.exec()
cmd2.exec()
cmd3.exec(1, 2, "str")

cmd1 === cmd2 // true
cmd1 === cmd3 // false

Java:

interface Command {
    void exec(Object... args);
}
Command cmd1 = new Command() {
    public void exec(Object... args) [
       // do something
    }
}
Command cmd2 = cmd1;
Command cmd3 = new Command() {
   public void exec(Object... args) {
      // do something else
   }
}

cmd1 == cmd2 // true
cmd1 == cmd3 // false

สิ่งนี้จะดีกว่ามากถ้าคุณทำให้เป็น Generic
Alexander

2

เป็นเวลา 2 วันแล้วและยังไม่มีใครรู้วิธีแก้ปัญหาดังนั้นฉันจะเปลี่ยนความคิดเห็นเป็นคำตอบ:

เท่าที่ฉันสามารถบอกได้คุณไม่สามารถตรวจสอบความเท่าเทียมกันหรือเอกลักษณ์ของฟังก์ชัน (เช่นตัวอย่างของคุณ) และ metaclasses (เช่นMyClass.self):

แต่ - และนี่เป็นเพียงความคิด - ฉันอดไม่ได้ที่จะสังเกตว่าwhereประโยคในชื่อสามัญดูเหมือนจะตรวจสอบความเท่าเทียมกันของประเภทได้ ดังนั้นคุณอาจใช้ประโยชน์จากสิ่งนั้นได้อย่างน้อยก็สำหรับการตรวจสอบตัวตน?


2

ไม่ใช่วิธีแก้ปัญหาทั่วไป แต่ถ้ามีใครพยายามใช้รูปแบบผู้ฟังฉันได้ส่งคืน "id" ของฟังก์ชันในระหว่างการลงทะเบียนดังนั้นฉันจึงสามารถใช้เพื่อยกเลิกการลงทะเบียนในภายหลัง (ซึ่งเป็นวิธีแก้ปัญหาสำหรับคำถามเดิม สำหรับกรณี "ผู้ฟัง" โดยปกติแล้วการยกเลิกการลงทะเบียนจะลงมาเพื่อตรวจสอบฟังก์ชันเพื่อความเท่าเทียมกันซึ่งอย่างน้อยก็ไม่ "เล็กน้อย" ตามคำตอบอื่น ๆ )

ดังนั้นสิ่งนี้:

class OfflineManager {
    var networkChangedListeners = [String:((Bool) -> Void)]()

    func registerOnNetworkAvailabilityChangedListener(_ listener: @escaping ((Bool) -> Void)) -> String{
        let listenerId = UUID().uuidString;
        networkChangedListeners[listenerId] = listener;
        return listenerId;
    }
    func unregisterOnNetworkAvailabilityChangedListener(_ listenerId: String){
        networkChangedListeners.removeValue(forKey: listenerId);
    }
}

ตอนนี้คุณเพียงแค่จัดเก็บสิ่งที่keyส่งคืนโดยฟังก์ชัน "register" และส่งต่อเมื่อยกเลิกการลงทะเบียน


0

วิธีแก้ปัญหาของฉันคือการรวมฟังก์ชันเข้ากับคลาสที่ขยาย NSObject

class Function<Type>: NSObject {
    let value: (Type) -> Void

    init(_ function: @escaping (Type) -> Void) {
        value = function
    }
}

เมื่อคุณทำเช่นนั้นจะเปรียบเทียบได้อย่างไร? สมมติว่าคุณต้องการลบหนึ่งในนั้นออกจากอาร์เรย์ของ Wrapper คุณจะทำอย่างไร? ขอบคุณ.
Ricardo

0

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

อย่างไรก็ตามหากไม่สามารถลบการปิดออกจากรายการเรียกใช้โดยการอ้างอิงเราจำเป็นต้องสร้างคลาส Wrapper ของเราเอง นั่นคือการลากและไม่ควรจำเป็น

ดังนั้นฉันเดาว่าผู้ถามต้องการรักษารายการโทรกลับเช่นนี้:

class CallbackList {
    private var callbacks: [() -> ()] = []

    func call() {
        callbacks.forEach { $0() }
    }

    func addCallback(_ callback: @escaping () -> ()) {
        callbacks.append(callback)
    }

    func removeCallback(_ callback: @escaping () -> ()) {
        callbacks.removeAll(where: { $0 == callback })
    }
}

แต่เราเขียนremoveCallbackแบบนั้น==ไม่ได้เพราะใช้ไม่ได้กับฟังก์ชัน (ไม่ทำ===เช่นกัน)

นี่เป็นวิธีอื่นในการจัดการรายการโทรกลับของคุณ ส่งคืนอ็อบเจ็กต์การลงทะเบียนจากaddCallbackและใช้อ็อบเจ็กต์การลงทะเบียนเพื่อลบการเรียกกลับ ในปี 2020 เราสามารถใช้ Combine's AnyCancellableเป็นการลงทะเบียนได้

API ที่แก้ไขมีลักษณะดังนี้:

class CallbackList {
    private var callbacks: [NSObject: () -> ()] = [:]

    func call() {
        callbacks.values.forEach { $0() }
    }

    func addCallback(_ callback: @escaping () -> ()) -> AnyCancellable {
        let key = NSObject()
        callbacks[key] = callback
        return .init { self.callbacks.removeValue(forKey: key) }
    }
}

ตอนนี้เมื่อคุณเพิ่มการโทรกลับคุณไม่จำเป็นต้องเก็บไว้เพื่อส่งremoveCallbackต่อในภายหลัง ไม่มีremoveCallbackวิธีการใดๆ แต่ให้คุณบันทึกAnyCancellableและเรียกcancelใช้เมธอดเพื่อลบการโทรกลับ ยิ่งไปกว่านั้นหากคุณจัดเก็บคุณสมบัติAnyCancellableในอินสแตนซ์มันจะยกเลิกตัวเองโดยอัตโนมัติเมื่ออินสแตนซ์ถูกทำลาย


เหตุผลส่วนใหญ่ที่เราต้องการสิ่งนี้คือการจัดการสมาชิกหลายคนสำหรับผู้เผยแพร่โฆษณา รวมวิธีแก้ปัญหาที่ไม่มีทั้งหมดนี้ สิ่งที่ C # อนุญาตและ Swift ไม่ได้คือการตรวจสอบว่าการปิดสองครั้งอ้างอิงฟังก์ชันชื่อเดียวกันหรือไม่ นั่นก็มีประโยชน์เช่นกัน แต่มักจะน้อยกว่ามาก
Jessy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.