El libro dice que "las funciones y los cierres son tipos de referencia". Entonces, ¿cómo saber si las referencias son iguales? == y === no funcionan.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
MyClass.self
)å
hacer referenciaa
es realmente interesante. ¿Hay alguna convención que estés explorando aquí? (No sé si realmente me gusta o no; pero parece que podría ser muy poderoso, especialmente en la programación funcional pura.)Respuestas:
Chris Lattner escribió en los foros de desarrolladores:
https://devforums.apple.com/message/1035180#1035180
Esto significa que ni siquiera debe intentar comparar los cierres por igualdad porque las optimizaciones pueden afectar el resultado.
fuente
Busqué mucho. Parece que no hay forma de comparar el puntero de función. La mejor solución que obtuve es encapsular la función o el cierre en un objeto hash. Me gusta:
var handler:Handler = Handler(callback: { (message:String) in //handler body }))
fuente
La forma más sencilla es designar el tipo de bloque como
@objc_block
, y ahora puede convertirlo en un AnyObject que sea comparable con===
. Ejemplo: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
fuente
Yo también he estado buscando la respuesta. Y por fin lo encontré.
Lo que necesita es el puntero de función real y su contexto oculto en el objeto de función.
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 }
Y aquí está la demostración:
// 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)")
Consulte las URL a continuación para averiguar por qué y cómo funciona:
Como puede ver, solo es capaz de verificar la identidad (la segunda prueba da como resultado
false
). Pero eso debería ser suficiente.fuente
Esta es una gran pregunta y aunque Chris Lattner intencionalmente no quiere admitir esta característica, yo, como muchos desarrolladores, tampoco puedo dejar de lado mis sentimientos provenientes de otros lenguajes donde esta es una tarea trivial. Hay muchos
unsafeBitCast
ejemplos, la mayoría de ellos no muestran la imagen completa, aquí hay uno más detallado :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
La parte interesante es cómo Swift lanza libremente SwfBlock a ObjBlock, pero en realidad dos bloques SwfBlock emitidos siempre tendrán valores diferentes, mientras que ObjBlocks no. Cuando lanzamos ObjBlock a SwfBlock, les sucede lo mismo, se convierten en dos valores diferentes. Entonces, para preservar la referencia, este tipo de casting debe evitarse.
Todavía estoy comprendiendo todo este tema, pero una cosa que dejé deseando es la capacidad de usar
@convention(block)
en métodos de clase / estructura, así que presenté una solicitud de función que necesita una votación positiva o explicar por qué es una mala idea. También tengo la sensación de que este enfoque podría ser malo en conjunto, si es así, ¿alguien puede explicar por qué?fuente
Struct S { func f(_: Int) -> Bool }
, en realidad tiene una función de tipoS.f
que tiene tipo(S) -> (Int) -> Bool
. Esta función se puede compartir. Está parametrizado únicamente por sus parámetros explícitos. Cuando lo usa como un método de instancia (ya sea vinculando implícitamente elself
parámetro llamando al método en un objeto, por ejemploS().f
, o vinculándolo explícitamente, por ejemploS.f(S())
), crea un nuevo objeto de cierre. Este objeto almacena un punteroS.f
(que se puede compartir), but also to your instance (
self, the
S () `).S
. Si la igualdad de puntero de cierre fuera posible, entonces se sorprendería al descubrir ques1.f
no es el mismo puntero ques2.f
(porque uno es un objeto de cierre que hace referencia as1
yf
, y el otro es un objeto de cierre que hace referencia as2
yf
).Aquí hay una posible solución (conceptualmente la misma que la respuesta 'tuncay'). El punto es definir una clase que envuelva alguna funcionalidad (por ejemplo, Command):
Rápido:
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
fuente
Bueno, han pasado 2 días y nadie ha intervenido con una solución, así que cambiaré mi comentario por una respuesta:
Por lo que puedo decir, no puede verificar la igualdad o identidad de funciones (como su ejemplo) y metaclases (por ejemplo,
MyClass.self
):Pero, y esto es solo una idea, no puedo evitar notar que la
where
cláusula en los genéricos parece poder verificar la igualdad de tipos. Entonces, ¿tal vez pueda aprovechar eso, al menos para verificar la identidad?fuente
No es una solución general, pero si uno está tratando de implementar un patrón de escucha, terminé devolviendo un "id" de la función durante el registro para poder usarlo para cancelar el registro más tarde (que es una especie de solución a la pregunta original para el caso de los "oyentes", ya que generalmente la cancelación del registro se reduce a verificar la igualdad de las funciones, que al menos no es "trivial" según otras respuestas).
Entonces algo como esto:
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); } }
Ahora solo necesita almacenar lo
key
devuelto por la función "registrar" y pasarlo al cancelar el registro.fuente
Mi solución fue ajustar funciones a la clase que extiende NSObject
class Function<Type>: NSObject { let value: (Type) -> Void init(_ function: @escaping (Type) -> Void) { value = function } }
fuente
Sé que estoy respondiendo esta pregunta con seis años de retraso, pero creo que vale la pena analizar la motivación detrás de la pregunta. El interrogador comentó:
Así que supongo que el interrogador quiere mantener una lista de devolución de llamada, como esta:
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 }) } }
Pero no podemos escribir de
removeCallback
esa manera, porque==
no funciona para funciones. (Tampoco lo hace===
).Esta es una forma diferente de administrar su lista de devolución de llamada. Devuelve un objeto de registro de
addCallback
y usa el objeto de registro para eliminar la devolución de llamada. Aquí en 2020, podemos usar el CombineAnyCancellable
como registro.La API revisada se ve así:
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) } } }
Ahora, cuando agrega una devolución de llamada, no es necesario que la guarde para pasarla
removeCallback
más tarde. No existe ningúnremoveCallback
método. En su lugar, guardaAnyCancellable
y llama a sucancel
método para eliminar la devolución de llamada. Aún mejor, si almacena laAnyCancellable
propiedad en una instancia, entonces se cancelará automáticamente cuando se destruya la instancia.fuente