Как вы тестируете функции и замыкания на равенство?
в книге говорится, что "функции и замыкания-это ссылочные типы". Итак, как вы узнаете, если ссылки равны? = = и === не работает.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
вот как Catterwauls имеют дело с этим:
7 ответов:
Крис Латтнер написал на форумах разработчиков:
Это функция, которую мы намеренно не хотим поддерживать. Есть множество вещей, которые вызовут равенство указателей функций (в стремительный тип чувство системы, которое включает несколько видов закрытия) сбой или изменение в зависимости от оптимизации. Если "= = = " были определены на функции, компилятор не будет разрешено объединить идентичный метод тела, разделяют удары и выполняют определенный захват оптимизации закрытия. Кроме того, равенство такого рода было бы чрезвычайно удивительно в некоторых контекстах дженериков, где вы можете получить реабстрацию thunks, которые настраивают фактическую сигнатуру функции на тот, который тип функции ожидает.
https://devforums.apple.com/message/1035180#1035180
Это означает, что вы даже не должны пытаться сравнивать замыкания для равенства, потому что оптимизация может повлиять на результат.
Я тоже искал ответ. И я наконец нашел его.
вам нужен фактический указатель на функцию и его контекст, скрытый в объекте функции.
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)")
смотрите URL-адреса ниже, чтобы узнать, почему и как это работает:
- https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift
- https://github.com/rodionovd/SWRoute/blob/master/SWRoute/rd_get_func_impl.c
как вы видите, он способен проверять только личность (2-й тест дает
false
). Но этого должно быть достаточно.
самый простой способ-обозначить тип блока как
@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
Я искал много. Там, кажется, нет никакого способа сравнения указателя функции. Лучшее решение, которое я получил, - это инкапсулировать функцию или закрытие в хешируемый объект. Например:
var handler:Handler = Handler(callback: { (message:String) in //handler body }))
Это отличный вопрос, и хотя Крис Латтнер намеренно не хочет поддерживать эту функцию, я, как и многие разработчики, также не могу отпустить свои чувства, исходящие из других языков, где это тривиальная задача. Есть много
unsafeBitCast
примеры, большинство из них не показывают полную картину, вот более развернутый: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
интересная часть заключается в том, как swift свободно бросает SwfBlock в ObjBlock, но на самом деле два литых блока SwfBlock будут всегда будут разные значения, в то время как ObjBlocks не будет. когда мы приводим ObjBlock к SwfBlock, с ними происходит то же самое, они становятся двумя разными значениями. Таким образом, чтобы сохранить ссылку, такого рода литья следует избегать.
Я все еще понимаю весь этот предмет, но одна вещь, которую я оставил желать, - это способность использовать
@convention(block)
на методы класса / структуры, поэтому я подал запрос это требует голосования или объяснения, почему это плохая идея. Я также получаю смысл этого подхода может быть плохо все вместе, если да, может кто-нибудь объяснить, почему?
Ну, это было 2 дня, и никто не вмешался с решением, поэтому я изменю свой комментарий на ответ:
насколько я могу судить, Вы не можете проверить равенство или идентичность функций (например, ваш пример) и метаклассов (например,
MyClass.self
):но – и это только идея – я не могу не заметить, что
where
предложение в обобщениях кажется, можно проверить равенство типов. Так что, возможно, вы можете использовать это, по крайней мере для проверки личность?
вот одно из возможных решений (концептуально то же самое, что и ответ "tuncay"). Дело в том, чтобы определить класс, который обертывает некоторые функции (например, команды):
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