I have the following class with a static method that uses MKDirections to calculate custom routes between two coordinates. Once it finishes calculating, the method uses a delegate to pass the route (an MKPolyline object) to the View Controller which adds it to a MapView as an overlay. Every route is assigned a title that determines in which color the route is rendered on the map.
class NavigationInterface {
weak static var routeDelegate: RouteDelegate!
static func addRouteFromTo(sourceCoor: CLLocationCoordinate2D, destinationCoor: CLLocationCoordinate2D, transportTypeString: String)
{
let sourcePlacemark = MKPlacemark(coordinate: sourceCoor)
let destinationPlacemark = MKPlacemark(coordinate: destinationCoor)
//var route = MKRoute()
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: sourcePlacemark)
request.destination = MKMapItem(placemark: destinationPlacemark)
request.requestsAlternateRoutes = false
//get MKDirectionsTransportType based on String identifier
request.transportType = getTransportType(transportTypeString: transportTypeString)
let directions = MKDirections(request: request)
directions.calculate { (response, error) in
if let directionResponse = response?.routes.first {
let route = directionResponse.polyline
route.title = transportTypeString
print("Got Here")
self.routeDelegate!.didAddRoute(route: route)
}
}
}
The delegate is defined through the following protocol:
protocol RouteDelegate: class {
func didAddRoute(route: MKPolyline)
func didAddBoundary(boundary: MKPolygon)
}
The View Controller implements the delegate as follows:
class MapViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
...
override func viewDidLoad() {
super.viewDidLoad()
NavigationInterface.routeDelegate = self
}
extension MapViewController: RouteDelegate {
// delegate Method
// called in Navigation Interface
func didAddRoute(route: MKPolyline) {
mapView.add(route)
}
func didAddBoundary(boundary: MKPolygon) {
mapView.add(boundary)
}
}
Now I have attempted to write a UnitTest which checks whether the delegate method "didAddRoute" returns the correct route
For this purpose I've created a test class "NavigationTests" which implements the RouteDelegate protocol and an test method that calculates a route and then evaluates the route returned from the "NavigationTests" protocol implementation of "didAddRoute":
class NavigationTests: XCTestCase, RouteDelegate {
var routes = [MKPolyline]()
var asyncExpectation: XCTestExpectation?
func didAddRoute(route: MKPolyline) {
routes.append(route)
asyncExpectation?.fulfill()
}
...
func testaddRouteFromTo(){
NavigationInterface.routeDelegate = self
asyncExpectation = expectation(description: "routes returned from delegate method")
NavigationInterface.addRouteFromTo(sourceCoor: CoordinateA, destinationCoor: CoordinateB, transportTypeString: "roadTravel")
let result = XCTWaiter.wait(for: [self.asyncExpectation!], timeout: 2.0)
if result == XCTWaiter.Result.completed {
let route = self.routes.first
XCTAssert(route!.title == "roadTravel", "failed to retrieve correct route")
print(route!.title)
} else {
XCTFail()
}
}
}
Now this test method randomly returns routes from the MapViewController implementation of RouteDelegate instead of the NavigationTests implementation. W
How can I avoid these unwanted references to the MapViewController and why is it created at all since I do not instantiate it in the test?
Ideally I would like to prevent the MapViewController from being instantiated when running this test class since it is not required for the Unit Test.
How can I make sure that only the NavigationTests implementation of the RouteDelegate is used?
Statics vs. tests
Because addRouteFromTo(sourceCoor:destinationCoor:transportTypeString:) is a static method, you have made NavigationInterface.routeDelegate static as well. When your tests run, they are setting a global variable. This means the tests have side effects which last beyond the scope of the tests.
Here are a couple of approaches to prevent this from happening:
a) Create a setUp() and tearDown(). In setUp(), save the old value of NavigationInterface.routeDelegate before overwriting it to self. In tearDown(), restore the old value.
b) Change from statics to an object. As a general rule, statics make things harder to test.
Prefer b). It is safer, and lets the pressure of testability improve your design.
…I don't see any references to MapViewController in your test. Was it created by your application delegate?
How to test an asynchronous call?
Now to your larger question. A test that does actual networking is slow and fragile. It depends on your network conditions. It depends on the back end. It introduces a time lag.
You would be better served by restructuring your code so that you can test the following:
Are you creating the correct MKDirectionsRequest?
Are you handling the response correctly?
This will be expressed in at least 2 tests, but probably more. Once you can independently test response handling, then you can test errors as well as successful responses.
So how do you test "create the response" independently from "handle the response"? By doing this work in separate methods. The tests can then just call these methods.
There is no need to test that Apple makes a network call, or does something on the back end, or sends a response. If you follow this approach, the need for asynchronous tests drops dramatically.
I hope this helps. If you need clarification, please ask. For more thoughts on how "the way Apple shows us to write code isn't good testable design," see https://qualitycoding.org/design-sense/
Related
Heres a caption of my API call:
So, I've got the abilities of the pokemons I needed, but now, idk how to get that data out of my Service class (where I'm doing all the parsing), and send it to my InfoViewController.
My purpose is to fetch that data on some label, and then show the ability names for every poke, according to their ID. Here is a caption of my app:
I wanna add an "Ability" label below Weight, and that's where I wanna assign the data. I have a whole CollectionView with all the pokemons, and the goal is assign the correct ability for each one of them.
I'm kinda struggling for a practical (and less verbose) way to reach this.
I apreciated every comment, any advice and suggestion too. Thanks!
EDIT: Heres my code:
extension InfoController: ServiceDelegate {
func finishedWithPokemonAbilities(abilities: [String], id: Int) {
self.abilities = abilities
self.ids = id
print(abilities)
}
}
You can create a custom Protocol(could call it PokemonServiceDelegate as an example) that your InfoViewController would inherit and implement. On your service object(I'm using PokemonService in the example) create a property with a type of PokemonServiceDelegate and set that property to the view controller that you want to receive the data. After the service finishes fetching the data, update the delegate by passing the data in the function declared in the protocol.
// Protocol your view controller will inherit
protocol PokemonServiceDelegate {
// Function your view controller will implement
func finishedWithPokemonAbilities(abilities: [String])
}
class InfoViewController: UIViewController {
// Reference to the service that makes the request
var service: PokemonService
override func viewDidLoad() {
...
// Set the delegate of the service to self
service.delegate = self
...
}
}
extension InfoViewController: PokemonServiceDelegate {
// Implement the protocol
func finishedWithPokemonAbilities(abilities: [String]) {
// Do something with their abilities here
}
}
struct PokemonService {
var delegate: PokemonServiceDelegate?
// The function that you call to get your abilities
func someUpdateFunc() {
...
let abilities = json[abilities].arrayValue.map {$0["ability"]["name"].stringValue}
delegate?.finishedWithPokemonAbilities(abilities: abilities)
...
}
}
I am implementing coordinator pattern to handle navigation in my app. In theory when users choose different category I want to set the splitViewController to replace the existing navigationController for that category by a new one.
When app starts the coordinator operate as expected, and when I pop or push on the same navigationController implemented at start also works fine, my only problem is when I try to replace the whole navigationController of the splitviewcontroller.
ISSUE: adding new navigationController is not displayed to the user
here is my implementation.
class Coordinator: Navigable, DataCommunicator{
//MARK: - Navigable Conformable
typealias UIController = SplitController
var viewController: UIController
var childCoordinators: [Coordinatable] = []
//MARK: - Root Custom setup
weak var parentCoordinator: RootCoordinator?
//MARK: - Init
init(viewController: UIController) {
self.viewController = viewController
}
func start() {
let categoryNavigationController = CategoryNavigationController()
let categoryNavigationCoordinator = CategoryNavigationCoordinator(viewController: noteNavigationController)
categoryNavigationCoordinator.start()
childCoordinators.append(categoryNavigationCoordinator)
categoryNavigationController.coordinator = self
viewController.viewControllers = [categoryNavigationController]
}
func startSearchCategory() {
childCoordinators.removeLast()
viewController.navigationController?.popToRootViewController(animated: false)
viewController.viewControllers.removeLast()
let searchNavigationController = SearchNavigationController()
let searchCoordinator = SearchNavigationCoordinator(viewController:searchNavigationController)
searchCoordinator.start()
childCoordinators.append(searchCoordinator)
searchNavigationController.coordinator = self
searchCoordinator.parentCoordinator = self
viewController.viewControllers = [searchNavigationController]
}
}
Update:
I think I reached the desired behavior with a different approach, still I am curious why I can't display different navigationController for the masterController in the UISplitViewController and display it.
But my approach helped my code to be more modular. I added in my Coordinator protocol the following function
func stopChild<T: Coordinatable>(coordinator: T, callback: CoordinatorCallBack?)
and implemented the function as the following:
override func stopChild<T>(coordinator: T, callback: CoordinatorCallBack?) where T : Coordinatable {
childCoordinators = childCoordinators.filter({$0 !== coordinator})
// Calling parent to stop the child coordinator to roll back to the rootController
parentCoordinator?.stopChild(coordinator: self, callback: nil)
}
Rolling back helped me to instantiate the full stack I desire without trying to add custom modifying code for the splitViewController, instead I am replacing the whole splitViewController with the one corresponding to the module I am working with, which is prettier for generic use.
Since in my call back I can send to the root coordinator the desired module the user will be interested in next.
Consider the following code, which adds a gesture recognizer to a view.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleGesture(gesture:)))
let test1 = self
#objc func handleGesture(gesture: UITapGestureRecognizer) {
// some code
print("hello")
}
override func viewDidLoad() {
let test2 = self
super.viewDidLoad()
imageView.addGestureRecognizer(gesture)
}
}
As per this question, the above code does not work because I'm trying to use self (in the gesture recognizer's initializer) when not fully initialized, and this is so because of Swift's two-phase initialization.
I'm not interested in the easy fix to make this work, but this triggers a couple of questions:
1) Why does the compiler allow us to use self here if self is not ready to be used? Shouldn't I get a compiler error if I'm trying to use self too soon?
2) We can't directly inspect the type of self with alt+click in XCode. However, we can inspect the types of my ad hoc variables test1 and test2. While test2's type is ViewController, as expected, test1's type is (ViewController) -> () -> ViewController (i.e., a closure that takes a ViewController and returns a closure that takes nothing and returns a ViewController). What is that and why does self have two different types within the same class?
1)
Shouldn't I get a compiler error if I'm trying to use self too soon?
I do agree. You may send a bug report to swift.org.
Why does the compiler allow us to use self here if self is not ready to be used?
Unfortunately, there's another self in the descendants of NSObject, the method self() of NSObject.
2)
What is that and why does self have two different types within the same class?
The current Swift interprets the initial value expression in the class context, not in the instance context.
You know method names can be used as closures in Swift:
class ViewController: UIViewController {
//..
func aMethod() {
//...
}
func anInstanceMethod() {
let meth = aMethod // () -> ()
}
}
Swift can also refer to an instance method in the class context, which generates a so-called unapplied method reference (see SE-0042), which currently returns a curried function:
class ViewController: UIViewController {
//...
func aMethod() {
//...
}
class func aClassMethod() {
let meth = aMethod // (ViewController) -> () -> ()
}
}
The method self() as well.
Generally we do not need self() method and this behavior should be changed, I think.
This is interesting behaviour that works for Objective-C objects. Let's take these three examples:
class Object: NSObject {
let test = self // compiles
}
class NonNSObject {
// let test = self // errors
lazy var lazyTest = self // compiles
}
struct NonClass {
// let test = self // errors
lazy var lazyTest = self // errors
}
NonNSObject exhibits what you'd escape:
The object cannot reference itself until it is fully initialized, and let bindings must all be initialized before full initialization, so this failed.
However, NSObject happens to have an Objective-C method - (instancetype)self; which returns self. We can model this on NonNSObject as so:
func returnSelf() -> NonNSObject {
return self
}
This is where we start to see the answer to 2).
If we reference this method returnSelf on the Class we get the signature (NonNSObject) -> () -> NonNSObject. You can do this with any instance method as so:
let test = NonNSObject.returnSelf
The signature makes sense in this context:
The argument is the object we actually want to call the method on
Then we "apply" the function (with no arguments, in this case)
And we finally get our return value
let curriedFunction = NonNSObject.returnSelf // (Self) -> () -> Self
let readyToCall = curriedFunction(NonNSObject()) // () -> Self
let finallyApplied = readyToCall() // Self
Putting all the pieces together, we can see that in the case of ViewController (which inherits from UIViewController which way up the chain inherits from NSObject) there is an instance method self which the compiler is assuming you meant, so it uses that instead of the instance itself (as that would be an error). Its signature is thus a natural consequence of using an instance method on the class itself—it needs an instance, which is the first argument.
In summary:
1) Instead of assuming you made an error, the Swift compiler finds a function self on NSObject and returns the curried form.
2) This is the curried form of a function, in particular, an instance method which returns its own type.
2.5) It's still highlighted in pink because Swift-ObjC interop is mildly hacky, and self is both a method and, well, self.
As a bonus, the struct cannot reference itself at all, even lazily.
My program consists of three relatively-distinct areas: listening on a network for new state, performing network actions, and updating the UI. So respectively I want three classes: StateListener, ActionSender, and ViewController, each chugging along on separate threads.
Would that it were so simple -- the three need to interact. Some states discovered by the StateListener require Actions to be sent by the ActionSender or the UI to be updated by the ViewController. Some responses to Actions require the UI to be updated by the ViewController. Some UI actions require Actions to be performed by the ActionSender.
Currently I do something like this (pseudocode):
/* ViewController.swift */
class ViewController : blah
{
//...
func buttonPressed()
{
// ?! Need to do an action here but I can't
// because actionSender is initialised below...
}
func viewDidLoad()
{
let actionSender = ActionSender(m_view: self)
let actionQueue = OperationQueue()
let stateListener = StateListener(m_view: self,
m_actionSender: actionSender,
m_actionQueue: actionQueue)
let stateQueue = OperationQueue()
stateQueue.addOperation(stateQueue.listen())
}
}
/* StateListener.swift */
class StateListener
{
// ...
func listen()
{
while true
{
var state = waitForNewState()
if shouldActOn(state)
{
m_actionQueue.addOperation(m_actionSender.act())
}
}
}
}
/* ActionSender.swift */
class ActionSender
{
// ...
func act()
{
var reply = sendAction()
OperationQueue.main.addOperation(m_view.m_textBox.append(reply))
}
}
This is fairly hellish and doesn't even do what I want it to do, because I can't have the ViewController perform actions (ActionSender's require a ViewController reference to update the view after the action, but I tried initialising the ActionSender within ViewController.init and I got bizarre errors to do with a Code.init that I hadn't implemented...). I want to get above ViewController and initialise all these OperationQueues and objects wherever ViewController gets initialised, but I can't find where that is...
What I've done above is basically object-reference injection of each object and OperationQueue. I know there are other ways of doing this (a hierarchy of callbacks, NSNotifications) but I'm unsure of which is best.
My question is in two parts:
What is the best (i.e., fastest, easiest to implement and maintain, most idiomatic in Swift) way to get the inter-object and inter-thread communication I desire?
I currently get things going from ViewController's viewDidLoad function, which seems awful (and means I can't get a 'higher-up' perspective of the ViewController. Where should this stuff go? AppDelegate advertises itself as the 'program startup' area, but I can't access the ViewController from there... XCode seems to have hidden the startup of my app from me!
I really appreciate your responses!
This post greatly helped with question 2 and the problem of passing self as a parameter to a data member in an initialiser : http://blog.scottlogic.com/2014/11/20/swift-initialisation.html
Specifically, I can use this pattern:
class Foo : blah
{
var m_bar : Bar!
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar = Bar(m_foo: self)
m_bar.doYourThing()
}
}
The blog prefers the following, which I feel I should add here out of gratitude to the author, though I prefer the above.
class Foo : blah
{
lazy var m_bar : Bar = {
return Bar(m_foo: self) // notice I can pass in self
}
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar.doYourThing()
}
}
I have a custom WorkoutSessionManager.swift that does not subclass WKInterfaceController. It has its own context and protocol. It contains the query and sample updates. All the heart rate, distance and energy data (HKUnits) are printing to the console. This simple block outputs the data to the console.
guard let sample = activeEnergyBurnedSamples.first else{return}
let value = sample.quantity.doubleValueForUnit(self.energyUnit)
print(value)
I have a separate Dashboard.swift with my mi, Cal, bpm labels.
Since it is live data is it possible to query the HK data directly without passing this value property?
If this isn't possible how should I accomplish sending the value to my labels in the external class?
WorkoutSessionManager.swift
func createActiveEnergyStreamingQuery(workoutStartDate: NSDate) -> HKQuery? {
guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned) else {return nil}
let activeEnergyQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, samples, deletedObjects, newAnchor, error) -> Void in
guard let newAnchor = newAnchor else {return}
self.anchor = newAnchor
self.addActiveEnergySamples(samples)
}
activeEnergyQuery.updateHandler = {(query, samples, deletedObjects, newAnchor, error) -> Void in
self.anchor = newAnchor!
self.addActiveEnergySamples(samples)
}
return activeEnergyQuery
}
func addActiveEnergySamples(samples: [HKSample]?) {
print("updating calorie samples")
guard let activeEnergyBurnedSamples = samples as? [HKQuantitySample] else { return }
dispatch_async(dispatch_get_main_queue()) {
self.currentActiveEnergyQuantity = self.currentActiveEnergyQuantity.addQuantitiesFromSamples(activeEnergyBurnedSamples, unit: self.energyUnit)
self.activeEnergySamples += activeEnergyBurnedSamples
self.delegate?.workoutSessionManager(self, didUpdateActiveEnergyQuantity: self.currentActiveEnergyQuantity)
// Checks
guard let sample = activeEnergyBurnedSamples.first else{return}
let value = sample.quantity.doubleValueForUnit(self.energyUnit)
print(value)
}
}
DashboardController.swift
#IBOutlet weak var milesLabel: WKInterfaceLabel!
#IBOutlet weak var caloriesLabel: WKInterfaceLabel!
#IBOutlet weak var bmpLabel: WKInterfaceLabel!
If I understand correctly, you want to query the HealthKit store for certain kinds of samples, and update your UI every time new samples are saved to the store.
You can do this in several ways, including by using delegation, closures, or notifications. See below for sample code using delegation.
You've already defined a WorkoutSessionManagerDelegate protocol in your WorkoutSessionManager class (shown here). If that protocol is the same as that used in WWDC 2015 Session 203, it provides methods such as didUpdateActiveEnergyQuantity, didUpdateDistanceQuantity, and didUpdateHeartRateSample. If you give WorkoutSessionManager an object that acts as a delegate, the manager can delegate UI management to that object using the methods provided by the delegation protocol.
In the principal class, WorkoutSessionManager, define a property to hold a weak reference to the delegate: weak var delegate: WorkoutSessionManagerDelegate?
Then, whenever new samples become available, call the corresponding delegate method. So, for example, in the addActiveEnergySamples function, you already have the following line: self.delegate?.workoutSessionManager(self, didUpdateActiveEnergyQuantity: self.currentActiveEnergyQuantity)
In the delegate class, DashboardController, adopt the WorkoutSessionManagerDelegate protocol: class DashboardController: WKInterfaceController, WorkoutSessionManagerDelegate
And in awakeWithContext, assign yourself as the manager's delegate: wSM?.delegate = self
Finally, in the delegate class, implement the methods provided by the delegation protocol, and make such changes to your UI as necessary based on the data passed through those methods.