Thread 1 EXC_BAD_ACCESS (code=2, address=0x7ffeeb1aeff8) - swift

I am trying to learn VIPER. I followed this tutorial. I have these Interactor and Presenter:
class PPresenter: ViewToPresenterProtocol {
var view: PresenterToViewProtocol?
var router: PresenterToRouterProtocol? = PRouter()
var interactor: PresenterToInteractorProtocol? = PInteractor()
func initiateFetch() {
interactor?.fetchMatches()
}
func showMatchScreen(navigationC: UIViewController) {
router?.pushToMatchDetailScreen(navigationC: navigationC)
}
}
extension PPresenter: InteractorToPresenterProtocol {
func matchFetched(match: MatchDetails?, banner: Banner?) {
print(match!)
print(banner!)
}
func matchFetchError() {
//TODO
}
}
class PInteractor: PresenterToInteractorProtocol {
var presenter: InteractorToPresenterProtocol? = PPresenter()
var live: Live?
var upcoming: Upcoming?
var banners: Banner?
func fetchMatches() {
let parameters = ["api_token" : Constants.USER_INFO["api_token"].rawValue,"player_id" : Constants.USER_INFO["player_id"].rawValue]
ServiceHelper.sharedInstance.sendRequest(path: "get-predictor", params: parameters, showSpinner: true) { (response, error) in
if let error = error {
print("Unable to fetch match listing",error.localizedDescription)
return
} else {
guard let obj = try? JSONDecoder().decode(MatchDetails.self, from: response.rawData()) else { self.presenter?.matchFetchError(); return }
guard let bannerObj = try? JSONDecoder().decode(Banner.self,from: response.rawData()) else {self.presenter?.matchFetchError(); return }
self.presenter?.matchFetched(match: obj, banner: bannerObj)
}
}
}
}
Now, what is happening here, I get the router working, the view is coming, it is calling presenter, the presenter is calling the interactor, the interactor is successfully calling the API and getting the data and now it is time to return the data received from Interactor to Presenter and here it constantly throwing the following error:
Thread 1 EXC_BAD_ACCESS (code=2, address=0x7ffeeb1aeff8)

I think you have a cyclic call, maybe your interactor is not fully initialized and then you want data from it and then you got "Bad access error".

Related

How can I update the view when the app is done scanning for NFC?

I have a Swift application that reads from an NFC card. I want it to show the data it read on the screen, which I am able to do if I have a button that checks for updated data using the getDetected() function. I want to, however, update the view when it is done reading the NFC tag so I can immediately display the data. How can I do this?
NFC Reader class:
import Foundation
import CoreNFC
class NFCReader: NSObject, NFCNDEFReaderSessionDelegate {
var detected = [NFCNDEFMessage]()
var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else { return }
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold your iPhone near the reader to unlock."
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
self.detected = messages
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
self.session = nil
}
func getDetected() -> [NFCNDEFMessage] {
return detected
}
}
you could try the following approach, using a ObservableObject.
Whenever the #Published var detected is changed, the UI will be updated.
class NFCReader: NSObject, NFCNDEFReaderSessionDelegate, ObservableObject { // <--- here
#Published var detected = [NFCNDEFMessage]() // <--- here
var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else { return }
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold your iPhone near the reader to unlock."
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
self.detected = messages
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
self.session = nil
}
// no real need for this
func getDetected() -> [NFCNDEFMessage] {
return detected
}
}
struct ContentView: View {
#StateObject var readerNFC = NFCReader() // <-- here
var body: some View {
ForEach(readerNFC.detected, id: \.self) { msg in
// .....
}
}
}

UnitTest does't work properly for MVP pattern Swift

I'm trying to write some UnitTests for the first time. My pattern is MVP and I'm trying to test my Presenter. I've created mock class: class TeamViewMock: TeamViewPresenterProtocol { }. It contains all the methods from my real Presenter. Inside the each method I'm trying to set the new value for the property, so when the method called - property should get a new value.
Only one property gets new value out of 4 and I've no clue why the other ones didn't get it.
You may see it in the following code
import XCTest
#testable import NHL
class TeamViewPresenterTest: XCTestCase {
var presenter: TeamViewPresenter!
var viewMock: TeamViewMock!
func setupPresenter() {
viewMock = TeamViewMock()
presenter = TeamViewPresenter(with: viewMock)
}
func testGetData() {
setupPresenter()
presenter.getData(completion: {_ in })
XCTAssertTrue(viewMock.isStart) // This one works and returns true
XCTAssertTrue(viewMock.isStop) // Return error
XCTAssertTrue(viewMock.isEndRefreshing) // Return error
XCTAssertTrue(viewMock.isReload) // Return error
}
}
class TeamViewMock: TeamViewPresenterProtocol {
var isStart = false
var isStop = false
var isEndRefreshing = false
var isReload = false
func startAnimating() {
self.isStart = true // Testing stops here and doesn't go any further...
}
func stopAnimating() {
self.isStop = true
}
func endRefreshing() {
self.isEndRefreshing = true
}
func reloadView(_ teams: NHLDTO) {
self.isReload = true
}
}
class TeamViewPresenter {
// MARK: - Public Properties
private weak var view: TeamViewPresenterProtocol?
public let dataFetcherService = DataFetcherService()
// MARK: - Initializers
init(with view: TeamViewPresenterProtocol) {
self.view = view
}
// MARK: - Public Methods
public func getData(completion: #escaping (AppError) -> Void) {
view?.startAnimating() // Testing stops here and doesn't go any further, but still returns true for the property isStart and error for the rest
dataFetcherService.fetchTeamData { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
completion(error)
print(error)
case .success(let teams):
guard let teams = teams else { return }
self.view?.reloadView(teams)
self.view?.stopAnimating()
self.view?.endRefreshing()
}
}
}
}
protocol TeamViewPresenterProtocol: AnyObject {
func startAnimating()
func stopAnimating()
func reloadView(_ teams: NHLDTO)
func endRefreshing()
}

delegate method does not get called second time

I am building a simple currency converter app. When ViewController gets opened it calls a function from CoinManager.swift:
class ViewController: UIViewController {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
coinManager.getCoinPrice(for: "AUD", "AZN", firstCall: true)
}
...
}
CoinManager.swift:
protocol CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String)
func tellTableView(descriptions: [String], symbols: [String])
func didFailWithError(error: Error)
}
struct CoinManager {
var delegate: CoinManagerDelegate?
let baseURL = "https://www.cbr-xml-daily.ru/daily_json.js"
func getCoinPrice (for currency1: String,_ currency2: String, firstCall: Bool) {
if let url = URL(string: baseURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
if firstCall {
var descriptions = [""]
let listOfCoins = Array(coinData.keys)
for key in listOfCoins {
descriptions.append(coinData[key]!.Name)
}
descriptions.removeFirst()
self.delegate?.tellTableView(descriptions: descriptions, symbols: listOfCoins)
}
if let coinInfo1 = coinData[currency1] {
let value1 = coinInfo1.Value
if let coinInfo2 = coinData[currency2] {
let value2 = coinInfo2.Value
//this line does not do anything the second time I call getCoinPrice:
self.delegate?.didUpdatePrice(price1: value1, currency1: currency1, price2: value2, currency2: currency2)
//And this one does work
print("delegate:\(currency1)")
} else {
print("no name matches currency2")
}
} else {
print("no name matches currency1")
}
}
}
}
task.resume()
}
}
func ParseJSON....
}
The method it calls (ViewController.swift):
extension ViewController: CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String) {
print("didUpdatePrice called")
DispatchQueue.main.async {
let price1AsString = String(price1)
let price2AsString = String(price2)
self.leftTextField.text = price1AsString
self.rightTextField.text = price2AsString
self.leftLabel.text = currency1
self.rightLabel.text = currency2
}
}
...
}
and finally, CurrencyViewController.swift:
var coinManager = CoinManager()
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
So when I launch the app i get following in my debug console:
didUpdatePrice called
delegate:AUD
And when I call getCoinPrice() from CurrencyViewController the delegate method does not get called. I know that my code goes through the delegate function line as I get this in debug console:
delegate:USD
I just can't wrap my head around it. The delegate method does not work when gets called second time. Even though it is called by the same algorithm
It's because you're creating a new object of CoinManager in CurrencyViewController where the delegate is not set. So you've to set the delegate every time you create a new instance of CoinManager.
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.delegate = self
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
Update: So, the above solution would require for you to make the delegate conformance in CurrencyViewController. If you're looking for an alternate solution you should probably pass the instance of coinManager in ViewController to CurrencyViewController. For that here are the things you need to update.
In CurrencyViewController:
class CurrencyViewController: UIViewController {
var coinManager: CoinManager! // you can optional unwrap if you intent to use CurrencyViewController without coinManager
//...
And in ViewController:
currencyViewController.coinManager = coinManager // passing the instance of coinManager
Can you share the full code of CoinManager? I see this part
if firstCall {
...
}
Maybe some block logic here or unhandled cases? And can you share the full code of protocol?
Also try to print something before this code:
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}

Swift unit testing view model interface

As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM

Swift completion handler in class and function

I have a Class with a function that connect to a firestoreDB and get some data:
import UIKit
import CoreLocation
import Firebase
private let _singletonInstance = GetBottlesFromDB()
class GetBottlesFromDB: NSObject {
class var sharedInstance: GetBottlesFromDB { return _singletonInstance }
var Pins = [LayoutBottlesFromDB]()
// MARK: - init
override init() {
super.init()
populatePinList(completion: { pin in self.Pins } )
//print("GET ALL PINS: \(Pins)")
}
func populatePinList(completion: #escaping ([LayoutBottlesFromDB]) -> ()) {
Pins = []
AppDelegate.ADglobalVar.db.collection("Bottles").whereField("pickupuser", isEqualTo: NSNull()).getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
print("start getting documents:")
for document in querySnapshot!.documents {
//print("\(document.documentID) => \(document.data())")
//print("\(document.documentID)")
let bottleID:String = document.documentID
let bottlekind:Int = document.data()["bottle"] as! Int
var bottletitel:String
var bottlesub:String
var bottleurl:String = (document.data()["pic"] as? String)!
let pin = LayoutBottlesFromDB(document.data()["lat"] as! CLLocationDegrees, document.data()["long"] as! CLLocationDegrees, ID: bottleID, title: bottletitel, subtitle: bottlesub, type: bottlekind, url:bottleurl)
//print("GET DAATA from DB: \(pin)")
self.Pins.append(pin)
} //for
completion(self.Pins)
} //else
} //querysnap
}//function
}//class
in my ViewController I call this function.
for pin in GetBottlesFromDB.sharedInstance.Pins{
print("Add Pin : \(pin)")
}
My pProblem is that the function will called but the print is empty.
The function doesn't wait for a completion. What did I do wrong?
You are calling directly GetBottlesFromDB.sharedInstance.Pins and this will not wait for completion of populatePinList method so that's why you are getting blank So You need to wait for completion or you can check if data is not available in pins variable the you need to call completion method like this way:
GetBottlesFromDB.sharedInstance.populatePinList { (pins) in
for pin in pins{
print("Add Pin : \(pin)")
}
}
Nothing in your code waits for the execution of the asynchronous method, so that's no surprise. Also, it would be a terrible design because it would block your app. In addition, your singleton implementaion is overly verbose and doesn't guarantee that it stays a singleton, so I'd recommend to change it to
class GetBottlesFromDB {
private(set) var Pins = [LayoutBottlesFromDB]()
static let shared = GetBottlesFromDB()
private init() {}
// populatePinList as before
}
and in your view controller, e.g. in viewDidLoad do:
override func viewDidLoad() {
GetBottlesFromDB.shared.populatePinList { pins in
pins.forEach { print("Add Pin: \(pin)") }
}
}