Short description of the issue:
In some unit tests, I use code like the following to simulate tapping on a UIBarButtonItem through its target-action pair. If that action is wired up using Rx, I get a crash in my tests related to memory.
func testBarButtonItem_actionExecution() {
let button = UIBarButtonItem()
weak var tapExpectation = expectation(description: "tap")
button.rx.tap.subscribe(onNext: {
tapExpectation?.fulfill()
}).disposed(by: disposeBag)
_ = button.target?.perform(button.action) // crashes here
waitForExpectations(timeout: 1, handler: nil)
}
Expected outcome:
Triggering the action fires the Rx observable and everything is happy. This was actually working until recently, when I switched they way I was integrating RxSwift from Carthage-built to directly incorporated into my project's workspace via a Carthage-managed git submodule.
What actually happens:
Crash in the execution of BarButtonItemTarget.action, swift_unknownRetain on the lines with the arrow:
RxCocoa`@objc BarButtonItemTarget.action(AnyObject) -> ():
0x115984f10 <+0>: pushq %rbp
0x115984f11 <+1>: movq %rsp, %rbp
0x115984f14 <+4>: pushq %r15
0x115984f16 <+6>: pushq %r14
0x115984f18 <+8>: pushq %r12
0x115984f1a <+10>: pushq %rbx
0x115984f1b <+11>: movq %rdx, %r14
0x115984f1e <+14>: movq %rdi, %rbx
0x115984f21 <+17>: incq 0x51058(%rip) ; __profc__TZFC7RxCocoa22RxWebViewDelegateProxy20createProxyForObjectfPs9AnyObject_PS1__ + 5504
0x115984f28 <+24>: movq 0x4eba9(%rip), %r15 ; direct field offset for RxCocoa.BarButtonItemTarget.callback : Swift.ImplicitlyUnwrappedOptional<() -> ()>
0x115984f2f <+31>: movq (%rbx,%r15), %r12
0x115984f33 <+35>: movq %r14, %rdi
0x115984f36 <+38>: callq 0x11599f708 ; symbol stub for: swift_unknownRetain
-> 0x115984f3b <+43>: movq %rbx, %rdi
0x115984f3e <+46>: callq 0x11599f510 ; symbol stub for: objc_retain
0x115984f43 <+51>: testq %r12, %r12
0x115984f46 <+54>: je 0x115984f73 ; <+99> [inlined] RxCocoa.BarButtonItemTarget.action (Swift.AnyObject) -> ()
0x115984f48 <+56>: movq 0x8(%rbx,%r15), %r15
0x115984f4d <+61>: movq %r15, %rdi
0x115984f50 <+64>: callq 0x1159429b0 ; swift_rt_swift_retain
0x115984f55 <+69>: movq %r15, %rdi
0x115984f58 <+72>: callq *%r12
0x115984f5b <+75>: movq %rbx, %rdi
0x115984f5e <+78>: callq 0x11599f50a ; symbol stub for: objc_release
0x115984f63 <+83>: movq %r14, %rdi
0x115984f66 <+86>: popq %rbx
0x115984f67 <+87>: popq %r12
0x115984f69 <+89>: popq %r14
0x115984f6b <+91>: popq %r15
0x115984f6d <+93>: popq %rbp
0x115984f6e <+94>: jmp 0x11599f6fc ; symbol stub for: swift_unknownRelease
0x115984f73 <+99>: ud2
libswiftCore.dylib`swift_unknownRetain:
0x115d8c6d0 <+0>: testq %rdi, %rdi
0x115d8c6d3 <+3>: je 0x115d8c6ff ; <+47>
0x115d8c6d5 <+5>: movabsq $-0x7fffffffffffffff, %rax ; imm = 0x8000000000000001
0x115d8c6df <+15>: andq %rdi, %rax
0x115d8c6e2 <+18>: jne 0x115d8c6ff ; <+47>
0x115d8c6e4 <+20>: movq 0x8a97d(%rip), %rax ; swift_isaMask
0x115d8c6eb <+27>: andq (%rdi), %rax
-> 0x115d8c6ee <+30>: testb $0x1, 0x20(%rax)
0x115d8c6f2 <+34>: je 0x115d8c6fa ; <+42>
0x115d8c6f4 <+36>: testb $0x2, 0x28(%rax)
0x115d8c6f8 <+40>: jne 0x115d8c700 ; <+48>
0x115d8c6fa <+42>: jmp 0x115d987cc ; symbol stub for: objc_retain
0x115d8c6ff <+47>: retq
0x115d8c700 <+48>: jmp 0x115d7a350 ; swift_retain
0x115d8c705 <+53>: nopw %cs:(%rax,%rax)
Self contained code example that reproduces the issue:
See test added in this branch:
https://github.com/ReactiveX/RxSwift/compare/develop...lyricsboy:lyricsboy/UIBarButton-tap-tests
Platform/Environment
How easy is to reproduce? (chances of successful reproduce after running the self contained code)
Xcode version:
Xcode 8.3.2
Build version 8E2002
Installation method:
I have multiple versions of Xcode installed:
(so we can know if this is a potential cause of your issue)
Level of RxSwift knowledge:
(this is so we can understand your level of knowledge
and formulate the response in an appropriate manner)
Not sure if this is a bug, button.target?.perform(button.action) makes an incorrect assumption that button.action doesn't take any arguments, but in fact the action function BarButtonItemTarget.action(_:) takes one argument. Changing it to button.target?.perform(button.action, with: nil) can avoid this crash.
Could you try with button.sendActions(for: .touchUpInside)?
@crazytonyli we have a winner! That was totally it. Good catch. I have no idea why it didn't crash in a different build context (release vs. debug maybe?), but that's a question best left to the thinkers.
@devxoul unfortunately not. UIBarButtonItem is not a UIControl, for reasons that are lost to time.
I'll submit a PR for the test I added, since it does test something that wasn't being tested before.
Most helpful comment
Could you try with
button.sendActions(for: .touchUpInside)?