[closures] 함수와 클로저가 같은지 어떻게 테스트합니까?

이 책은 “함수와 클로저는 참조 유형”이라고 말합니다. 그렇다면 참조가 동일한 지 어떻게 알 수 있습니까? == 및 === 작동하지 않습니다.

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



답변

Chris Lattner는 개발자 포럼에 다음과 같이 썼습니다.

이것은 우리가 의도적으로 지원하고 싶지 않은 기능입니다. 최적화에 따라 포인터가 동일한 함수 (여러 종류의 클로저를 포함하는 신속한 유형 시스템 의미에서)가 실패하거나 변경되도록하는 다양한 요소가 있습니다. “===”가 함수에 정의 된 경우 컴파일러는 동일한 메서드 본문을 병합하고, 썽크를 공유하고, 클로저에서 특정 캡처 최적화를 수행 할 수 없습니다. 또한 이러한 종류의 동등성은 일부 제네릭 컨텍스트에서 매우 놀랍습니다. 여기서 함수의 실제 서명을 함수 유형이 예상하는 것으로 조정하는 재 추출 썽크를 얻을 수 있습니다.

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

이는 최적화가 결과에 영향을 미칠 수 있기 때문에 클로저를 동등하게 비교하려고 시도하지 않아야 함을 의미합니다.


답변

나는 많이 검색했다. 함수 포인터 비교 방법이없는 것 같습니다. 내가 얻은 최고의 솔루션은 해시 가능한 객체에 함수 또는 클로저를 캡슐화하는 것입니다. 처럼:

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


답변

가장 간단한 방법은 블록 유형을로 지정하는 것입니다. @objc_block이제 .NET과 비슷한 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

흥미로운 부분은 SwfBlock을 ObjBlock으로 신속하게 캐스팅하는 방법이지만 실제로 두 개의 캐스팅 된 SwfBlock 블록은 항상 다른 값이지만 ObjBlocks는 그렇지 않습니다. ObjBlock을 SwfBlock으로 캐스트하면 똑같은 일이 발생하고 두 개의 다른 값이됩니다. 따라서 참조를 보존하려면 이러한 종류의 캐스팅을 피해야합니다.

나는 여전히이 전체 주제를 이해하고 있지만, 내가 바라는 한 가지는 @convention(block)클래스 / 구조체 메서드에서 사용할 수 있는 기능 이므로 up-voting이 필요한 기능 요청 을 제출 하거나 왜 나쁜 생각인지 설명했습니다. 나는 또한이 접근 방식이 모두 나쁠 수 있음을 느낍니다. 그렇다면 누구든지 이유를 설명 할 수 있습니까?


답변

다음은 하나의 가능한 솔루션입니다 (개념적으로 ‘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


답변

이틀이 지났고 아무도 해결책을 제시하지 않았으므로 내 의견을 답변으로 변경하겠습니다.

내가 말할 수있는 한, 함수 (예 : 예)와 메타 클래스 (예 :)의 동등성 또는 동일성을 확인할 수 없습니다 MyClass.self.

하지만 – 그리고 이것은 단지 아이디어입니다 – where제네릭절이 유형의 동등성을 확인할 수있는 것으로 보인다는 것을 알 수 있습니다. 그래서 적어도 신원을 확인하기 위해 그것을 활용할 수 있습니까?