Recently I watched this talk (link, see section Networking) and I decided to do some excerise in protocol-oriented programming. So I thought about this simple example: View Controller for displaying list of files. Of course, protocol-oriented way, with following constraints:
FilesViewController - containts table view & FilesTableViewAdapter. Table view delegate.
FilesTableViewAdapter - initializable with table view & FilesProvider: Gettable,
so that in tests I can inject FilesProviderMock: Gettable.
FilesTableViewAdapter is a data source of table view and uses FilesProvider for fetching files.
final class FilesTableViewController: UIViewController {
var filesTableView: FilesTableView! { return view as! FilesTableView }
private var tableViewAdapter: FilesTableViewAdapter<FilesProvider>!
// MARK: Subclassing
override func loadView() {
view = FilesTableView(frame: UIScreen.main.bounds)
}
override func viewDidLoad() {
tableViewAdapter = FilesTableViewAdapter(filesTableView.tableView, provider: FilesProvider())
// Actually I would like to have this method in Adapter
// so that VC isn't handling networking.
tableViewAdapter.provider.get { result in
// result type: (Result<[File]>)
switch result {
case .success(let files): print(files)
case .failure(let error): print(error)
}
}
filesTableView.tableView.delegate = self
filesTableView.tableView.dataSource = tableViewAdapter
}
}
extension FilesTableViewController: UITableViewDelegate {
//
}
final class FilesTableViewAdapter<T: Gettable>: NSObject, UITableViewDataSource {
let provider: T
private let tableView: UITableView
init(_ tableView: UITableView, provider: T) {
self.tableView = tableView
self.provider = provider
super.init()
}
func problem() {
provider.get { result in
// Result type is (Result<T.T>) - :(
switch result {
case .success(let files): print(files)
case .failure(let error): print(error)
}
}
}
struct FilesProvider {
private let Files = [File]()
}
extension FilesProvider: Gettable {
func get(completionHandler: (Result<[File]>) -> Void) {
//
}
}
protocol Gettable {
associatedtype T
func get(completionHandler: (Result<T>) -> Void)
}
I know I went too far with generalizing this piece of code. Now I'm stuck and I have this questions I can't answer myself:
How to make it in protocol-oriented way, with networking code in class different than VC (say Adapter)?
How to make it easily testable and extendable in the future?
Related
this is not a question regarding that should we use singleton or not. but rather mocking singleton related.
this is just a sample example, as i was reading about mocking singleton is tough. so i thought let me give a try.
i am able to mock it but not sure is this a correct approach ?
protocol APIManagerProtocol {
static var sharedManager: APIManagerProtocol {get set}
func doThis()
}
class APIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = APIManager()
private init() {
}
func doThis() {
}
}
class ViewController: UIViewController {
private var apiManager: APIManagerProtocol?
override func viewDidLoad() {
}
convenience init(_ apimanager: APIManagerProtocol){
self.init()
apiManager = apimanager
}
func DoSomeRandomStuff(){
apiManager?.doThis()
}
}
import Foundation
#testable import SingleTonUnitTesting
class MockAPIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = MockAPIManager()
var isdoThisCalled = false
func doThis(){
isdoThisCalled = true
}
private init(){
}
}
class ViewControllerTests: XCTestCase {
var sut: ViewController?
var mockAPIManager: MockAPIManager?
override func setUp() {
mockAPIManager = MockAPIManager.sharedManager as? MockAPIManager
sut = ViewController(mockAPIManager!)
}
func test_viewController_doSomeRandomStuffs(){
sut?.DoSomeRandomStuff()
XCTAssertTrue(mockAPIManager!.isdoThisCalled)
}
override func tearDown() {
sut = nil
mockAPIManager = nil
}
}
The basic idea is right: Avoid repeated references to the singleton directly throughout the code, but rather inject object that conforms to the protocol.
What’s not quite right is that you are testing something internal to the MockAPIManager class. The mock is only there to serve a broader goal, namely to test your business logic (without external dependencies). So, ideally, you should be testing something that is exposed by APIManagerProtocol (or some logical result of that).
So, let’s make this concrete: For example, let’s assume your API had some method to retrieve the age of a user from a web service:
public protocol APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void)
}
(Note, by the way, that the static singleton method doesn’t belong in the protocol. It’s an implementation detail of the API manager, not part of the protocol. No controllers that get a manager injected will ever need to call shared/sharedManager themselves.)
And lets assume that your view controller (or perhaps better, its view model/presenter) had a method to retrieve the age and create an appropriate message to be shown in the UI:
func buildAgeMessage(for userid: String, completion: #escaping (String) -> Void) {
apiManager?.fetchAge(for: userid) { result in
switch result {
case .failure:
completion("Error retrieving age.")
case .success(let age):
completion("The user is \(age) years old.")
}
}
}
The API manager mock would then implement the method:
class MockAPIManager: APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void) {
switch userid {
case "123":
completion(.success(42))
default:
completion(.failure(APIManagerError.notFound))
}
}
}
Then you could test the logic of building this string to be shown in your UI, using the mocked API rather than the actual network service:
class ViewControllerTests: XCTestCase {
var viewController: ViewController?
override func setUp() {
viewController = ViewController(MockAPIManager())
}
func testSuccessfulAgeMessage() {
let e = expectation(description: "testSuccessfulAgeMessage")
viewController?.buildAgeMessage(for: "123") { string in
XCTAssertEqual(string, "The user is 42 years old.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
func testFailureAgeMessage() {
let e = expectation(description: "testFailureAgeMessage")
viewController?.buildAgeMessage(for: "xyz") { string in
XCTAssertEqual(string, "Error retrieving age.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
}
i was reading about mocking singleton is tough
The notion is that if you have these APIManager.shared references sprinkled throughout your code, it’s harder to swap them out with the mock object. Injecting solves this problem.
Then, again, if you’ve now injected this APIManager instance everywhere to facilitate mocking and have eliminate all of these shared references, it begs the question that you wanted to avoid, namely why use a singleton anymore?
Where is the correct place I should put the code that would trigger a loading to display in my app.
It is correct to do is on view? since it is displaying something on screen, so it fits as a UI logic
class ViewController: UIViewController {
func fetchData() {
showLoading()
interactor?.fetchData()
}
}
or on interactor? since it's a business logic. something like, everytime a request is made, we should display a loading. View only knows how to construct a loading, not when to display it.
class Interactor {
func fetchData() {
presenter?.presentLoading(true)
worker?.fetchData() { (data) [weak self] in
presenter?.presentLoading(false)
self?.presenter?.presentData(data)
}
}
}
same question applies to MVVM and MVP.
it is totally up to you . i am showing loading using an Observable .
in my viewModel there is an enum called action :
enum action {
case success(count:Int)
case deleteSuccess
case loading
case error
}
and an Observable of action type :
var actionsObservable = PublishSubject<action>()
then , before fetching data i call onNext method of actionObservable(loading)
and subscribing to it in viewController :
vm.actionsObserver
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (action) in
switch action {
case .success(let count):
if(count == 0){
self.noItemLabel.isHidden = false
}
else{
self.noItemLabel.isHidden = true
}
self.refreshControl.endRefreshing()
self.removeSpinner()
case .loading:
self.showSpinner(onView : self.view)
case .error:
self.removeSpinner()
}
}, onError: { (e) in
print(e)
}).disposed(by: disposeBag)
You can use the delegate or completion handler to the update the UI from view model.
class PaymentViewController: UIViewController {
// for UI update
func showLoading() {
self.showLoader()
}
func stopLoading() {
self.removeLoader()
}
}
protocol PaymentOptionsDelegate : AnyObject {
func showLoading()
func stopLoading()
}
class PaymentOptionsViewModel {
weak var delegate : PaymentOptionsDelegate?
func fetchData() {
delegate?.showLoading()
delegate?.stopLoading()
}
}
I have an application written in the MVVM-C pattern, using RxSwift
After adding a new view programmatically, the application crashes with a
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
error. I am at a complete loss, the implementation is almost exactly the same, minus the fact one view controller is a storyboard and one is not.
This is my new ViewController
import UIKit
import RxSwift
import RxCocoa
final class FeedViewController: TableViewController, ViewModelAttaching {
var viewModel: Attachable<FeedViewModel>!
var bindings: FeedViewModel.Bindings {
let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.mapToVoid()
.asDriverOnErrorJustComplete()
let refresh = tableView.refreshControl!.rx
.controlEvent(.valueChanged)
.asDriver()
return FeedViewModel.Bindings(
fetchTrigger: Driver.merge(viewWillAppear, refresh),
selection: tableView.rx.itemSelected.asDriver()
)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func bind(viewModel: FeedViewModel) -> FeedViewModel {
viewModel.posts
.drive(tableView.rx.items(cellIdentifier: FeedTableViewCell.reuseID, cellType: FeedTableViewCell.self)) { _, viewModel, cell in
cell.bind(to: viewModel)
}
.disposed(by: disposeBag)
viewModel.fetching
.drive(tableView.refreshControl!.rx.isRefreshing)
.disposed(by: disposeBag)
viewModel.errors
.delay(0.1)
.map { $0.localizedDescription }
.drive(errorAlert)
.disposed(by: disposeBag)
return viewModel
}
}
This is an existing one, that works but uses storyboards
final class PostsListViewController: TableViewController, ViewModelAttaching {
var viewModel: Attachable<PostsListViewModel>!
var bindings: PostsListViewModel.Bindings {
let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.mapToVoid()
.asDriverOnErrorJustComplete()
let refresh = tableView.refreshControl!.rx
.controlEvent(.valueChanged)
.asDriver()
return PostsListViewModel.Bindings(
fetchTrigger: Driver.merge(viewWillAppear, refresh),
selection: tableView.rx.itemSelected.asDriver()
)
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
// MARK: - View Methods
private func setupView() {
title = "Posts"
}
func bind(viewModel: PostsListViewModel) -> PostsListViewModel {
viewModel.posts
.drive(tableView.rx.items(cellIdentifier: PostTableViewCell.reuseID, cellType: PostTableViewCell.self)) { _, viewModel, cell in
cell.bind(to: viewModel)
}
.disposed(by: disposeBag)
viewModel.fetching
.drive(tableView.refreshControl!.rx.isRefreshing)
.disposed(by: disposeBag)
viewModel.errors
.delay(0.1)
.map { $0.localizedDescription }
.drive(errorAlert)
.disposed(by: disposeBag)
return viewModel
}
}
They are basically exactly the same. The exception is thrown on the let refresh = tableView.refreshControl!.rx line.
The working coordinator, using a Storyboard is
import RxSwift
class PostsCoordinator: BaseCoordinator<Void> {
typealias Dependencies = HasPostService
private let navigationController: UINavigationController
private let dependencies: Dependencies
init(navigationController: UINavigationController, dependencies: Dependencies) {
self.navigationController = navigationController
self.dependencies = dependencies
}
override func start() -> Observable<Void> {
let viewController = PostsListViewController.instance()
navigationController.viewControllers = [viewController]
let avm: Attachable<PostsListViewModel> = .detached(dependencies)
let viewModel = viewController.attach(wrapper: avm)
viewModel.selectedPost
.drive(onNext: { [weak self] selection in
self?.showDetailView(with: selection)
})
.disposed(by: viewController.disposeBag)
// View will never be dismissed
return Observable.never()
}
private func showDetailView(with post: Post) {
let viewController = PostDetailViewController.instance()
viewController.viewModel = PostDetailViewModel(post: post)
navigationController.showDetailViewController(viewController, sender: nil)
}
}
I have an extension to allow me to instantiate it also
protocol Reusable {
static var reuseID: String { get }
}
extension Reusable {
static var reuseID: String {
return String(describing: self)
}
}
// MARK: - View Controller
extension UIViewController: Reusable {
class func instance() -> Self {
let storyboard = UIStoryboard(name: reuseID, bundle: nil)
return storyboard.instantiateViewController()
}
}
extension UIStoryboard {
func instantiateViewController<T: UIViewController>() -> T {
guard let viewController = self.instantiateViewController(withIdentifier: T.reuseID) as? T else {
fatalError("Unable to instantiate view controller: \(T.self)")
}
return viewController
}
}
The 'broken' coordinator is exactly the same, except I swapped
let viewController = PostsListViewController.instance()
for
let viewController = FeedViewController()
I am at a complete loss at to why this is throwing. Print statements and breakpoints at various points haven't turned up a nil on any values.
Please let me know if it would be easier to share a sample app as I appreciate the code snippets may not be the most obvious.
tableView.refreshControl is nil. You are trying to force access the nil refreshControl.
The Refreshing property is Enabled for the UITableViewController in your storyboard that works. In the programmatic version, the refreshControl is not created automatically.
The default value of the refreshControl property is nil. You need to instantiate and assign a UIRefreshControl to self.refreshControl before it exists.
When you create your view using a Storyboard and enable it, this is taken care of behind the scenes for you. Programmatically you will be required to implement this yourself.
I want to detective networking state, when networking state changed, show a error view in current controller. But there is a problem by using protocol.
Here is the codes:
private func networkingDetection() {
//This is the detective method in appdelegate
try! reachability.startNotifier()
reachability.whenReachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().hideNetworkingErrorView()
}
}
reachability.whenUnreachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().showNetworkingErrorView()
}
}
}
And here is the protocol
protocol NetworkingErrorProtocol {
// I want to show the default view if there is no networkingErrorView, and
when declare a custom view in controller, show the custom view.
//var networkingErrorView: UIView? { get }
func showNetworkingErrorView()
func hideNetworkingErrorView()
}
extension UIViewController: NetworkingErrorProtocol {
func showNetworkingErrorView() {
}
func hideNetworkingErrorView() {
}
}
Anyone can tell me how to figure it out? It's really makes me crazy. Thanks a lot.
The issue with your setup is that conforming UIViewController to your protocol does not allow you to receive that call in your subclass. If you try to override the protocol function in your subclass you will get a compiler error: Declarations from extensions cannot be overridden yet
First off, a note about NotificationCenter. If you need multiple parts of your app to be notified of the change that would be a good way to go. If you only need to tell one controller, this is a classic usage for a delegate.
Here are two ways to get the desired functionality: using the delegate pattern and without.
Let's say Manager is the class where the monitoring is happening:
Using a delegate pattern
class Manager {
weak var networkDelegate : NetworkStatusListener?
func monitorNetworkStatus() {
var reachable = true;
if reachable {
// We can call the delegate directly
networkDelegate?.networkStatusChanged(.connected)
}
else {
networkDelegate?.networkStatusChanged(.disconnected)
}
}
}
And the same Manager without a delegate pattern. This would be the simplest fix for your current implementation issue.
class Manager {
func currentViewController() -> UIViewController { return vc }
func monitorNetworkStatus() {
var maybeAtListener = currentViewController()
// DON't SHIP THIS, but it can be helpful during development to make sure you didn't forget to conform one of your classes
assert(maybeAtListener is NetworkStatusListener, "Oops, did you mean to conform \(currentVC) to NetworkStatusListener")
var reachable = true;
if reachable {
// We can't be sure the controller conforms to the protocol but we can try
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
else {
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
}
}
Then for your view controller
class MyController : UIViewController, NetworkStatusDelegate {
func networkStatusChanged(_ status: NetworkStatus) {
switch status {
case .connected:
// Normal UI
break
case .disconnected:
// No network connect
break;
}
}
}
Also, not directly related to your question but for this example I used a slightly different approach to the protocol design that can be helpful for "status" oriented protocols. Having multiple functions can often become a little more tedious to conform to as protocols get larger.
enum NetworkStatus {
case connected
case disconnected
}
protocol NetworkStatusListener : class {
func networkStatusChanged(_ status: NetworkStatus)
}
Try using reachability class's NSNotificationCenter
add this in appdelegate's didFinishLaunchingWithOptions if you want for whole app
OR add in your specific viewcontroller if you want this in specific Viewcontroller
NotificationCenter.default.addObserver(self, selector:Selector(("checkForReachability:")), name: NSNotification.Name.reachabilityChanged, object: nil);
let reachability: Reachability = Reachability.forInternetConnection();
reachability.startNotifier();
This method called while network state changed .
func checkForReachability(notification:NSNotification)
{
let networkReachability = notification.object as! Reachability;
_ = networkReachability.currentReachabilityStatus()
// do yor additional work here
}
I'm beginning with MVVM in order to well separate logic code from the view. But I have some concern about where to put the progressHUD related code when tapping a button that makes a request.
Before, I used to do that:
//Before
#IBAction func startRequestTapped() {
SVProgressHUD.show()
self.apiClient.requestObservable().subscribe(onError: { (error) in
SVProgressHUD.hide()
}, onCompleted: {
SVProgressHUD.hide()
})
}
But when I use mvvm, I do like that:
//In the viewModel
public var validateButtonDidTap = PublishSubject<Void>()
init() {
validateButtonDidTap.flatMap { (_)
return self.apiClient.requestObservable()
}
}
// In the viewController
viewDidLoad() {
let tap = self.validateButton.rx.tap
tap.bindTo(self.viewModel.validateButtonDidTap)
}
And amongst that, I don't know where to put the the ProgressHUD hide or show.
Mark answer is right, but I am going to guide you step by step.
Let's supose you're going to try signing in.
Copy ActivityIndicator.swift file in your project.
In the viewModel:
//MARK: - Properties
/// The http client
private let apiClient: YourApiClient
/// Clousure when button is tapped
var didTappedButton: () -> Void = {}
/// The user
var user: Observable<User>
/// Is signing process in progress
let signingIn: Observable<Bool> = ActivityIndicator().asObservable()
//MARK: - Initialization
init(client: YourApiClient) {
self.client = client
self.didTappedButton = { [weak self] in
self.user = self.apiClient
.yourSignInRequest()
.trackActivity(self.signingIn)
.observeOn(MainScheduler.instance)
}
}
Create an extension of SVProgressHUD: (I don't know SVProgressHUD library, but it would be something like this. Please fix it if needed)
extension Reactive where Base: SVProgressHUD {
/// Bindable sink for `show()`, `hide()` methods.
public static var isAnimating: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { progressHUD, isVisible in
if isVisible {
progressHUD.show() // or other show methods
} else {
progressHUD.dismiss() // or other hide methods
}
}
}
}
In your viewController:
#IBAction func startRequestTapped() {
viewModel.didTappedButton()
}
override func viewDidLoad() {
// ...
viewModel.signingIn
.bindTo(SVProgressHUD.rx.isAnimating)
.addDisposableTo(disposeBag)
}
Accepted answer updated to Swift 4, RxSwift 4.0.0 and SVProgressHUD 2.2.2:
3- Extension:
extension Reactive where Base: SVProgressHUD {
public static var isAnimating: Binder<Bool> {
return Binder(UIApplication.shared) {progressHUD, isVisible in
if isVisible {
SVProgressHUD.show()
} else {
SVProgressHUD.dismiss()
}
}
}
}
4- Controller:
viewModel.signingIn.asObservable().bind(to: SVProgressHUD.rx.isAnimating).disposed(by: disposeBag)
You could try using an ActivityIndicator.
See the example here:
https://github.com/RxSwiftCommunity/RxSwiftUtilities