Książka mówi, że „funkcje i zamknięcia są typami referencyjnymi”. Jak więc sprawdzić, czy odniesienia są równe? == i === nie działają.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
Książka mówi, że „funkcje i zamknięcia są typami referencyjnymi”. Jak więc sprawdzić, czy odniesienia są równe? == i === nie działają.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
MyClass.self
)å
odniesieniu do odniesieńa
jest naprawdę interesujące. Czy jest jakaś konwencja, którą tu eksplorujesz? (Nie wiem, czy mi się to podoba, czy nie; ale wygląda na to, że może być bardzo potężny, szczególnie w programowaniu czysto funkcjonalnym.)Odpowiedzi:
Chris Lattner napisał na forach programistów:
https://devforums.apple.com/message/1035180#1035180
Oznacza to, że nie powinieneś nawet próbować porównywać domknięć pod kątem równości, ponieważ optymalizacje mogą wpłynąć na wynik.
źródło
Szukałem dużo. Wydaje się, że nie ma możliwości porównania wskaźników funkcji. Najlepszym rozwiązaniem, jakie otrzymałem, jest hermetyzacja funkcji lub zamknięcia w obiekcie, który można skasować. Lubić:
var handler:Handler = Handler(callback: { (message:String) in //handler body }))
źródło
Najprostszym sposobem jest wyznaczenie typu bloku jako
@objc_block
, a teraz możesz rzutować go na AnyObject, który jest porównywalny z===
. Przykład: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
źródło
Ja też szukałem odpowiedzi. I w końcu go znalazłem.
Potrzebny jest rzeczywisty wskaźnik funkcji i jego kontekst ukryty w obiekcie funkcji.
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 }
A oto demo:
// 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)")
Zobacz poniższe adresy URL, aby dowiedzieć się, dlaczego i jak to działa:
Jak widzisz, jest w stanie sprawdzić tylko tożsamość (drugi test daje wyniki
false
). Ale to powinno wystarczyć.źródło
To świetne pytanie i chociaż Chris Lattner celowo nie chce wspierać tej funkcji, podobnie jak wielu programistów, nie mogę również odpuścić moich uczuć pochodzących z innych języków, w których jest to trywialne zadanie. Istnieje wiele
unsafeBitCast
przykładów, większość z nich nie pokazuje pełnego obrazu, oto bardziej szczegółowy :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
Interesujące jest to, jak szybko rzuca SwfBlock na ObjBlock, ale w rzeczywistości dwa rzucane bloki SwfBlock zawsze będą miały różne wartości, podczas gdy ObjBlocks nie. Kiedy rzucamy ObjBlock na SwfBlock, dzieje się z nimi to samo, stają się dwiema różnymi wartościami. Tak więc, aby zachować odniesienie, należy unikać tego rodzaju rzutowania.
Wciąż rozumiem cały ten temat, ale jedyną rzeczą, której pragnąłem, jest możliwość użycia
@convention(block)
w metodach klas / struktur, więc złożyłem prośbę o funkcję, która wymaga głosowania w górę lub wyjaśnienia, dlaczego jest to zły pomysł. Mam też poczucie, że to podejście może być złe w sumie, jeśli tak, czy ktoś może wyjaśnić dlaczego?źródło
Struct S { func f(_: Int) -> Bool }
, w rzeczywistości masz funkcję typu,S.f
która ma typ(S) -> (Int) -> Bool
. Ta funkcja może być współdzielona. Jest parametryzowana wyłącznie przez swoje jawne parametry. Gdy używasz go jako metody instancji (albo przez niejawne wiązanieself
parametru przez wywołanie metody na obiekcie, np.S().f
Lub przez jawne wiązanie go, np.S.f(S())
), Tworzysz nowy obiekt zamknięcia. Ten obiekt przechowuje wskaźnik doS.f
(który można udostępniać), but also to your instance (
self, the
S () `).S
. Gdyby równość wskaźnika zamknięcia była możliwa, byłbyś zaskoczony, gdybyś odkrył, żes1.f
nie jest to ten sam wskaźnik cos2.f
(ponieważ jeden jest obiektem zamknięcia, który odwołuje się dos1
if
, a drugi jest obiektem zamknięcia, który odwołuje się dos2
if
).Oto jedno możliwe rozwiązanie (koncepcyjnie to samo, co odpowiedź „tuncay”). Chodzi o to, aby zdefiniować klasę, która opakowuje niektóre funkcjonalności (np. Command):
Szybki:
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
Jawa:
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
źródło
Cóż, minęły 2 dni i nikt nie włączył rozwiązania, więc zmienię swój komentarz na odpowiedź:
O ile wiem, nie możesz sprawdzić równości lub tożsamości funkcji (jak Twój przykład) i metaklas (np.
MyClass.self
):Ale - i to jest tylko pomysł - nie mogę nie zauważyć, że
where
klauzula w rodzajach wydaje się być w stanie sprawdzić równość typów. Więc może możesz to wykorzystać, przynajmniej do sprawdzenia tożsamości?źródło
Nie jest to ogólne rozwiązanie, ale jeśli ktoś próbuje zaimplementować wzorzec nasłuchiwania, w końcu zwróciłem "id" funkcji podczas rejestracji, więc mogę go użyć do późniejszego wyrejestrowania (co jest rodzajem obejścia pierwotnego pytania w przypadku "słuchaczy" jak zwykle wyrejestrowanie sprowadza się do sprawdzenia funkcji pod kątem równości, co przynajmniej nie jest "trywialne" jak w innych odpowiedziach).
Więc coś takiego:
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); } }
Teraz wystarczy tylko zapamiętać
key
zwracany przez funkcję "register" i przekazać go przy wyrejestrowaniu.źródło
Moim rozwiązaniem było zawinięcie funkcji do klasy, która rozszerza NSObject
class Function<Type>: NSObject { let value: (Type) -> Void init(_ function: @escaping (Type) -> Void) { value = function } }
źródło
Wiem, że odpowiadam na to pytanie sześć lat później, ale myślę, że warto przyjrzeć się motywacji stojącej za tym pytaniem. Pytający skomentował:
Więc wydaje mi się, że pytający chce prowadzić listę oddzwonień, taką jak ta:
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 }) } }
Ale nie możemy pisać w
removeCallback
ten sposób, ponieważ==
nie działa dla funkcji. (Ani też===
.)Oto inny sposób zarządzania listą oddzwonień. Zwróć obiekt rejestracji z
addCallback
i użyj obiektu rejestracji, aby usunąć wywołanie zwrotne. Tutaj w 2020 roku możemy użyć KombinatuAnyCancellable
jako rejestracji.Zmieniony interfejs API wygląda następująco:
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) } } }
Teraz, gdy dodajesz oddzwonienie, nie musisz go przechowywać, aby przejść na
removeCallback
później. Nie maremoveCallback
metody. Zamiast tego zapiszAnyCancellable
i wywołaj jegocancel
metodę, aby usunąć wywołanie zwrotne. Co więcej, jeśli przechowujesz właściwośćAnyCancellable
in a instance, automatycznie anuluje się ona po zniszczeniu instancji.źródło