I'm developing a new project from scratch and there are some concepts about this architecture that I would like to implement in the best way.
In many references on the Internet I can see some examples to deal with Clean Swift Architecture. In this architecture you can find that Interactor has a dependency of Worker classes
protocol InteractorLogic {
// Functions
}
class Interactor: InteractorLogic {
var presenter: PresenterLogic?
var worker: AWorker?
}
The doubt I have is about implementing this Worker because I saw in some examples that inside this Worker they put all the "use cases" related to the scene:
protocol SceneWorkerLogic {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void)))
func getUser(success: ((User) -> Void), error: ((Error) -> Void)))
}
class SceneWorker {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void))) {
}
func getUser(success: ((User) -> Void), error: ((Error) -> Void))) {
}
}
and in another examples, they divide functionality in different Workers like UserWorker and PostWorker where CRUD functions related with Users and Posts are implemented:
protocol PostWorkerLogic {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void)))
}
class PostWorker {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void))) {
}
}
I consider that the second approach is better in terms of Clean Code but I've realized that it could be even cleaner if we use a file for a single CRUD operation like getUser or getPosts.
How can I improve this?
There are a lot of ways to do! The way that was showed on the clean swift book isn't my favorite...
I prefer to use workers with dependency injections to use as a facade pattern to get the data! Doing this you will have the scene worker and specific workers to do the jobs.
Example: You have a scene that present a list of movies. This list could be fetched from network or coredata so you will have a worker that receive the kind of datasource that you will use on its init method and implements a protocol with the method getMovies and this will be the scene worker! Then you will have other worker to get this from network and another one for coredata, both of them must to implement that protocol with getMovies!
I think that will be better if you get a sample app that i built from scratch using this architecture to understand it better! I really think that this way is much better than the way that he uses on his clean swift sample app (clean store).
Here is my sample app using clean swift: https://github.com/chavitos/TheMovieDB
Related
Wondering what the benefits of writing a protocol for a function are. For example given the following simple example what could be the justification for first creating a protocol that the class conforms to? Is it generally better to always create and conform to a protocol? Better for unit testing?
Are their advantages for doing unit testing when writing ng
protocol UserRepositoryProtocol {
func fetchUser (completion: (User)-> Void)
}
final class UserRepository: UserRepositoryProtocol {
func fetchUser (completion: (User) -> Void) {
}
}
I'm working in a fresh Kotlin Multiplatform mobile project, and I am having trouble implementing a Kotlin interface into a Swift class.
Here is my setup:
From kotlin common (shared) module:
interface LocalUserSource {
suspend fun saveUser(user: User): Boolean
suspend fun readUser(): User?
}
Implementing the protocol in Swift (I believe the protocol is generated by Kotlin/Native):
class DBUserSource : LocalUserSource {
func readUser(completionHandler: #escaping (common.User?, Error?) -> Void) {
// read user from core data
}
func saveUser(user: common.User, completionHandler: #escaping (KotlinBoolean?, Error?) -> Void) {
// save user with core data
}
}
The Xcode project is able to see the generated common framework, and I am able to jump to class / protocol definitions within the framework
But building the Xcode project continually results in this error:
Type 'DBUserSource' does not conform to protocol 'LocalUserSource'
When I use the "Fix" option in Xcode, it continually duplicates the method over and over and shows the same error. I've tried everything to clean both android studio (where I'm running the gradle build) and Xcode.
What's odd is, I've seen this work. I've saved and read users to core data, but today I cannot get the iOS side of things to work. Just wondering if anyone has experienced anything similar, and has any pointers.
Also here is the objective-c definition from the common framework:
__attribute__((swift_name("LocalUserSource")))
#protocol CommonLocalUserSource
#required
- (void)readUserWithCompletionHandler:(void (^)(CommonUser * _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("readUser(completionHandler:)")));
- (void)saveUserUser:(CommonUser *)user completionHandler:(void (^)(CommonBoolean * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("saveUser(user:completionHandler:)")));
#end;
suspend fun readUser(): User? is a nullable in your Kotlin code, whereas you're using a non-nullable/non-optional type in the Swift equivalent function signature:
func readUser(completionHandler: #escaping (common.User, Error?) -> Void) {
// read user from core data
}
// The above should be
func readUser(completionHandler: #escaping (common.User?, Error?) -> Void) {
// read user from core data
}
So I finally figured it out. I had a generic Result class in my common module that looked like this:
sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(private val exception: Throwable, val message: String? = exception.message) : Result<Nothing>()
inline fun <T : Any> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)
return this
}
inline fun <T : Any> Result<T>.onError(action: (Error) -> Unit): Result<T> {
if (this is Error) action(this)
return this
}
Once I removed this, I no longer saw the implementation error in the Swift code and the project ran. Honestly, no idea why. I assume something with generics and Kotlin/Native. But, if anyone has any idea, I'd love to know!
I would like to create a generic protocol for a data fetching service like this:
protocol FetchDataDelegate: AnyObject {
associatedtype ResultData
func didStartFetchingData()
func didFinishFetchingData(with data: ResultData)
func didFinishFetchingData(with error: Error)
}
class Controller: UIViewController, FetchDataDelegate {
func didStartFetchingData() {
//...
}
func didFinishFetchingData(with data: ResultData) {
//...
}
func didFinishFetchingData(with error: Error) {
//...
}
}
class NetworkManager {
weak var delegate: FetchDataDelegate?
//...
}
The Controller class would have a reference to a NetworkManager instance, and through this, I would like to start the network operations. When the operation is ended I would like to call the appropriate delegate function to pass the result back to the controller. But with this setup I got the following error:
Protocol 'FetchDataDelegate' can only be used as a generic constraint because it has Self or associated type requirements
The question is what should I do to use this generic protocol as a variable type? Or if is not possible what would be the correct way? Thanks!
While this is closely related to the question David Smith linked (and you should read that as well), it's worth answering separately because it's a different concrete use case, and so we can talk about it.
First, imagine you could store this variable. What would you do with it? What function in NetworkManager could call delegate.didFinishFetchingData? How would you generate the ResultData when you don't know what it is?
The point is that this isn't what PATs (protocols with associated types) are for. It's not their goal. Their goal is to help you add extensions to other types, or to restrict which kinds of types can be passed to generic algorithms. For those purposes, they're incredibly powerful. But what you want are generics, not protocols.
Instead of creating delegates, you should use generic functions to handle the result of a specific call, rather than trying to nail down each view controller to a specific result type (which isn't very flexible anyway). For example, in the simplest, and least flexible way that still gives you progress reporting:
struct APIClient {
func fetch<Model: Decodable>(_: Model.Type,
with urlRequest: URLRequest,
completion: #escaping (Result<Model, Error>) -> Void)
-> Progress {
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, _, error) in
if let error = error {
completion(.failure(error))
}
else if let data = data {
let decoder = JSONDecoder()
completion(Result {
try decoder.decode(Model.self, from: data)
})
}
}
task.resume()
return task.progress
}
}
let progress = APIClient().fetch(User.self, with: urlRequest) { user in ... }
This structure is the basic approach to this entire class of problem. It can be made much, much more flexible depending on your specific needs.
(If your didStartFetchingData method is very important, in ways that Progress doesn't solve, leave a comment and I'll show how to implement that kind of thing. It's not difficult, but this answer is pretty long already.)
Possible duplicate of this
In summary, you cannot use generic protocols as variable types, it would have to be leveraged as a generic constraint since you wouldn't know the type of ResultData at compilation time
I have a several protocols set up in my framework to deal with resources. In one of the protocols, I have set up an extension to provide a default implementation for a decode function. It's simpler to show the code and what happens (see calls to fatalError). There's a lot more code in the actual implementation, but this illustrates the issue:
This is the "base" protocol:
public protocol Resourceful {
associatedtype AssociatedResource
typealias ResourceCompletionHandler = (AssociatedResource?, Error?) -> Void
func fetch(_ completion: #escaping ResourceCompletionHandler)
}
This is a generic, concrete implementaion of Resourceful:
open class WebResourceApiCall<Resource>: Resourceful {
public typealias AssociatedResource = Resource
public typealias FetchedResponse = (data: Data?, urlResponse: URLResponse?)
public init() {
}
public func fetch(_ completion: #escaping ResourceCompletionHandler) {
try! decode(fetched: (data: nil, urlResponse: nil))
}
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("It ends up here, but I don't want it to!")
}
}
extension WebResourceApiCall where Resource: Decodable {
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("This is where I want it to go...")
}
}
This is how I'm attempting to use it:
public struct Something: Decodable { }
var apiCall = WebResourceApiCall<Something>()
apiCall.fetch { _, _ in } // Implictly calls decode... but not the decode I expected it to! See fatalError() calls...
Instead of calling decode on the extension, like I hoped it would, the "default" decode method with no constraints is always called.
Why doesn't this work the way I expect it to?
Thanks in advance!
Swift is a statically dispatched language, thus the address of the decode() function to be called is computed at compile time, and because the call happens inside the base definition of the class, the compiler picks the original implementation.
Now, if you call the method from a place where the compiler has enough information to pick the implementation you need, it will work:
var apiCall = WebResourceApiCall<Something>()
try apiCall.decode(fetched: (nil, nil))
The above code will call the method from the specialized extension, as at this point the compiler is a better position to know that it has a more specialized implementation to call.
It should be possible to achieve the behaviour you need if you move the decode() method in the dynamic dispatch world - i.e. at the protocol level.
I've run into an issue when generating code coverage with Xcode for view models in an MVVM environment.
Our basic setup is the view controller makes requests to the view model, which in turn calls methods on a data manager that talks to web services.
I came up with what I thought was a reasonably elegant way to test the view models by creating a fake data manager that subclasses the actual data manager and overrides the function called by the VM.
The problem is that for this to work, the VM must be part of the app target and the test target. An apparent side effect of this is that code coverage is not generated for items belonging to two or more targets, even though the unit tests pass. Code coverage is enabled in the project.
Here is a excerpted view model:
import Foundation
class BoosViewModel: BaseViewModel {
convenience override init() {
self.init(dataManager: BoosDataManager(), andModel: nil)
}
func getUnlinkedBoos(_ cardType: CardType) {
(dataManager as! BoosDataManager).getUnlinkedBoos(cardType) { result, error in
...stuff happens here...
}
}
}
... and the data manager
class BoosDataManager: DataManager {
static let SharedInstance: BoosDataManager = {
var manager = BoosDataManager()
return manager
}()
func getUnlinkedBoos(_ cardType: CardType = .loyalty, completion: #escaping ((_ result: BoosModel?, _ error: NSError?) -> Void)) {
...stuff happens here...
}
}
...and the test
class BoosViewModelTests: XCTestCase {
func testGetUnlinkedBoosHappyPath() {
class FauxDataManager: BoosDataManager {
override func getUnlinkedBoos(_ cardType: CardType = .loyalty, completion: #escaping ((_ result: BoosModel?, _ error: NSError?) -> Void)) {
...stuff happens here...
}
}
let viewModel = BoosViewModel()
let dataManager = FauxDataManager()
viewModel.dataManager = dataManager
viewModel.getUnlinkedBoos(.loyalty)
XCTAssertTrue(testObserver.updated)
XCTAssertEqual(testObserver.newViewModel.getBoos().count, 1)
}
}
As I noted earlier the unit tests in this scenario complete successfully, but unit coverage does not get generated.
I have older tests where I actually created an external fake data manager class that was used by the test, the class under test is not part of the test target, and coverage works fine.
The drawback to that is that I have to create multiple data managers to handle specific cases for its returns. If I can't encapsulate the classes, I would need to create a bunch of swift data managers, one for each scenario.
That's why I came up with the internal class.
Now, the problem comes in if I remove the view model under test from the testing target. After doing this, I add #testable import BoosApp to the unit test so that the view model under test can be resolved. When I do this, I get the following error:
Could not cast value of type 'BoosTests.BoosViewModelTests.(testGetUnlinkedBoosHappyPath () -> ()).(FauxDataManager #1)' (0x11f673d18) to 'Boos.BoosDataManager' (0x10444b128).
Aug 30 20:43:01 Pay[19025] : Could not cast value of type 'BoosTests.BoosViewModelTests.(testGetUnlinkedBoosHappyPath () -> ()).(FauxDataManager #1)' (0x11f673d18) to 'Boos.BoosDataManager' (0x10444b128).
I'm not sure what I'm missing. Is there a way to make this scenario work, or am I stuck creating multiple data managers outside of the test code?
Ultimately, I figured out the main issue was that the view model and data manager had somehow gotten added to the test target. After removing them from the test target I was able to make a couple of minor changes and everything is running fine. FYI.