Le livre dit que "les fonctions et les fermetures sont des types de référence". Alors, comment savoir si les références sont égales? == et === ne fonctionnent pas.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
MyClass.self
)å
comme référencea
est vraiment intéressante. Y a-t-il une convention que vous explorez ici? (Je ne sais pas si je l'aime vraiment ou non; mais il semble que cela pourrait être très puissant, en particulier en programmation fonctionnelle pure.)Réponses:
Chris Lattner a écrit sur les forums des développeurs:
https://devforums.apple.com/message/1035180#1035180
Cela signifie que vous ne devriez même pas essayer de comparer les fermetures pour l'égalité, car les optimisations peuvent affecter le résultat.
la source
J'ai beaucoup cherché. Il ne semble y avoir aucun moyen de comparer les pointeurs de fonction. La meilleure solution que j'ai obtenue est d'encapsuler la fonction ou la fermeture dans un objet hachable. Comme:
var handler:Handler = Handler(callback: { (message:String) in //handler body }))
la source
Le moyen le plus simple est de désigner le type de bloc comme
@objc_block
, et vous pouvez maintenant le convertir en un AnyObject comparable à===
. Exemple: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
la source
J'ai aussi cherché la réponse. Et je l'ai enfin trouvé.
Ce dont vous avez besoin est le pointeur de fonction réel et son contexte cachés dans l'objet de fonction.
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 }
Et voici la démo:
// 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)")
Consultez les URL ci-dessous pour savoir pourquoi et comment cela fonctionne:
Comme vous le voyez, il est capable de vérifier uniquement l'identité (le deuxième test donne
false
). Mais cela devrait suffire.la source
C'est une excellente question et bien que Chris Lattner ne veuille intentionnellement pas supporter cette fonctionnalité, je ne peux pas, comme beaucoup de développeurs, abandonner mes sentiments venant d'autres langues où c'est une tâche triviale. Il y a beaucoup d'
unsafeBitCast
exemples, la plupart d'entre eux ne montrent pas l'image complète, en voici un plus détaillé :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 partie intéressante est la façon dont Swift convertit librement SwfBlock en ObjBlock, mais en réalité, deux blocs SwfBlock coulés auront toujours des valeurs différentes, contrairement à ObjBlocks. Lorsque nous convertissons ObjBlock en SwfBlock, la même chose leur arrive, ils deviennent deux valeurs différentes. Ainsi, afin de préserver la référence, ce type de casting doit être évité.
Je comprends toujours tout ce sujet, mais une chose que j'ai laissée espérer est la possibilité d'utiliser
@convention(block)
des méthodes de classe / structure, j'ai donc déposé une demande de fonctionnalité qui nécessite un vote ascendant ou expliquant pourquoi c'est une mauvaise idée. J'ai aussi le sentiment que cette approche pourrait être mauvaise dans l'ensemble, si oui, quelqu'un peut-il expliquer pourquoi?la source
Struct S { func f(_: Int) -> Bool }
, vous avez en fait une fonction de typeS.f
qui a un type(S) -> (Int) -> Bool
. Cette fonction peut être partagée. Il est uniquement paramétré par ses paramètres explicites. Lorsque vous l'utilisez comme méthode d'instance (soit en liant implicitement leself
paramètre en appelant la méthode sur un objet, par exempleS().f
, soit en la liant explicitement, par exempleS.f(S())
), vous créez un nouvel objet de fermeture. Cet objet stocke un pointeur versS.f
(qui peut être partagé), but also to your instance (
self, the
S () `).S
. Si l'égalité du pointeur de fermeture était possible, alors vous seriez surpris de découvrir que ces1.f
n'est pas le même pointeur ques2.f
(car l'un est un objet de fermeture qui fait référence às1
etf
, et l'autre est un objet de fermeture qui références2
etf
).Voici une solution possible (conceptuellement identique à la réponse «tuncay»). Le but est de définir une classe qui englobe certaines fonctionnalités (par exemple, Command):
Rapide:
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
la source
Eh bien, cela fait 2 jours et personne n'a proposé de solution, je vais donc changer mon commentaire en réponse:
Pour autant que je sache, vous ne pouvez pas vérifier l'égalité ou l'identité des fonctions (comme votre exemple) et des métaclasses (par exemple
MyClass.self
):Mais - et ce n'est qu'une idée - je ne peux m'empêcher de remarquer que la
where
clause des génériques semble pouvoir vérifier l'égalité des types. Alors peut-être pouvez-vous en tirer parti, au moins pour vérifier votre identité?la source
Ce n'est pas une solution générale, mais si l'on essaie d'implémenter un modèle d'écoute, j'ai fini par renvoyer un "id" de la fonction lors de l'enregistrement afin que je puisse l'utiliser pour me désinscrire plus tard (ce qui est une sorte de solution de contournement à la question d'origine pour les "auditeurs", le désenregistrement revient généralement à vérifier l'égalité des fonctions, ce qui n'est au moins pas "trivial" comme pour d'autres réponses).
Donc quelque chose comme ça:
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); } }
Il ne vous reste plus qu'à stocker le
key
retourné par la fonction "register" et à le transmettre lors de la désinscription.la source
Ma solution était d'encapsuler des fonctions dans une classe qui étend NSObject
class Function<Type>: NSObject { let value: (Type) -> Void init(_ function: @escaping (Type) -> Void) { value = function } }
la source
Je sais que je réponds à cette question avec six ans de retard, mais je pense qu'il vaut la peine d'examiner la motivation derrière la question. L'intervenant a commenté:
Donc, je suppose que l'interlocuteur veut maintenir une liste de rappel, comme ceci:
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 }) } }
Mais nous ne pouvons pas écrire de
removeCallback
cette façon, car==
cela ne fonctionne pas pour les fonctions. (Ni l'un ni l'autre===
.)Voici une autre façon de gérer votre liste de rappel. Renvoyez un objet d'inscription à partir de
addCallback
et utilisez l'objet d'inscription pour supprimer le rappel. Ici en 2020, nous pouvons utiliser le CombineAnyCancellable
comme enregistrement.L'API révisée ressemble à ceci:
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) } } }
Désormais, lorsque vous ajoutez un rappel, vous n'avez pas besoin de le conserver pour le transmettre
removeCallback
plus tard. Il n'y a pas deremoveCallback
méthode. Au lieu de cela, vous enregistrez leAnyCancellable
et appelez sacancel
méthode pour supprimer le rappel. Mieux encore, si vous stockez laAnyCancellable
propriété dans une instance, elle s'annulera automatiquement lorsque l'instance sera détruite.la source