Mocking a class with Rx extensions - ios

Im trying to achieve dependency injection with protocols in Swift :)
My system under test calls .rx.signIn() on the dependency, meaning that it has extended Reactive where Base : ConcreteObject.
Is it possible to setup a protocol ready to receive .rx calls?
Any small example or alternative would be greatly appreciated.

I had the same problem during the upgrade from RxSwift2 to RxSwift3. In RxSwift2 I simply used a protocol for the rx-methods in my production code (ex. rx_JSON()) and injected a simple mock object adhering to that protocol in my unit tests.
After a short detour into generics, associated type and type erasure I settled with the following setup, which closely matches my RxSwift2-setup:
For example to hide URLSession as the concrete implementation of my HTTP-calls, I created a protocol RemoteClient:
protocol RemoteClient {
func json(url: URL) -> Observable<Any>
}
Then in my production code I reference only that protocol:
class SomeService {
var remoteClient: RemoteClient
init(remoteClient: RemoteClient) {
self.remoteClient = remoteClient
}
func getSomeData(_ id: String) -> Observable<JSONDictionary> {
let urlString = "..."
return remoteClient
.json(url: URL(string: urlString)!)
.map { data in
guard let show = data as? JSONDictionary else {
throw ParsingError.json
}
return show
}
}
}
In my unit tests I created mock objects which adhere to the RemoteClient protocol:
class RemoteClientMock: RemoteClient {
var output: Any?
var verifyInput: ((URL) -> Void)?
func json(url: URL) -> Observable<Any> {
verifyInput?(url)
return Observable.create { observer in
if let data = self.output {
observer.on(.next(data))
}
observer.on(.completed)
return Disposables.create()
}
}
}
And inject them in the system under test:
class SomeServiceTest: TestCase {
let disposeBag = DisposeBag()
func testGetSomeData() {
// setup
let expectation = self.expectation(description: "request succeeded")
let remoteClientMock = RemoteClientMock()
remoteClientMock.verifyInput = { url in
XCTAssertEqual(URL(string: "some url"), url)
}
remoteClientMock.output = ["id": 4711]
let service = SomeService(remoteClient: remoteClientMock)
// exercise
let observable = service.getSomeData("4711")
// verify
observable
.subscribe(onNext: { data in
XCTAssertEqual(4711, data["id"] as? Int)
expectation.fulfill()
})
.addDisposableTo(disposeBag)
waitForExpectations(timeout: 1, handler: nil)
}
}
So I simply hide the rx-namespace behind my protocol. I do that for now by using an adapter:
let remoteClient = URLSessionRemoteClientAdapter(urlSession: URLSession.shared)
let service = SomeService(remoteClient: remoteClient)
struct URLSessionRemoteClientAdapter: RemoteClient {
let urlSession: URLSession
init(urlSession: URLSession) {
self.urlSession = urlSession
}
func json(url: URL) -> Observable<Any> {
return urlSession.rx.json(url: url)
}
}

Related

Refresh value in text label from JSON, with Xcode

I am learning Swift to develop macOS applications and I ran into a problem. I am trying to get certain data from a JSON from the internet. I have managed to get such data and put it in simple text labels in the view but when I run Xcode and get the values, if the values from the JSON get updated, I can't see it reflected in my app. I know that I must perform a function to refresh the data but what I have always found is the function to refresh the data that is in a table, not a simple text label.
Regardless of this problem, if I wanted to add a table with 3 columns (each structure has 3 data, at least) with the values from the JSON. When I run the refresh of the table, I should include in the function the function that gets the data from the internet, right? I'm a bit lost with this too.
This is what I have:
ViewController.swift
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
let user_items = UserItems()
#IBOutlet var item_salida: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let struc_item = user_items.Item_Struct()
let position = struc_item.firstIndex(where: { $0.name == "Leanne Graham" })!
print(struc_item[position].state!)
item_salida.stringValue = struc_item[position].state!
} }
Struct.swift
import Foundation
import SwiftyJSON
// MARK: - Dato
struct User: Codable {
var name: String?
var username: String?
var email: String?
}
typealias Datos = [User]
class UserItems {
func Item_Struct() -> Datos {
let urlString = "https://jsonplaceholder.typicode.com/users"
var items_available: [User] = []
if let url = NSURL(string: urlString){
if let data = try? NSData(contentsOf: url as URL, options: []){
let items = try! JSONDecoder().decode([User].self, from: data as Data)
for item in items {
items_available.append(item)
}
}
}
return items_available
}
}
Thanks, a lot!
First of all - as you are learning Swift - please stop using snake_case variable names and also the NS.. classes NSURL and NSData.
Never load data from a remote URL with synchronous Data(contentsOf. It blocks the thread.
You need URLSession and an asynchronous completion handler.
// MARK: - Dato
struct User: Codable {
let name: String
let username: String
let email: String
}
typealias Datos = [User]
class UserItems {
func loadData(completion: #escaping (Datos) -> Void) {
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error { print(error); return }
do {
let items = try JSONDecoder().decode([User].self, from: data!)
completion(items)
} catch {
print(error)
}
}.resume()
}
}
And use it in the controller
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet var itemSalida: NSTextField!
let userItems = UserItems()
override func viewDidLoad() {
super.viewDidLoad()
userItems.loadData { users in
if let position = users.firstIndex(where: { $0.name == "Leanne Graham" }) {
DispatchQueue.main.async {
print(users[position].username)
self.itemSalida.stringValue = users[position].username
}
}
}
}
}
And forget SwiftyJSON. It's not needed anymore in favor of Codable.

How to add a function that automatically moves to the second API key in Rxswift

One API key can only make 100 requests per day. So one API key can't handle a lot of requests per day. There are other ways to solve this problem, but I would like to solve this problem by entering various API keys. For example, if the first API key makes 100 requests and the request value returns as an error, I want to add a function that automatically moves to the second API key.
Can you tell me how to make it with Rxswift?
I would appreciate any help you can provide.
The code is as below.
private func loadTopNews() {
let resource = Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode[0])&sortBy=%20popularity&apiKey=\(apiKey[0])")!)
URLRequest.load(resource: resource)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
}).disposed(by: disposeBag)
}
struct Resource<T: Decodable> {
let url: URL
}
extension URLRequest {
static func load<T>(resource: Resource<T>) -> Observable<T> {
return Observable.just(resource.url)
.flatMap { url -> Observable<Data> in
let request = URLRequest(url: url)
return URLSession.shared.rx.data(request: request)
}.map { data -> T in
return try JSONDecoder().decode(T.self, from: data)
}
}
}
struct ArticleResponse: Decodable {
let articles: [Article]
}
struct Article: Decodable {
let title: String
let publishedAt: String
let urlToImage: String?
let url: String
}
struct ArticleListViewModel {
let articlesVM: [ArticleViewModel]
}
extension ArticleListViewModel {
init(_ articles: [Article]) {
self.articlesVM = articles.compactMap(ArticleViewModel.init)
}
}
extension ArticleListViewModel {
func articleAt(_ index: Int) -> ArticleViewModel {
return self.articlesVM[index]
}
}
struct ArticleViewModel {
let article: Article
init(_ article: Article) {
self.article = article
}
}
extension ArticleViewModel {
var title: Observable<String> {
return Observable<String>.just(article.title)
}
var publishedAt: Observable<String> {
return Observable<String>.just(article.publishedAt)
}
var urlToImage: Observable<String> {
return Observable<String>.just(article.urlToImage ?? "NoImage")
}
var url: Observable<String> {
return Observable<String>.just(article.url)
}
}
I wrote an article covering this very thing (albeit in a different context): RxSwift and Handling Invalid Tokens
The above article will help if you are making multiple requests at the same time and need to restart all of them with the new token. It might be overkill in this specific case.
To solve this problem, you need:
A function that will build a resource with a given api key
An Observable that emits a different API key whenever it's subscribed to.
Once you have those two pieces, you can just retry your subscription until one of the keys works.
Solution
I suggest you use the above as clues and try to solve the problem yourself. Then you can check your answer against the solution below...
For item 1, I see that you need two arguments to create a resource. So I suggest making a function factory that will produce a function that takes an apiKey. Like this:
func makeResource(selectedLanguagesCode: String) -> (String) -> Resource<ArticleResponse> {
{ apiKey in
Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode)&sortBy=%20popularity&apiKey=\(apiKey)")!)
}
}
Note that this function is not part of the class. It doesn't need self.
For item 2, we need a function that takes the array of apiKeys and produces an Observable that will emit a different key each time it's subscribed to:
Something like this should work:
func produceApiKey(apiKeys: [String]) -> Observable<String> {
var index = 0
return Observable.create { observer in
observer.onNext(apiKeys[index % apiKeys.count])
observer.onCompleted()
index += 1
return Disposables.create()
}
}
Again, this function doesn't need self so it's not part of the class.
Now that you have these two elements, you can use them in your loadTopNews() method. Like this:
private func loadTopNews() {
produceApiKey(apiKeys: apiKey)
.map(makeResource(selectedLanguagesCode: selectedLanguagesCode[0]))
.flatMap(URLRequest.load(resource:))
.retry(apiKey.count - 1)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
})
.disposed(by: disposeBag)
}

How to implement simple MVC design pattern in Swift?

I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[DataModelItem])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
func requestData() {
}
private func setDataWithResponse(response:[AnyObject]){
var data = [DataModelItem]()
for item in response{
if let tableViewModel = DataModelItem(data: item as? [String : String]){
data.append(tableViewModel)
}
}
delegate?.didRecieveDataUpdata(data: data)
}
}
And for DataModelItem:
class DataModelItem{
var name:String?
var id:String?
init?(data:[String:String]?) {
if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
self.name = serviceName
self.id = serviceId
}
else{
return nil
}
}
}
Controller:
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
How to implement simple MVC design pattern in Swift?
As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.
For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.
How to call a DataModel init function in "requestData" function
It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.
In the view controller:
let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()
I am just sharing my answer here and I am using a codable. It will be useful for anyone:
Model:
import Foundation
struct DataModelItem: Codable{
struct Result : Codable {
let icon : String?
let name : String?
let rating : Float?
let userRatingsTotal : Int?
let vicinity : String?
enum CodingKeys: String, CodingKey {
case icon = "icon"
case name = "name"
case rating = "rating"
case userRatingsTotal = "user_ratings_total"
case vicinity = "vicinity"
}
}
let results : [Result]?
}
NetWork Layer :
import UIKit
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[String])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
var theatreNameArray = [String]()
var theatreVicinityArray = [String]()
var theatreiconArray = [String]()
func requestData() {
Service.sharedInstance.getClassList { (response, error) in
if error != nil {
self.delegate?.didFailUpdateWithError(error: error!)
} else if let response = response{
self.setDataWithResponse(response: response as [DataModelItem])
}
}
}
private func setDataWithResponse(response:[DataModelItem]){
for i in response[0].results!{
self.theatreNameArray.append(i.name!)
self.theatreVicinityArray.append(i.vicinity!)
self.theatreiconArray.append(i.icon!)
}
delegate?.didRecieveDataUpdata(data: theatreNameArray)
print("TheatreName------------------------->\(self.theatreNameArray)")
print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
print("Theatreicon------------------------->\(self.theatreiconArray)")
}
}
Controller :
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
APIManager :
class Service : NSObject{
static let sharedInstance = Service()
func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
guard let gitUrl = URL(string: "") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(DataModelItem.self, from: data)
completion!([gitData],nil)
} catch let err {
print("Err", err)
completion!(nil,err as NSError)
}
}.resume()
}
}
I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application.
You may refer its documentation at :
Managing Shared resources using singleton
With this you wont need to initialise this class instance every time you need to access data.

How to proper inject dependency using swinject

I'm trying to inject dependency using Swinject, and I have no clue what I'm doing wrong.
I have protocol, that handles registring user.
protocol AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) }
and a class that conforms to this protocol make all the logic:
class AuthService: AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) {
let lowerCaseMail = email.lowercased()
let body: [String: Any] = [
"email": lowerCaseMail,
"password" : password
]
Alamofire.request(URL_REGISTER, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseString { (response) in
if response.result.error == nil {
completion(true)
} else {
completion(false)
debugPrint(response.result.error as Any)
}
}
}
}
so, in AppDelegate we register container and it looks like:
let container = Container() { container in
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
return controller
}
}
but in CreateAccountVC authService is empty. Any ideas how can i do it?
CreateAccountVC is a subclass of ViewController, i have try'ed it by property, and constructors, but it's nil all the time.
Check your code:
var container : Container {
let container = Container()
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
print(r.resolve(AuthServiceProtocol.self))
return controller
}
return container
}
You have computed property and every time you call it, it creates a NEW Container object.
Refactor your code to have a single Container and I believe you will be good to go.
EDIT:
Here's a working code snippet.
Below is a small wrapper class to abstract concrete DI service (in case Swinject is one day replace by something else):
import Swinject
public class ConfigurationProvider {
// Currently using Swinject
private let backingService = Container()
// Singleton
public static let shared = ConfigurationProvider()
// Hidden initializer
private init() {}
// MARK: - Bind / Resolve
public func bind<T>(interface: T.Type, to assembly: T) {
backingService.register(interface) { _ in assembly }
}
public func resolve<T>(interface: T.Type) -> T! {
return backingService.resolve(interface)
}
}
// Extension methods to ignore 'shared.' call, like:
// ConfigurationProvider.bind(interface: IAssembly, to: Assembly())
// ConfigurationProvider.resolve(interface: IAssembly)
public extension ConfigurationProvider {
static func bind<T>(interface: T.Type, to assembly: T) {
ConfigurationProvider.shared.bind(interface: interface, to: assembly)
}
static func resolve<T>(interface: T.Type) -> T! {
return ConfigurationProvider.shared.resolve(interface: interface)
}
}
Usage:
class RSAuthLoginModuleAssembly: IAuthLoginModuleAssembly {
}
// Register:
ConfigurationProvider.bind(interface: IAuthLoginModuleAssembly.self, to: ConcreteAuthLoginModuleAssembly())
// Resolve:
guard let assembly = ConfigurationProvider.resolve(interface: IAuthLoginModuleAssembly.self) else {
throw NSError(domain: "Assembly cannot be nil", code: 999, userInfo: nil)
}

How to write a simple generic Core Data function for all Entities

EDIT: Specifically using Swift Generics
I want a countAll() function for all of my entities and I'm hoping to achieve this by writing one generic function.
The following handles an entity called 'Event', but I want to handle an entity named 'T'.
I'm not sure how to write a generic function like this. Could someone help please?
func countAll() -> Int {
let request: NSFetchRequest<Event> = Event.fetchRequest()
do {
return try persistentContainer.viewContext.count(for: request)
} catch {
XCTAssert(false, "Core Data failed to fetch with error: " + error.localizedDescription)
return 0
}
}
This is the closest I can get before I hit an error:
func count<T: NSFetchRequestResult>(entityName: String) -> Int {
let request = NSFetchRequest<T>(entityName: entityName)
do {
return try persistentContainer.viewContext.count(for: request)
} catch {
XCTAssert(false, "Core Data failed to fetch with error: " + error.localizedDescription)
return 0
}
}
You can pass the managed object class instead of NSFetchRequestResult
to the generic function:
func countAll<T: NSManagedObject>(entity: T.Type) -> Int {
let entityName = String(describing: entity)
let request = NSFetchRequest<T>(entityName: entityName)
do {
return try thePersistentContainer.viewContext.count(for: request)
} catch {
print(error.localizedDescription)
return 0
}
}
let count = countAll(entity: YourEntity.self)
An alternative is to define an extension method on NSManagedObject
(similarly as in How can I create instances of managed object subclasses in a NSManagedObject Swift extension?):
extension NSManagedObject {
class func countAll() -> Int {
let eName = String(describing: self)
let request = NSFetchRequest<NSFetchRequestResult>(entityName: eName)
do {
return try thePersistentContainer.viewContext.count(for: request)
} catch {
print(error.localizedDescription)
return 0
}
}
}
let count = YourEntity.countAll()
A suitable alternative to a generic function is a protocol extension and class methods
protocol Fetchable
{
associatedtype FetchableType: NSManagedObject = Self
static var entityName : String { get }
static func countAll(in persistentContainer: NSPersistentContainer) throws -> Int
}
extension Fetchable where Self : NSManagedObject, FetchableType == Self
{
static var entityName : String {
return NSStringFromClass(self).components(separatedBy: ".").last!
}
static func countAll(in persistentContainer: NSPersistentContainer) throws -> Int
{
let request = NSFetchRequest<FetchableType>(entityName: entityName)
return try persistentContainer.viewContext.count(for: request)
}
}
The benefit is you can call the method on the class, just make your NSManagedObject subclasses adopt the protocol.
do {
try Event.countAll(in: persistentContainer)
} catch { print(error) }

Resources