この本には、「関数とクロージャは参照型である」と書かれています。では、参照が等しいかどうかをどのように確認しますか?==および===は機能しません。
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
は非常に興味深いことです。ここで検討しているコンベンションはありますか?(実際に気に入ったかどうかはわかりませんが、特に純粋な関数型プログラミングでは非常に強力な可能性があるようです。)
回答:
ChrisLattnerは開発者フォーラムに次のように書いています。
これは、意図的にサポートしたくない機能です。関数のポインターの同等性(いくつかの種類のクロージャーを含む迅速な型システムの意味で)が失敗したり、最適化に応じて変更されたりする原因はさまざまです。関数に「===」が定義されている場合、コンパイラーは、同一のメソッド本体をマージし、サンクを共有し、クロージャーで特定のキャプチャー最適化を実行することを許可されません。さらに、この種の同等性は、関数の実際のシグネチャを関数タイプが期待するものに調整する再抽象化サンクを取得できる一部のジェネリックコンテキストでは非常に驚くべきものです。
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を参照してください。
ご覧のとおり、IDのみをチェックできます(2番目のテストで得られますfalse
)。しかし、それで十分なはずです。
これは素晴らしい質問です。ChrisLattnerは意図的にこの機能をサポートしたくありませんが、多くの開発者と同様に、これが簡単な作業である他の言語からの私の気持ちを手放すことはできません。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に自由にキャストする方法ですが、実際には、キャストされた2つのSwfBlockブロックは常に異なる値になりますが、ObjBlocksはそうではありません。ObjBlockをSwfBlockにキャストすると、同じことが起こり、2つの異なる値になります。したがって、参照を保持するために、この種のキャストは避ける必要があります。
私はまだこの主題全体を理解していますが、私が望んでいたことの1つは@convention(block)
、クラス/構造体メソッドで使用できることです。そこで、賛成票を投じたり、なぜそれが悪い考えであるかを説明する必要がある機能リクエストを提出しました。私はまた、このアプローチがすべて一緒に悪いかもしれないと感じます、もしそうなら、誰かが理由を説明できますか?
Struct S { func f(_: Int) -> Bool }
、実際にはタイプS.f
がタイプの関数があります(S) -> (Int) -> Bool
。この機能は共有できます。明示的なパラメーターによってのみパラメーター化されます。これをインスタンスメソッドとして使用する場合(self
たとえばS().f
、オブジェクトでメソッドを呼び出すことによってパラメーターを暗黙的にバインドするか、たとえば、明示的にバインドすることによってS.f(S())
)、新しいクロージャーオブジェクトを作成します。このオブジェクトは、S.f
(共有可能な), but also to your instance (
self , the
S() `へのポインタを格納します。
S
。閉鎖ポインタの平等が可能であった場合は、それが発見して驚くだろうs1.f
と同じポインタではありませんs2.f
(1が参照閉鎖オブジェクトであるため、s1
そしてf
もう一方がどの参照閉鎖オブジェクトで、s2
そしてf
)。
これが1つの可能な解決策です(概念的には「tuncay」の答えと同じです)。重要なのは、いくつかの機能(コマンドなど)をラップするクラスを定義することです。
迅速:
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
さて、2日が経ちましたが、誰も解決策を教えてくれなかったので、コメントを回答に変更します。
私の知る限り、関数(例のように)とメタクラス(例)の同等性や同一性をチェックすることはできませんMyClass.self
。
しかし、これは単なるアイデアです。where
ジェネリックスの句が型の同等性をチェックできるように見えることに気づかずにはいられません。それで、少なくとも身元を確認するために、それを活用できるかもしれませんか?
一般的な解決策ではありませんが、リスナーパターンを実装しようとすると、登録中に関数の「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
、「register」関数によって返されたものを保存し、登録解除時に渡す必要があります。
私はこの質問に6年遅れて答えていることを知っていますが、質問の背後にある動機を調べる価値があると思います。質問者はコメントしました:
ただし、参照によって呼び出しリストからクロージャを削除することはできませんが、独自のラッパークラスを作成する必要があります。それはドラッグであり、必要ではないはずです。
したがって、質問者は次のようなコールバックリストを維持したいと考えています。
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年には、Combineを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
に、インスタンスプロパティに格納すると、インスタンスが破棄されたときに自動的にキャンセルされます。
MyClass.self
)