programing

함수와 닫힘의 균등성을 검사하려면 어떻게 해야 합니까?

itmemos 2023. 9. 9. 09:05
반응형

함수와 닫힘의 균등성을 검사하려면 어떻게 해야 합니까?

이 책에는 "기능과 폐쇄는 참조형"이라고 나와 있습니다.그렇다면 참고문헌이 동일한지 어떻게 알 수 있습니까?== ===는 작동하지 않습니다.

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

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

이것은 우리가 의도적으로 지원하고 싶지 않은 기능입니다.최적화에 따라 함수의 포인터 동일성(여러 종류의 폐쇄를 포함하는 swift type system sense에서)이 실패하거나 변경되는 다양한 것이 있습니다.함수에 "==="가 정의된 경우 컴파일러는 동일한 메서드 본체를 병합하고 Thank를 공유하며 폐쇄 상태에서 특정 캡처 최적화를 수행할 수 없습니다.또한 일부 일반적인 상황에서는 이러한 유형의 동등성이 매우 놀랍습니다. 함수의 실제 서명을 함수 유형이 예상하는 것으로 조정하는 추상화 Thank를 다시 얻을 수 있습니다.

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

즉, 최적화가 결과에 영향을 미칠 수 있으므로 동일성을 위해 폐점을 비교하지 말아야 합니다.

많이 찾아봤어요.함수 포인터를 비교할 방법이 없는 것 같습니다.내가 얻은 가장 좋은 해결책은 해시 가능한 객체에 함수나 클로저를 캡슐화하는 것입니다.좋아요:

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

가장 간단한 방법은 블록 유형을 다음과 같이 지정하는 것입니다.@objc_block 과 할 과 할 === 예:

    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

2021년 업데이트, 변경됨@objc_block@convention(block)스위프트를 2.x그리고 나중에 (인식하지 못하는)@objc_block).

저도 답을 찾고 있었습니다.드디어 찾았어요.

필요한 것은 실제 함수 포인터와 함수 객체에 숨겨진 컨텍스트입니다.

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(그러나 그것으로 충분할 것입니다.

다음은 한 가지 가능한 해결책입니다(개념적으로 '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"register" 기능에 의해 반환되며 등록 취소 시 전달됩니다.

이것은 훌륭한 질문이고 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이 항상 다른 값이 되는 반면 ObjBlock은 그렇지 않습니다.우리가 ObjBlock을 SwfBlock에 캐스팅할 때, 그들에게도 같은 일이 일어나 두 개의 다른 값이 됩니다.따라서 참고문헌을 보존하기 위해서는 이런 종류의 주조는 피해야 합니다.

을 다 , 한 것은 ① ③ ④ ⑤ ⑥ ⑦ 입니다 는 할 은 하고 를 입니다 할 는 은 @convention(block)수업/구조 방법에 관해서는 사전투표나 왜 나쁜 생각인지 설명이 필요한 기능 요청서를 제출했습니다.또한 이 접근 방식이 모두 좋지 않을 수도 있다는 것을 알게 되었습니다. 그렇다면 누가 그 이유를 설명해 줄 수 있습니까?

이틀이 지났는데도 아무도 해결책을 제시하지 않았으니 제 의견을 답변으로 바꾸겠습니다.

내가 아는 한, 당신은 (당신의 예처럼) 함수와 메타 클래스의 동일성이나 동일성을 확인할 수 없습니다.MyClass.self):

하지만 – 그리고 이것은 단지 아이디어일 뿐입니다 – 저는 제네릭의 조항이 유형의 동일성을 확인할 수 있는 것처럼 보인다는 것에 주목하지 않을 수 없습니다.그래서 최소한 신원 확인을 위해서라도 그걸 활용할 수 있을까요?

저의 해결책은 NSObject를 확장하는 클래스로 함수를 랩핑하는 것이었습니다.

class Function<Type>: NSObject {
    let value: (Type) -> Void

    init(_ function: @escaping (Type) -> Void) {
        value = function
    }
}

제가 이 질문에 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년에는 콤바인의 것을 사용할 수 있습니다.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인스턴스(instance) 속성의 경우 인스턴스가 파괴되면 자동으로 자동 취소됩니다.

당신은 a를 사용할 수 있습니다.callAsFunctionmethod so 예를 들어

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

이 경우에는 항상 참일 수 있습니다. 초기화자가 다른 내부 상태를 제공하거나 다른 방법으로 상태를 변경할 수 있습니다.인수를 사용하도록 함수를 변경할 수 있습니다.

주소만 테스트하고 있기 때문에 ===가 실제 기능에서 작동하지 않는 이유는 확실하지 않지만 ==는 == 메서드를 호출합니다.Equatable프로토콜, 그리고 기능은 이 프로토콜을 구현하지 않습니다.

언급URL : https://stackoverflow.com/questions/24111984/how-do-you-test-functions-and-closures-for-equality

반응형