Skip to content

Instantly share code, notes, and snippets.

@tokijh
Created April 10, 2020 04:33
Show Gist options
  • Save tokijh/410f6056c97e7cb1d9e93ef0b3d953c0 to your computer and use it in GitHub Desktop.
Save tokijh/410f6056c97e7cb1d9e93ef0b3d953c0 to your computer and use it in GitHub Desktop.
Stubber 에서 같은 함수인데 functionAddress 가 다르게 설정되는 문제

Stubber 에서 같은 함수인데 functionAddress 가 다르게 설정되는 문제

// [register]
// arguments type : () -> ()
// return type : ()
// function address : 5003223104
Stubber.register(someClass.some(completion:)) { completion in
  completion()
}
// [register]
// arguments type : Optional<() -> ()>
// return type : ()
// function address : 5003223104
Stubber.register(someClass.some(completion:)) { completion in
  completion?()
}

분명 서로 같은 함수 (someClass.some(completion:)) 인데 functionAddress 가 다르다. 서로 다른 점은 completion() 을 호출할 때 optional 로 했냐 아니냐의 차이로 arguments type 이 바뀌어 서로 다른 함수로 인식하게된다.

Swift 5.1.3 에서는 문제없이 컴파일이 된다. 테스트시에 이로인해 Stubber 를 인식하지 못하는 경우가 있으니 되도록이면 optional 이면 optional 표시를 하는 것을 추천한다.

import Stubber
protocol SomeClassProtocol {
func some(completion: (() -> ())?)
}
class SomeClassStub: SomeClassProtocol {
func some(completion: (() -> ())?) {
Stubber.invoke(some, args: completion, default: Void())
}
}
let someClass = SomeClassStub()
// [register]
// arguments type : () -> ()
// return type : ()
// function address : 5003221280
Stubber.register(someClass.some(completion:)) { completion in
completion()
}
// [register]
// arguments type : Optional<() -> ()>
// return type : ()
// function address : 5003223104
Stubber.register(someClass.some(completion:)) { completion in
completion?()
}
// [invoke]
// arguments type : Optional<() -> ()>
// return type : ()
// function address : 5003223104
someClass.some(completion: nil)
// [executions]
// arguments type : Optional<() -> ()>
// return type : ()
// function address : 5003223104
Stubber.executions(someClass.some(completion:))
@devxoul
Copy link

devxoul commented Apr 12, 2020

흥미가 생겨서 조금 더 테스트 해보았는데요. Swift 5.1.3 에서도 같은 현상이 생깁니다.

private func functionAddress<A, R>(of f: @escaping (A) throws -> R) -> Int {
  let (_, lo) = unsafeBitCast(f, to: (Int, Int).self)
  let offset = MemoryLayout<Int>.size == 8 ? 16 : 12
  let pointer = UnsafePointer<Int>(bitPattern: lo + offset)!
  return pointer.pointee
}

func stub<A, R>(_ f: @escaping (A) -> R, with closure: @escaping (A) -> R) {
  let addr = String(functionAddress(of: f), radix: 16, uppercase: false)
  print(addr, A.self)
}

func search(completion: (() -> Void)?) {
}

stub(search) { completion in
  completion?() // 1285a19e0 Optional<() -> ()>
}
stub(search) { completion in
  completion?() // 1285a19e0 Optional<() -> ()> (위와 동일)
}

stub(search) { completion in
  completion() // 1285a1330 () -> ()
}
stub(search) { completion in
  completion() // 1285a15b0 () -> () (위와 달라짐)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment