How do you test functions and closures for equality?
ClosuresSwiftEqualityClosures Problem Overview
The book says that "functions and closures are reference types". So, how do you find out if the references are equal? == and === don't work.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
Closures Solutions
Solution 1 - Closures
Chris Lattner wrote on the developer forums:
> This is a feature we intentionally do not want to support. There are > a variety of things that will cause pointer equality of functions (in > the swift type system sense, which includes several kinds of closures) > to fail or change depending on optimization. If "===" were defined on > functions, the compiler would not be allowed to merge identical method > bodies, share thunks, and perform certain capture optimizations in > closures. Further, equality of this sort would be extremely > surprising in some generics contexts, where you can get reabstraction > thunks that adjust the actual signature of a function to the one the > function type expects.
https://devforums.apple.com/message/1035180#1035180
This means that you should not even try to compare closures for equality because optimizations may affect the outcome.
Solution 2 - Closures
I searched a lot. There seems to be no way of function pointer comparison. The best solution I got is to encapsulate the function or closure in an hashable object. Like:
var handler:Handler = Handler(callback: { (message:String) in
//handler body
}))
Solution 3 - Closures
Simplest way is designate the block type as @objc_block
, and now you can cast it to an AnyObject which is comparable with ===
. Example:
typealias Ftype = @convention(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
>Update 2021; changed @objc_block
to @convention(block)
to support Swift 2.x
and later (which don't recognize @objc_block
).
Solution 4 - Closures
I've been looking for the answer, too. And I've found it at last.
What you need is the actual function pointer and its context hidden in the function object.
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
}
And here is the demo:
// 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)")
See the URLs below to find why and how it works:
- https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift
- https://github.com/rodionovd/SWRoute/blob/master/SWRoute/rd_get_func_impl.c
As you see it is capable of checking identity only (the 2nd test yields false
). But that should be good enough.
Solution 5 - Closures
Here is one possible solution (conceptually the same as 'tuncay' answer). The point is to define a class that wraps some functionality (e.g. Command):
Swift:
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
Solution 6 - Closures
This is a great question and while Chris Lattner intentionally doesn't want to support this feature I, like many developers, also can't let go of my feelings coming from other languages where this is a trivial task. There are plenty of unsafeBitCast
examples, most of them don't show the full picture, 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
The interesting part is how swift freely casts SwfBlock to ObjBlock, yet in reality two casted SwfBlock blocks will always be different values, while ObjBlocks won't. When we cast ObjBlock to SwfBlock, the same thing happens to them, they become two different values. So, in order to preserve the reference, this sort of casting should be avoided.
I'm still comprehending this whole subject, but one thing I left wishing for is ability to use @convention(block)
on class / struct methods, so I filed a feature request that needs up-voting or explaining why it's a bad idea. I also get a sense this approach might be bad all together, if so, can anyone explain why?
Solution 7 - Closures
Not a general solution, but if one is trying to implement a listener pattern, I've ended up returning an "id" of the function during the registration so I can use it to unregister later (which is kind of workaround to the original question for "listeners" case as usually unregistering comes down to checking the functions for equality, which is at least not "trivial" as per other answers).
So something like this:
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);
}
}
Now you just need to store the key
returned by the "register" function and pass it when unregistering.
Solution 8 - Closures
Well it's been 2 days and nobody has chimed in with a solution, so I'll change my comment to an answer:
As far as I can tell, you can't check equality or identity of functions (like your example) and metaclasses (e.g., MyClass.self
):
But – and this is just an idea – I can't help but notice that the where
clause in generics appears to be able to check equality of types. So maybe you can leverage that, at least for checking identity?
Solution 9 - Closures
My solution was to wrap functions to class that extends NSObject
class Function<Type>: NSObject {
let value: (Type) -> Void
init(_ function: @escaping (Type) -> Void) {
value = function
}
}
Solution 10 - Closures
I know I'm answering this question six years late, but I think it's worth looking at the motivation behind the question. The questioner commented:
> Without being able to remove closures from an invocation list by reference, however, we need to create our own wrapper class. That is a drag, and shouldn't be necessary.
So I guess the questioner wants to maintain a callback list, like this:
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 })
}
}
But we can't write removeCallback
that way, because ==
doesn't work for functions. (Neither does ===
.)
Here's a different way to manage your callback list. Return a registration object from addCallback
, and use the registration object to remove the callback. Here in 2020, we can use the Combine's AnyCancellable
as the registration.
The revised API looks like this:
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) }
}
}
Now, when you add a callback, you don't need to keep it around to pass to removeCallback
later. There is no removeCallback
method. Instead, you save the AnyCancellable
, and call its cancel
method to remove the callback. Even better, if you store the AnyCancellable
in an instance property, then it will cancel itself automatically when the instance is destroyed.
Solution 11 - Closures
You can use a callAsFunction
method so for example
struct MyType: Equatable {
func callAsFunction() {
print("Image a function")
}
static func == (lhs: MyType, rhs: MyType) -> Bool { true }
}
let a = MyType()
let b = MyType()
a()
b()
let e = a == b
In this case they are always going to be true, you can have initialiser t give them different internal states, or other methods to change there states, and the callAsFunction can be changed to take arguments
Not sure why === would not work on real functions because you are just testing address, but == invokes the == method of the Equatable
protocol, and function don't implement this protocol