web-dev-qa-db-fra.com

Comment testez-vous l'égalité des fonctions et des fermetures?

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

Voici comment les Catterwauls gèrent cela:

Fermetures multiples et fermetures équitables

tests

82
Jessy

Chris Lattner a écrit sur les forums de développeurs:

Il s'agit d'une fonctionnalité que nous ne souhaitons pas intentionnellement prendre en charge. Il y a une variété de choses qui entraîneront l'échec ou la modification de l'égalité des fonctions du pointeur (dans le sens système de type Swift, qui inclut plusieurs types de fermetures) en fonction de l'optimisation. Si "=== "ont été définis sur des fonctions, le compilateur ne serait pas autorisé à fusionner des corps de méthode identiques, à partager des thunks et à effectuer certaines optimisations de capture dans des fermetures. qui ajustent la signature réelle d'une fonction à celle attendue par le type de fonction.

https://devforums.Apple.com/message/1035180#103518

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.

64
drewag

La manière la plus simple est de désigner le type de bloc comme @objc_block, et maintenant vous pouvez 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
8
matt

J'ai aussi cherché la réponse. Et je l'ai enfin trouvé.

Ce dont vous avez besoin, c'est du pointeur de fonction réel et de son contexte caché dans l'objet 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 l'identité uniquement (le deuxième test donne false). Mais cela devrait être suffisant.

6
dankogai

J'ai beaucoup cherché. Il semble qu'il n'y ait 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 lavable. Comme:

var handler:Handler = Handler(callback: { (message:String) in
            //handler body
}))
6
tuncay

Voici une solution possible (conceptuellement la même que la réponse "tuncay"). Le but est de définir une classe qui encapsule certaines fonctionnalités (par exemple la commande):

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
4
baso

C'est une excellente question et bien que Chris Lattner ne veuille pas intentionnellement prendre en charge cette fonctionnalité, comme de nombreux développeurs, je ne peux pas non plus abandonner mes sentiments venant d'autres langues où c'est une tâche triviale. Il existe de nombreux exemples de unsafeBitCast, 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 de savoir comment Swift convertit librement SwfBlock en ObjBlock, mais en réalité, deux blocs SwfBlock castés auront toujours des valeurs différentes, contrairement à ObjBlocks. Lorsque nous lançons ObjBlock en SwfBlock, la même chose qui leur arrive, ils deviennent deux valeurs différentes. Donc, pour conserver la référence, ce genre de casting doit être évité.

Je comprends toujours tout ce sujet, mais une chose que j'ai laissée souhaiter est la possibilité d'utiliser @convention(block) sur les méthodes class/struct, j'ai donc déposé un demande de fonctionnalité qui a besoin de up- voter ou expliquer pourquoi c'est une mauvaise idée. J'ai également l'impression que cette approche pourrait être mauvaise ensemble, si oui, quelqu'un peut-il expliquer pourquoi?

4
Ian Bytchek

Eh bien, cela fait 2 jours et personne n'est intervenu avec une 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 clause where en générique semble pouvoir vérifier l'égalité des types. Alors peut-être pouvez-vous en tirer parti, au moins pour vérifier l'identité?

2
Jiaaro

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 à la question d'origine pour le cas des "auditeurs", comme d'habitude, la non-inscription revient à vérifier l'égalité des fonctions, ce qui n'est pas du moins "trivial" selon les 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 vous suffit maintenant de stocker la key renvoyée par la fonction "register" et de la passer lors de la désinscription.

0
vir us