该书说“函数和闭包是引用类型”。那么,如何确定引用是否相等?==和===不起作用。
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
该书说“函数和闭包是引用类型”。那么,如何确定引用是否相等?==和===不起作用。
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
å
进行引用a
确实很有趣。您在这里探索一个约定吗?(我不知道我是否真的喜欢它;但是看起来它可能非常强大,尤其是在纯函数式编程中。)
Answers:
克里斯·拉特纳(Chris Lattner)在开发人员论坛上写道:
这是我们有意不想支持的功能。有多种情况会导致函数的指针相等(在快速类型的系统意义上,其中包括几种闭包)根据优化而失败或更改。如果在函数上定义了“ ===”,则不允许编译器合并相同的方法主体,共享thunk并在闭包中执行某些捕获优化。此外,在某些泛型上下文中,此类相等将是非常令人惊讶的,在这种情况下,您会得到重新提取的重击,将函数的实际签名调整为该函数类型所期望的签名。
https://devforums.apple.com/message/1035180#1035180
这意味着您甚至不应该尝试比较闭包是否相等,因为优化可能会影响结果。
最简单的方法是将块类型指定为@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
我也一直在寻找答案。我终于找到了。
您需要的是实际的函数指针及其在函数对象中隐藏的上下文。
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,以了解其原因和工作方式:
如您所见,它仅能检查身份(第二次测试会产生false
)。但这应该足够了。
这是一个很大的问题,尽管Chris Lattner故意不希望支持此功能,但我和许多开发人员一样,也无法摆脱我来自其他语言的感觉,而这是一项微不足道的任务。有很多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)
在类/结构方法上使用,因此我提出了一项功能要求,需要进行投票或解释为什么这是一个坏主意。我也觉得这种方法可能不好,如果有的话,有人可以解释为什么吗?
Struct S { func f(_: Int) -> Bool }
,实际上就S.f
具有具有type的type函数(S) -> (Int) -> Bool
。可以共享此功能。它仅通过其显式参数进行参数化。通过将其用作实例方法(通过self
在对象上调用方法来隐式绑定参数,例如S().f
,或通过显式绑定它(例如S.f(S())
)),可以创建一个新的关闭对象。该对象存储一个指向S.f
(可以共享的), but also to your instance (
self , the
S()`的指针。
S
。如果可以使用闭包指针相等,那么您会惊讶地发现它s1.f
与指针不同s2.f
(因为一个是引用s1
和f
的闭包对象,另一个是引用s2
和的闭包对象f
)。
这是一种可能的解决方案(概念上与“ 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
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
这不是一种通用的解决方案,但是如果尝试实现侦听器模式,那么我最终会在注册过程中返回该函数的“ 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
“注册”函数返回的值,并在注销时传递它。
我知道我要迟到六年才回答这个问题,但是我认为值得研究这个问题背后的动机。发问者评论:
但是,由于无法通过引用从调用列表中删除闭包,我们需要创建自己的包装器类。这是一个阻力,并且不是必须的。
所以我想发问者想维护一个回调列表,像这样:
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
方法删除回调。更好的是,如果将int存储AnyCancellable
在instance属性中,则销毁实例后它将自动取消自身。
MyClass.self
)