closures - functions - swift method
¿Cómo se evalúan las funciones y los cierres para la igualdad? (7)
Aquí hay una posible solución (conceptualmente lo mismo que la respuesta ''tuncay''). El punto es definir una clase que envuelve alguna funcionalidad (por ejemplo, Comando):
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
El libro dice que "las funciones y los cierres son tipos de referencia". Entonces, ¿cómo averiguas 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
Así es como los Catterwauls están lidiando con esto:
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 la identidad de las funciones (como su ejemplo) y las metaclases (por ejemplo, MyClass.self
):
Pero, y esto es solo una idea, no puedo dejar de notar que la cláusula where
en los genéricos parece ser capaz de verificar la igualdad de tipos. Entonces, ¿puede aprovechar eso, al menos para verificar su identidad?
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 manejable. Me gusta:
var handler:Handler = Handler(callback: { (message:String) in
//handler body
}))
Chris Lattner escribió en los foros de desarrolladores:
Esta es una función que intencionalmente no queremos apoyar. Hay una variedad de cosas que harán que la igualdad de funciones del puntero (en el sentido del sistema de tipo rápido, que incluye varios tipos de cierres) falle o cambie dependiendo de la optimización. Si se definiera "===" en las funciones, el compilador no podría fusionar cuerpos de métodos idénticos, compartir procesos y realizar ciertas optimizaciones de captura en cierres. Además, la igualdad de este tipo sería extremadamente sorprendente en algunos contextos de genéricos, donde puede obtener transferencias de reabstracción que ajustan la firma real de una función a la que espera el tipo de función.
https://devforums.apple.com/message/1035180#1035180
Esto significa que ni siquiera debería tratar de comparar los cierres para la igualdad porque las optimizaciones pueden afectar el resultado.
Esta es una gran pregunta y, aunque intencionalmente Chris Lattner no quiere respaldar esta característica, yo, como muchos desarrolladores, tampoco puedo dejar de lado mis sentimientos provenientes de otros idiomas, donde esta es una tarea trivial. Hay muchos ejemplos unsafeBitCast
de unsafeBitCast
, 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 qué tan rápido lanza SwfBlock a ObjBlock, pero en realidad dos bloques SwfBlock lanzados siempre serán valores diferentes, mientras que ObjBlocks no lo hará. Cuando lanzamos ObjBlock a SwfBlock, les sucede lo mismo, se convierten en dos valores diferentes. Por lo tanto, para preservar la referencia, este tipo de conversión debe evitarse.
Todavía estoy comprendiendo todo este tema, pero una cosa que dejé de desear es la capacidad de usar @convention(block)
en los métodos de clase / estructura, así que presenté una solicitud de función que necesita una votación ascendente o explicando por qué es una mala idea. También tengo la sensación de que este enfoque podría ser malo en conjunto, de ser así, ¿alguien puede explicar por qué?
He estado buscando la respuesta, también. Y lo he encontrado por fin.
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 descubrir por qué y cómo funciona:
- https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift
- https://github.com/rodionovd/SWRoute/blob/master/SWRoute/rd_get_func_impl.c
Como puede ver, solo es capaz de verificar identidad (la segunda prueba arroja resultados false
). Pero eso debería ser lo suficientemente bueno.
La forma más simple es designar el tipo de bloque como @objc_block
, y ahora puedes convertirlo en AnyObject, que es 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