你如何测试函数和闭包是否相等?

时间:2014-06-08 23:38:04

标签: closures swift equality

The book says that "functions and closures are reference types".那么,你怎么知道引用是否相等? ==和===不能工作。

func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments

以下是Catterwauls如何处理这个问题:

MultiClosures & Equatable Closures

tests

10 个答案:

答案 0 :(得分:61)

Chris Lattner在开发者论坛上写道:

  

这是我们有意不想支持的功能。有   各种各样会导致函数指针相等的东西(in   swift类型的系统感,包括几种闭包)   失败或根据优化而改变。如果" ==="定义于   函数,编译器不允许合并相同的方法   实体,共享thunk,并执行某些捕获优化   关闭。此外,这种平等是非常的   在某些仿制药环境中令人惊讶,你可以在那里进行重新收割   将一个函数的实际签名调整为一个的thunk   函数类型期望。

https://devforums.apple.com/message/1035180#1035180

这意味着您甚至不应该尝试比较闭包是否相等,因为优化可能会影响结果。

答案 1 :(得分:8)

最简单的方法是将块类型指定为@objc_block,现在可以将其转换为与===相当的AnyObject。例如:

    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

答案 2 :(得分:6)

我一直在寻找答案。我终于找到了它。

您需要的是实际的函数指针及其隐藏在函数对象中的上下文。

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
}

以下是演示:

// 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)")

请参阅以下网址,了解原因和工作原理:

如您所见,它只能检查身份(第二次测试产生false)。但这应该足够好了。

答案 3 :(得分:6)

我搜索了很多。似乎没有办法比较函数指针。我得到的最好的解决方案是将函数或闭包封装在一个可散列的对象中。像:

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

答案 4 :(得分:4)

这是一个很好的问题,虽然Chris Lattner故意不想支持这个功能,但我和许多开发人员一样,也不能放弃来自其他语言的感觉,这是一项微不足道的任务。有很多unsafeBitCast个例子,其中大多数都没有显示完整的图片,here's a more detailed one

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

有趣的部分是如何快速自由地将SwfBlock转换为ObjBlock,但实际上两个转换的SwfBlock块总是不同的值,而ObjBlocks则不会。当我们将ObjBlock转换为SwfBlock时,同样的事情发生在他们身上,它们变成了两个不同的值。因此,为了保留参考,应该避免这种铸造。

我仍然理解这整个主题,但我想要的一件事就是能够在类/结构方法上使用@convention(block),所以我提交了一个feature request,需要进行投票或解释为什么这是一个坏主意。我也觉得这种方法可能一共都很糟糕,如果是这样,有人可以解释为什么吗?

答案 5 :(得分:3)

这是一种可能的解决方案(概念上与'tuncay'答案相同)。关键是要定义一个包含某些功能的类(例如Command):

夫特:

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

爪哇:

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

答案 6 :(得分:2)

嗯,已经有2天了,没有人找到解决方案,所以我会将我的评论改为答案:

据我所知,您无法检查函数(例如您的示例)和元类(例如MyClass.self)的相等性或同一性:

但是 - 这只是一个想法 - 我不禁注意到where clause in generics似乎能够检查类型的相等性。那么也许你可以利用它,至少是为了检查身份?

答案 7 :(得分:0)

这不是一种通用的解决方案,但是如果尝试实现侦听器模式,那么我最终会在注册过程中返回该函数的“ id”,因此我可以在以后使用它注销(这是一种解决方法有关“侦听器”情况的原始问题(通常是注销)归结为检查功能是否相等,根据其他答案,这至少不是“琐碎的”)。

是这样的:

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);
    }
}

现在,您只需要存储“注册”函数返回的key,并在注销时传递它即可。

答案 8 :(得分:0)

我的解决方案是将函数包装到扩展NSObject的类中

class Function<Type>: NSObject {
    let value: (Type) -> Void

    init(_ function: @escaping (Type) -> Void) {
        value = function
    }
}

答案 9 :(得分:0)

我知道我将在六年后回答这个问题,但是我认为值得研究这个问题背后的动机。发问者评论:

但是,由于无法通过引用从调用列表中删除闭包,因此,我们需要创建自己的包装器类。这是一个阻力,并且不是必须的。

所以我想提问者想维护一个回调列表,像这样:

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 })
    }
}

但是我们不能那样写removeCallback,因为==对函数不起作用。 (===也没有。)

这是管理回调列表的另一种方法。从addCallback返回注册对象,并使用注册对象删除回调。在2020年,我们可以使用联合收割机的AnyCancellable作为注册。

修改后的API如下:

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) }
    }
}

现在,当您添加回调时,无需保留该回调,以后再传递给removeCallback。没有removeCallback方法。相反,您保存AnyCancellable,然后调用其cancel方法以删除回调。更好的是,如果将AnyCancellable存储在instance属性中,则销毁实例后它将自动取消自身。