O livro diz que "funções e fechamentos são tipos de referência". Então, como você descobre se as referências são iguais? == e === não funcionam.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
MyClass.self
)å
para referênciaa
é realmente interessante. Existe uma convenção que você está explorando aqui? (Não sei se gosto ou não; mas parece que pode ser muito poderoso, especialmente em programação funcional pura.)Respostas:
Chris Lattner escreveu nos fóruns de desenvolvedores:
https://devforums.apple.com/message/1035180#1035180
Isso significa que você não deve nem tentar comparar fechamentos de igualdade, porque otimizações podem afetar o resultado.
fonte
Eu procurei muito. Parece não haver nenhuma maneira de comparação de ponteiro de função. A melhor solução que encontrei é encapsular a função ou o encerramento em um objeto hashble. Gostar:
var handler:Handler = Handler(callback: { (message:String) in //handler body }))
fonte
A maneira mais simples é designar o tipo de bloco como
@objc_block
, e agora você pode lançá-lo em um AnyObject comparável com===
. Exemplo: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
fonte
Também estou procurando a resposta. E eu finalmente encontrei.
O que você precisa é do ponteiro de função real e seu contexto oculto no objeto de função.
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 }
E aqui está a demonstração:
// 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)")
Veja os URLs abaixo para descobrir por que e como funciona:
Como você pode ver, ele é capaz de verificar apenas a identidade (o segundo teste produz
false
). Mas isso deve ser bom o suficiente.fonte
Esta é uma ótima pergunta e, embora Chris Lattner intencionalmente não queira oferecer suporte a esse recurso, eu, como muitos desenvolvedores, também não posso deixar de lado meus sentimentos vindos de outras linguagens onde esta é uma tarefa trivial. Existem muitos
unsafeBitCast
exemplos, a maioria deles não mostra a imagem completa, aqui está um mais detalhado :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
A parte interessante é como o Swift transmite livremente SwfBlock para ObjBlock, mas na realidade dois blocos SwfBlock fundidos sempre terão valores diferentes, enquanto ObjBlocks não. Quando lançamos ObjBlock para SwfBlock, a mesma coisa acontece com eles, eles se tornam dois valores diferentes. Portanto, para preservar a referência, esse tipo de fundição deve ser evitado.
Ainda estou compreendendo todo esse assunto, mas uma coisa que deixei de desejar é a capacidade de usar
@convention(block)
métodos de classe / estrutura, então preenchi uma solicitação de recurso que precisa ser votada ou explicada por que é uma má ideia. Também tenho a sensação de que essa abordagem pode ser ruim, se for o caso, alguém pode explicar por quê?fonte
Struct S { func f(_: Int) -> Bool }
, você na verdade tem uma função de tipoS.f
que tem tipo(S) -> (Int) -> Bool
. Esta função pode ser compartilhada. Ele é parametrizado exclusivamente por seus parâmetros explícitos. Quando você o usa como um método de instância (ou vinculando implicitamente oself
parâmetro chamando o método em um objeto, por exemploS().f
, ou vinculando-o explicitamente, por exemploS.f(S())
), você cria um novo objeto de encerramento. Este objeto armazena um ponteiro paraS.f
(que pode ser compartilhado), but also to your instance (
self, the
S () `).S
. Se a igualdade do ponteiro de fechamento fosse possível, você ficaria surpreso ao descobrir ques1.f
não é o mesmo ponteiro ques2.f
(porque um é um objeto de fechamento que faz referência as1
ef
, e o outro é um objeto de fechamento que faz referência as2
ef
).Aqui está uma solução possível (conceitualmente o mesmo que a resposta 'tuncay'). O objetivo é definir uma classe que envolva alguma funcionalidade (por exemplo, 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
fonte
Bem, já se passaram 2 dias e ninguém sugeriu uma solução, então vou mudar meu comentário para uma resposta:
Pelo que eu posso dizer, você não pode verificar a igualdade ou identidade de funções (como seu exemplo) e metaclasses (por exemplo,
MyClass.self
):Mas - e isso é apenas uma ideia - não posso deixar de notar que a
where
cláusula nos genéricos parece ser capaz de verificar a igualdade de tipos. Então, talvez você possa aproveitar isso, pelo menos para verificar a identidade?fonte
Não é uma solução geral, mas se alguém está tentando implementar um padrão de ouvinte, acabei retornando um "id" da função durante o registro para que possa usá-lo para cancelar o registro mais tarde (que é uma espécie de solução alternativa para a questão original para o caso de "ouvintes", já que geralmente cancelar o registro se resume a verificar a igualdade das funções, o que pelo menos não é "trivial", conforme outras respostas).
Então, algo assim:
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); } }
Agora você só precisa armazenar o
key
retornado pela função "registrar" e passá-lo ao cancelar o registro.fonte
Minha solução foi encapsular funções em classes que estendem NSObject
class Function<Type>: NSObject { let value: (Type) -> Void init(_ function: @escaping (Type) -> Void) { value = function } }
fonte
Sei que estou respondendo a essa pergunta com seis anos de atraso, mas acho que vale a pena examinar a motivação por trás da pergunta. O questionador comentou:
Então, acho que o questionador deseja manter uma lista de retorno de chamada, 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 }) } }
Mas não podemos escrever
removeCallback
assim, porque==
não funciona para funções. (Nem===
.)Esta é uma maneira diferente de gerenciar sua lista de retorno de chamada. Retorne um objeto de registro de
addCallback
e use o objeto de registro para remover o retorno de chamada. Aqui em 2020, podemos usar o CombineAnyCancellable
como registro.A API revisada tem a seguinte aparência:
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) } } }
Agora, quando você adiciona um retorno de chamada, você não precisa ficar com ele para passar para
removeCallback
mais tarde. Não existeremoveCallback
método. Em vez disso, você salva oAnyCancellable
e chama seucancel
método para remover o retorno de chamada. Melhor ainda, se você armazenar oAnyCancellable
em uma propriedade de instância, ele se cancelará automaticamente quando a instância for destruída.fonte