이 책은 "함수와 클로저는 참조 유형"이라고 말합니다. 그렇다면 참조가 동일한 지 어떻게 알 수 있습니까? == 및 === 작동하지 않습니다.
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
것은 정말 흥미 롭습니다. 여기서 탐구하고있는 컨벤션이 있습니까? (I 실제로 같은 아닌지 모르겠어요,하지만 특히 순수 함수형 프로그래밍에서 매우 강력한 수처럼 보인다.)
답변:
Chris Lattner는 개발자 포럼에 다음과 같이 썼습니다.
이것은 우리가 의도적으로 지원하고 싶지 않은 기능입니다. 최적화에 따라 포인터가 동일한 함수 (여러 종류의 클로저를 포함하는 신속한 유형 시스템 의미에서)가 실패하거나 변경되도록하는 다양한 요소가 있습니다. "==="가 함수에 정의 된 경우 컴파일러는 동일한 메서드 본문을 병합하고, 썽크를 공유하고, 클로저에서 특정 캡처 최적화를 수행 할 수 없습니다. 또한 이러한 종류의 동등성은 일부 제네릭 컨텍스트에서 매우 놀랍습니다. 여기서 함수의 실제 서명을 함수 유형이 예상하는 것으로 조정하는 재 추출 썽크를 얻을 수 있습니다.
https://devforums.apple.com/message/1035180#1035180
이는 최적화가 결과에 영향을 미칠 수 있기 때문에 클로저를 동등하게 비교하려고 시도하지 않아야 함을 의미합니다.
가장 간단한 방법은 블록 유형을로 지정하는 것입니다. @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이 필요한 기능 요청 을 제출 하거나 왜 나쁜 생각인지 설명했습니다. 나는 또한이 접근 방식이 모두 나쁠 수 있음을 느낍니다. 그렇다면 누구든지 이유를 설명 할 수 있습니까?
Struct S { func f(_: Int) -> Bool }
, 당신은 실제로 type 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
(하나가되는 참조 폐쇄 객체이기 때문에 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
자바:
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"함수에 의해 반환 된 을 저장하고 등록을 취소 할 때 전달하면됩니다.
나는이 질문에 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
)