หนังสือเล่มนี้ระบุว่า "ฟังก์ชันและการปิดเป็นประเภทอ้างอิง" แล้วคุณจะทราบได้อย่างไรว่าข้อมูลอ้างอิงเท่ากัน? == และ === ไม่ทำงาน
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
หนังสือเล่มนี้ระบุว่า "ฟังก์ชันและการปิดเป็นประเภทอ้างอิง" แล้วคุณจะทราบได้อย่างไรว่าข้อมูลอ้างอิงเท่ากัน? == และ === ไม่ทำงาน
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
å
เพื่ออ้างอิงa
นั้นน่าสนใจจริงๆ มีการประชุมที่คุณกำลังสำรวจอยู่ที่นี่หรือไม่? (ฉันไม่รู้ว่าชอบจริงหรือเปล่า แต่ดูเหมือนว่ามันจะทรงพลังมากโดยเฉพาะอย่างยิ่งในการเขียนโปรแกรมที่ใช้งานได้จริง)
คำตอบ:
Chris Lattner เขียนในฟอรัมนักพัฒนา:
นี่เป็นคุณสมบัติที่เราไม่ต้องการสนับสนุนโดยเจตนา มีหลายสิ่งที่จะทำให้ความเท่าเทียมกันของตัวชี้ของฟังก์ชัน (ในความหมายของระบบประเภทรวดเร็วซึ่งรวมถึงการปิดหลายประเภท) ล้มเหลวหรือเปลี่ยนแปลงขึ้นอยู่กับการปรับให้เหมาะสม หากกำหนด "===" ในฟังก์ชันคอมไพลเลอร์จะไม่ได้รับอนุญาตให้รวมเมธอดที่เหมือนกันแบ่งปัน thunks และดำเนินการเพิ่มประสิทธิภาพการจับภาพบางอย่างในการปิด นอกจากนี้ความเท่าเทียมกันของการเรียงลำดับนี้จะเป็นเรื่องที่น่าแปลกใจอย่างยิ่งในบริบททั่วไปบางประการซึ่งคุณจะได้รับการตอบกลับที่น่าทึ่งซึ่งปรับลายเซ็นจริงของฟังก์ชันให้เป็นแบบที่ประเภทฟังก์ชันคาดไว้
https://devforums.apple.com/message/1035180#1035180
ซึ่งหมายความว่าคุณไม่ควรพยายามเปรียบเทียบการปิดเพื่อความเท่าเทียมกันเนื่องจากการเพิ่มประสิทธิภาพอาจส่งผลต่อผลลัพธ์
ฉันค้นหามาก ดูเหมือนจะไม่มีวิธีเปรียบเทียบตัวชี้ฟังก์ชัน ทางออกที่ดีที่สุดที่ฉันได้รับคือการห่อหุ้มฟังก์ชันหรือการปิดในวัตถุที่ล้างทำความสะอาดได้ ชอบ:
var handler:Handler = Handler(callback: { (message:String) in
//handler body
}))
วิธีที่ง่ายที่สุดคือการกำหนดประเภทบล็อกเป็น@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
ฉันกำลังมองหาคำตอบอยู่เช่นกัน และฉันได้พบมันในที่สุด
สิ่งที่คุณต้องการคือตัวชี้ฟังก์ชันจริงและบริบทที่ซ่อนอยู่ในวัตถุฟังก์ชัน
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
) แต่นั่นก็น่าจะดีพอ
นี่เป็นคำถามที่ยอดเยี่ยมและในขณะที่ 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)
วิธีการเรียน / โครงสร้างดังนั้นฉันจึงยื่นคำขอคุณลักษณะที่ต้องการการโหวตเพิ่มหรืออธิบายว่าเหตุใดจึงเป็นความคิดที่ไม่ดี ฉันยังรู้สึกว่าแนวทางนี้อาจไม่ดีด้วยกันถ้าเป็นเช่นนั้นใครสามารถอธิบายได้ว่าทำไม?
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 () `)
S
วัตถุปิดนี้จะต้องไม่ซ้ำกันต่อตัวอย่างของ หากความเท่าเทียมกันของตัวชี้การปิดเป็นไปได้คุณจะต้องประหลาดใจเมื่อพบว่าs1.f
ตัวชี้นั้นไม่ใช่ตัวชี้เดียวกับs2.f
(เนื่องจากตัวชี้เป็นวัตถุปิดซึ่งอ้างอิงs1
และf
อีกชิ้นหนึ่งเป็นวัตถุปิดซึ่งอ้างอิงs2
และf
)
นี่คือวิธีแก้ปัญหาที่เป็นไปได้ทางหนึ่ง (แนวคิดเหมือนกับคำตอบของ '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
เป็นเวลา 2 วันแล้วและยังไม่มีใครรู้วิธีแก้ปัญหาดังนั้นฉันจะเปลี่ยนความคิดเห็นเป็นคำตอบ:
เท่าที่ฉันสามารถบอกได้คุณไม่สามารถตรวจสอบความเท่าเทียมกันหรือเอกลักษณ์ของฟังก์ชัน (เช่นตัวอย่างของคุณ) และ metaclasses (เช่นMyClass.self
):
แต่ - และนี่เป็นเพียงความคิด - ฉันอดไม่ได้ที่จะสังเกตว่าwhere
ประโยคในชื่อสามัญดูเหมือนจะตรวจสอบความเท่าเทียมกันของประเภทได้ ดังนั้นคุณอาจใช้ประโยชน์จากสิ่งนั้นได้อย่างน้อยก็สำหรับการตรวจสอบตัวตน?
ไม่ใช่วิธีแก้ปัญหาทั่วไป แต่ถ้ามีใครพยายามใช้รูปแบบผู้ฟังฉันได้ส่งคืน "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" และส่งต่อเมื่อยกเลิกการลงทะเบียน
วิธีแก้ปัญหาของฉันคือการรวมฟังก์ชันเข้ากับคลาสที่ขยาย NSObject
class Function<Type>: NSObject {
let value: (Type) -> Void
init(_ function: @escaping (Type) -> Void) {
value = function
}
}
ฉันรู้ว่าฉันกำลังตอบคำถามนี้ช้าไปหกปี แต่ฉันคิดว่ามันคุ้มค่าที่จะดูแรงจูงใจที่อยู่เบื้องหลังคำถาม ผู้ถามแสดงความคิดเห็นว่า
อย่างไรก็ตามหากไม่สามารถลบการปิดออกจากรายการเรียกใช้โดยการอ้างอิงเราจำเป็นต้องสร้างคลาส 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
ในอินสแตนซ์มันจะยกเลิกตัวเองโดยอัตโนมัติเมื่ออินสแตนซ์ถูกทำลาย
MyClass.self
)