How to test if a UIControlEvents has been fired - ios

I have a library implementing a custom UIControl with a method which would fire a .valueChanged event when called. I would like to test the method for that behavior.
My custom control:
class MyControl: UIControl {
func fire() {
sendActions(for: .valueChanged)
}
}
And the test:
import XCTest
class ControlEventObserver: NSObject {
var expectation: XCTestExpectation!
init(expectation anExpectation: XCTestExpectation) {
expectation = anExpectation
}
func observe() {
expectation.fulfill()
}
}
class Tests: XCTestCase {
func test() {
let myExpectation = expectation(description: "event fired")
let observer = ControlEventObserver(expectation: myExpectation)
let control = MyControl()
control.addTarget(observer, action: #selector(ControlEventObserver.observe), for: .valueChanged)
control.fire()
waitForExpectations(timeout: 1) { error in
XCTAssertNil(error)
}
}
}
The problem is the observe method never gets called so the expectation is not fulfilled.
The question is: how can we test for UIControlEvents like in this case? Perhaps we need to force the runloop somehow?
EDIT 1:
Please note that since I am testing a library, my test target does not have any Host Application. The test above passes when the test target has a host application.

Apple's documentation for UIControl states that:
When a control-specific event occurs, the control calls any associated
action methods right away. Action methods are dispatched through the
current UIApplication object, which finds an appropriate object to
handle the message, following the responder chain if needed.
When sendActions(for:) is called on a UIControl, the control will call the UIApplication's sendAction(_:to:from:for:) to deliver the event to the registered target.
Since I am testing a library without any Host Application, there is no UIApplication object. Hence, the .valueChanged event is not dispatched and the observe method does not get called.

You are declaring the observer object inside the test method. This means that as soon as the method completes it will be released from memory and hence is not called. Create a reference to the observer at class level in the Tests class as follows and it will work.
class Tests: XCTestCase {
var observer: ControlEventObserver!
func test() {
let myExpectation = expectation(description: "event fired")
self.observer = ControlEventObserver(expectation: myExpectation)
let control = MyControl()
control.addTarget(observer, action:#selector(ControlEventObserver.observe), for: .valueChanged)
control.fire()
waitForExpectations(timeout: 1) { error in
XCTAssertNil(error)
}
}
}
You will also need the myExpectation & control to be declared in the same way else that won't be called either.

Related

Calling delegate methods either synchronously or asynchronously on a user provided queue

I am developing an API with its own delegate. I provide the caller a property to chose their own callback queue for the delegate methods.
The structure of my API class looks like:
class MyAPI {
weak var delegate: APIDelegate!
let delegateDispatchQueue: DispatchQueue
init(delegate: APIDelegate, delegateDispatchQueue: DispatchQueue) {
self.delegate = delegate
self.delegateDispatchQueue = delegateDispatchQueue
}
// public method definitions ...
}
While mostly I can call the delegate methods asynchronously, in some cases I need to call them synchronously. And that's where I seem to run into problems. If the user of my API calls my methods on the main thread, and they give the delegateDispatchQueue as the main queue, I get a crash when I try to call delegate methods synchronously.
Here is the helper class I'm using to dispatch my delegate calls to hopefully add a bit more flesh to this issue:
// Calls SyncServerDelegate methods on the `delegateDispatchQueue` either synchronously or asynchronously.
class Delegator {
private weak var delegate: SyncServerDelegate!
private let delegateDispatchQueue: DispatchQueue
init(delegate: SyncServerDelegate, delegateDispatchQueue: DispatchQueue) {
self.delegate = delegate
self.delegateDispatchQueue = delegateDispatchQueue
}
// All delegate methods must be called using this, to have them called on the client requested DispatchQueue. If sync is true, delegate method is effectively called synchronously on the `delegateDispatchQueue`. If sync is false, delegate method is called asynchronously on the `delegateDispatchQueue`.
func call(sync: Bool = false, callback: #escaping (SyncServerDelegate)->()) {
if sync {
// This is crashing with: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
// seemingly because I am doing a sync dispatch on the main thread when I'm already on the main thread. The problem is, I can't compare threads/queues. https://stackoverflow.com/questions/17489098
delegateDispatchQueue.sync { [weak self] in
guard let self = self else { return }
callback(self.delegate)
}
}
else {
delegateDispatchQueue.async { [weak self] in
guard let self = self else { return }
callback(self.delegate)
}
}
}
}
My initial thought on a solution was to internally dispatch methods to another queue. Such as:
class MyAPI {
// ...
private let startQueue = DispatchQueue(label: "SyncServer", qos: .background)
public myAPIMethod() throws {
startQueue.async {
try myAPIMethodAux() // syntax error
}
}
}
but this is currently a non-starter because I am doing error handling in much of my code by throwing errors and the above pattern immediately generates a syntax error. I could re-write code without this form of error handling, but that's a big effort I'm not quite ready to take on.
Thoughts?
Update
I've not solved this yet, but am working around it. I've split my delegate methods into two parts. The main group of them I can call back asynchronously on delegateDispatchQueue. The other group, where I need to call them synchronously, I make no promises about what queue I call them on-- and just use the same queue that my API is currently running on.

How to properly fire/call a "selector" in Swift?

Question Summary:
If you have a Swift class that takes a selector as an argument in its initializer, how do you manually "fire/call" that selector?
Full Question:
Consider the following attempt at making a custom timer in Swift:
let TIME_INTERVAL = 0.1
class ValueAnimator : NSObject {
private var timer = Timer()
private let maxRep: Int
private var currentRepIndex: Int = 0
private var selector: Selector
init(durationInSeconds: Int, selector: Selector) {
print("VALUEANIMATOR INIT")
self.maxRep = Int(Double(durationInSeconds) / TIME_INTERVAL)
self.selector = selector
}
func start() {
timer = Timer.scheduledTimer(timeInterval: TIME_INTERVAL, target: self, selector: (#selector(timerCallback)), userInfo: nil, repeats: true)
}
#objc func timerCallback() {
currentRepIndex += 1
perform(selector) // <-------- this line causes crash, "unrecognized selector sent to instance 0x600001740030"
print ("VA timer called!, rep: \(currentRepIndex)")
if currentRepIndex == maxRep {
timer.invalidate()
print("VA timer invalidated")
}
}
}
The usage of this "ValueAnimator" would be similar to a normal Timer/NSTimer, in that you pass a "selector" as an argument and that selector is called each time the ValueAnimator fires:
[In Parent Class]:
// { ...
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp))
valueAnimatorTest.start()
}
#objc func temp() {
print("temp VA callback works!") // this doesn't happen :(
}
I'm trying to implement the same thing and as I understand, the line:
perform(selector)
should fire the selector in the parent class, but instead I get the error: "unrecognized selector sent to instance 0x600001740030"
I'm in a bit over my head here. I have tried googling the error, but everyone seems to be talking about how to use a selector from the parent-side (how to use Timer.scheduledTimer(), etc.) but I already know how to do that successfully.
I've also tried various tweaks to the code (changing public/private, scope of variables, and different forms of the performSelector() function)... but can't figure out the proper way to make the selector fire... or the unrelated mistake I've made if there is one.
Thanks for any help.
By calling perform(selector) it's like you're calling self.perform(selector) (self is implied), and by doing so the current instance of the ValueAnimator class is the object that actually performs the selector. When that happens, it tries to call a method called temp() of the ValueAnimator class, but as it doesn't exist the app is crashing.
You can verify that if you add a temp() method in the ValueAnimator:
#objc func temp() {
print("Wrong method!!!")
}
If you run now you'll have no crash and the "Wrong Selector!!!" message will appear on the console.
The solution to your problem is to pass the object that should run the selector method along with the selector to the initialisation of the ValueAnimator object.
In the ValueAnimator class declare the following property:
private var target: AnyObject
Update the init method so it can get the target as an argument:
init(durationInSeconds: Int, selector: Selector, target: AnyObject) {
...
self.target = target
}
Also update the timerCallback():
#objc func timerCallback() {
...
_ = target.perform(selector)
...
}
Finally, when you initialise a ValueAnimator instance pass the object that the selector belongs to as well:
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp), target: self)
Run again and the proper temp() method will be executed this time.
I hope it helps.
You are calling perform on the wrong object: its an instance method of NSObject, so you are trying to call perform on ValueAnimator and ValueAnimator does not respond to "temp". You must pass in both the object and the selector you want to perform, then you call perform on that object with the selector. Notice that this is exactly what Timer does: you have to pass in self as the object and the timer call the selector you specify on self.

Are delegates necessary here?

This might be very basic. But, I am not very sure if the delegates are necessary in the following scenario?
Are delegates used in synchronous ways? If yes, is it good to call a delegate method in a function called by a caller who is a delegate[Like the example below]?
class FooViewController: UIViewController {
func login() {
let loginHelper = LoginHelper()
loginHelper.fooDelegate = self
loginHelper.shouldEnableLogin()
}
func enableLogin() {
// Do some UI updates
}
func reset() {
// Clear some values in the views
}
}
class LoginHelper {
weak var delegate: fooDelegate?
func shouldEnableLogin() {
//clear some text views
delegate.reset()
//do some validation, synchronous
delegate.enableLogin()
}
}
Delegates are a design pattern that allows one object to send messages to another object when a specific event happens. Imagine an object A calls an object B to perform an action. Once the action is complete, object A should know that B has completed the task and take necessary action, this can be achieved with the help of delegates!
I think the crux here is your question "Are delegates used in synchronous ways?".
The fundamental delegate mechanism is synchronous: I.e. the called delegate method will be on the same thread as the caller. So if the caller is your object then you control what thread this occurs on.
However the caller could create a new thread and then call the delegate method from that. So if the caller is not yours, check the documentation for it carefully before relying on the call being on the same thread.

Swift private access control causing issues

I'm working on an iOS application and I'm using swift in that. For more readability and organising functions I've used extensions in my swift file.
// MARK: Class Declaration
class PaymentView
{
// Some stuffs
}
// MARK: Lifecycle methods
extension PaymentView
{
// Overriden for adding gesture recogniser
override func awakeFromNib()
{
super.awakeFromNib()
// Causes a crash when tapped on view
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("paymentViewSelected:"))
self.addGestureRecognizer(tapGesture)
// Works correctly !!!
paymentViewSelected(tapGesture);
}
}
// MARK: Private Methods
extension PaymentView
{
private func paymentViewSelected(sender : UITapGestureRecognizer)
{
print("Method called")
}
}
My issue is when I tap on my view the application crashes with unrecognised selector error. If I remove that private access control specifier from the method it works perfectly.
My question is, I can call the paymentViewSelected: directly from the awakeFromNib regardless the private. But why it is crashing when used as a selector ?
According to Swift AccessControl Reference
Private access restricts the use of an entity to its own defining
source file. Use private access to hide the implementation details of
a specific piece of functionality.
But my class, extension, gesture all are in same file. I think I'm missing some basic key concept here. Please help me to understand the issue.
About Your point : I can call the paymentViewSelected: directly from the awakeFromNib regardless the private. But why it is crashing when used as a selector ?
Its because methods that are marked with private can accessible inside the class, but the object of class cannot call the method marked with private.
In your tapGesture, it is called using object of class automatically, once you tap on the view.
In this case your method is not available because it is marked with private, this is the reason of crash error unrecognised selector.
If you are calling a method from a selector and it is private they cannot be called because the method is called from outside. When you call paymentViewSelected() in the awakeFromNib it is called inside the class. However when it is called via Selector, it is called from outside. like object.paymentViewSelected(). You cannot call private method from outside.

Call class method with argument from another class method

In my code file MyItemVC.swift I have defined the following class and method:
class MyItemVC: UIViewController, UITextViewDelegate {
var timer = NSTimer()
func cycleTimer(toggleOn: Bool) {
if toggleOn == true {
// Timer calls the replaceItem method every 3 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("replaceItem"), userInfo: nil, repeats: true)
} else {
timer.invalidate() // stop the timer
}
}
}
Elsewhere in this class, I call cycleTimer(true) to start the timer and cycleTimer(false) to stop it.
Now, I also want to use the usual methods in my AppDelegate.swift code file to start and stop the timer when the app moves from active to inactive state. But I'm having trouble calling the cycleTimer method from that class.
I found an answer on Stack Overflow that suggested I could call it like this:
func applicationWillResignActive(application: UIApplication) {
MyItemVC.cycleTimer()
}
But I also need to pass in an argument. So I tried calling it like this:
func applicationWillResignActive(application: UIApplication) {
MyItemVC.cycleTimer(true)
}
But I got this error:
Cannot invoke 'cycleTimer' with an argument list of type '(Bool)'
How can I call this method from the AppDelegate methods while passing in an argument?
Thanks for the help. I realize this must be a very basic question but I'm new to programming and trying to teach myself using Swift. An answer using Swift rather than Obj-C would be greatly appreciated.
You need to use class function to be able to use it this way.
class func cycleTimer(toggleOn: Bool) {
However, I'm not sure about thread safety.
The function you have specified is not a class function. Add class keyword before func keyword.
The changed code:
class func cycleTimer
Note: In the previous versions of Swift you must use the following code (and also in C or other languages):
static func cycleTimer

Resources