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
å
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:
Jest to funkcja, której celowo nie chcemy obsługiwać. Jest wiele rzeczy, które spowodują, że wskaźnik równości funkcji (w sensie systemu typu szybkiego, który obejmuje kilka rodzajów domknięć) zawiedzie lub zmieni się w zależności od optymalizacji. Gdyby w funkcjach zdefiniowano "===", kompilator nie miałby możliwości scalania identycznych treści metod, współużytkowania thunks i wykonywania pewnych optymalizacji przechwytywania w domknięciach. Co więcej, równość tego rodzaju byłaby niezwykle zaskakująca w niektórych kontekstach generycznych, w których można uzyskać operacje reabstrakcyjne, które dostosowują rzeczywistą sygnaturę funkcji do tej, której oczekuje typ funkcji.
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.
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
}))
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
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ć.
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?
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ązanie self
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 do S.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ł, że s1.f
nie jest to ten sam wskaźnik co s2.f
(ponieważ jeden jest obiektem zamknięcia, który odwołuje się do s1
i f
, a drugi jest obiektem zamknięcia, który odwołuje się do s2
i f
).
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
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?
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.
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
}
}
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ł:
Jednak bez możliwości usuwania domknięć z listy wywołań przez odniesienie, musimy utworzyć własną klasę opakowania. To jest problem i nie powinno być konieczne.
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ć Kombinatu AnyCancellable
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 ma removeCallback
metody. Zamiast tego zapisz AnyCancellable
i wywołaj jego cancel
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.
MyClass.self
)