Protocol with associatedtype conformance with generic gives compiler error - swift

I'm trying to make a DB mock to test some UI implementations, but compiler keeps giving me the following error:
Type 'DBClientMock<T>' does not conform to protocol 'DBClient'
This is my code...
protocol DBClient {
associatedtype Model
func get(id: UUID, completion: #escaping ((Model?, Error?)) -> Void)
func insert(_ model: Model, completion: #escaping (Error?) -> Void)
func delete(_ model: Model, completion: #escaping (Error?) -> Void)
}
final class DBClientMock<T>: DBClient where T: Identifiable, T: Equatable {
typealias Model = T
private let queue: DispatchQueue
private var modelDB = [T]()
enum Error: Swift.Error {
case notFound
}
init(queue: DispatchQueue = DispatchQueue.global()) {
self.queue = queue
}
private func getModel(id: UUID) -> T? {
let results = modelDB.filter({ $0.id as? UUID == id })
guard results.count > 0 else { return nil }
return results[0]
}
// Extension
func get(id: UUID, completion: #escaping ((T?, Error?)) -> Void) {
let record = getModel(id: id)
queue.asyncAfter(deadline: .now() + .milliseconds(1500), execute: {
if let model = record {
completion((model, nil))
} else {
completion((nil, Error.notFound))
}
})
}
func insert(_ model: T, completion: #escaping (Error?) -> Void) {
modelDB.append(model)
queue.asyncAfter(deadline: .now() + .milliseconds(1000), execute: {
completion(nil)
})
}
func delete(_ model: T, completion: #escaping (Error?) -> Void) {
modelDB.removeAll(where: { $0 == model })
queue.asyncAfter(deadline: .now() + .milliseconds(800), execute: {
completion(nil)
})
}
}
XCode: Version 12.4 (12D4e)
Swift: 5.0
What am I doing wrong? Do I have to be more explicit with the generic type in some way? I tried replacing T with Model but had the same result.
Thanks for your help!

It doesn't conform because you declared another Error type inside the class, so everywhere where you use Error in the required methods, it uses DBClientMock.Error instead of the protocol-required Swift.Error.
Either rename DBClientMock.Error to something else, or change the Error in methods to Swift.Error, like below:
// Extension
func get(id: UUID, completion: #escaping (T?, Swift.Error?) -> Void) {
//...
}
func insert(_ model: T, completion: #escaping (Swift.Error?) -> Void) {
//...
}
func delete(_ model: T, completion: #escaping (Swift.Error?) -> Void) {
//...
}

Related

Invalid conversion from throwing function of type XXXX to non-throwing function type XXXX

I am stuck with this situation where I have a custom JSONDecoder struct which contains a private function to decode data, and another function which is exposed, and should return a specific, Decodable type. I would like these functions to throw successively so I only have to write my do/catch block inside the calling component, but I'm stuck with this error on the exposedFunc() function:
Invalid conversion from throwing function of type '(Completion) throws -> ()' (aka '(Result<Data, any Error>) throws -> ()') to non-throwing function type '(Completion) -> ()' (aka '(Result<Data, any Error>) -> ()')
Here is the code:
import Foundation
import UIKit
typealias Completion = Result<Data, Error>
let apiProvider = ApiProvider()
struct DecodableTest: Decodable {
}
struct CustomJSONDecoder {
private static func decodingFunc<T: Decodable>(
_ response: Completion,
_ completion: #escaping (T) -> Void
) throws {
switch response {
case .success(let success):
try completion(
JSONDecoder().decode(
T.self,
from: success
)
)
case .failure(let error):
throw error
}
}
static func exposedFunc(
value: String,
_ completion: #escaping (DecodableTest) -> Void
) throws {
apiProvider.request {
try decodingFunc($0, completion)
}
}
}
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
try CustomJSONDecoder.exposedFunc(value: "test_value") { result in
// Do something with result
}
} catch {
print(error)
}
}
}
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
Thank you for your help
This defines method that takes a non-throwing function:
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
So in all cases, this function must take a Completion and return Void without throwing. However, you pass the following:
apiProvider.request {
try decodingFunc($0, completion)
}
This method does throw (note the uncaught try), so that's not allowed. You need to do something if this fails:
apiProvider.request {
do {
try decodingFunc($0, completion)
} catch {
// Here you must deal with the error without throwing.
}
}
}
Not using concurrency features is making your code hard to understand. Switch!
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
let result = DecodableTest()
// Do something with result
}
}
}
extension DecodableTest {
init() async throws {
self = try JSONDecoder().decode(Self.self, from: await APIProvider.data)
}
}
enum APIProvider {
static var data: Data {
get async throws { .init() }
}
}

Getting error while mocking services in test target

I am trying to write test cases for the test target. The app is running perfectly, but the test case failing with the mocking.
Here is my code, please let me know if there any other solutions. How do I mock services?
Is there any other way to write a test case for the ViewCoontroller initializer?
class NavigationCodeTests: XCTestCase {
var subject: ViewController?
override func setUp() {
self.subject = ViewController(service: MockUserService())
_ = self.subject?.view
}
func test_user_service_not_nil() {
XCTAssertNotNil(self.subject?.service, "User service can't be nil after initialization of ViewController")
}
func test_user_service_should_have_user() {
self.subject?.userViewModel?.user.observe(on: self, observerBlock: { (user) in
XCTAssertNotNil(user?.name, "User service can't be nil after initialization of ViewController")
})
}
}
class MockUserService: UserService {
func fetch(_ completion: #escaping(_ user: User) -> Void) {
completion(User(name: "abc", contact: "124"))
}
}
class UserService: UserServiceDelegate {
func fetch(_ completion: #escaping(_ user: User) -> Void) {
completion(User(name: "Damu", contact: "12"))
}
}
protocol UserServiceDelegate {
func fetch(_ completion: #escaping(_ user: User) -> Void)
}

Having trouble returning a user token from StoreKit as a string

import StoreKit
class AppleMusicAPI {
let developerToken = "ABC123"
func getUserToken() -> String {
var userToken = String()
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
userToken = receivedToken!
}
return userToken
}
}
I'm trying to basically return the userToken by doing AppleMusicAPI().getUserToken() however nothing gets returned (literally just blank/empty).
How can I output the token as a string?
If you take a look a the method signature that you're calling, it looks like this:
open func requestUserToken(forDeveloperToken developerToken: String, completionHandler: #escaping (String?, Error?) -> Void)
Notice the #escaping keyword which indicates that the completion block is not necessarily called right away (asynchronous). To fix your issue, I'd suggest using something like this:
class AppleMusicAPI {
let developerToken = "ABC123"
func getUserToken(completion: #escaping (String?, Error?) -> Void) {
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken, completionHandler: completion)
}
}

How to infer type in static generic method with the type of the caller in Swift

I'm declaring this method on a protocol extention
protocol Storable { ... }
extention Storable {
static func get<T: Decodable>(by identifier: String, completion: #escaping (T?) -> Void)
...
}
Now I'm using the method on a type which implements Storable.
struct User: Storable {
...
}
User.get(by: "userId", completion: { user in
print(user)
}
But the compiler says: Generic parameter 'T' could not be inferred
I want to tell to the compiler "T is the class who calls the static method"
I succeed to compile with :
static func get<T>(by identifier: String, type: T.Type, completion: #escaping (T?) -> Void) where T: Decodable
and
User.get(by: "mprot", type: User.self) { ... }
But it seems redundant :(
I want to tell to the compiler "T is the class who calls the static method"
Assuming you want to apply your get only when T is the class who calls the static method, how is this?
protocol Storable {
//...
}
extension Storable where Self: Decodable {
static func get(by identifier: String, completion: #escaping (Self?) -> Void) {
//...
}
}
struct User: Storable, Decodable {
//...
}
This will be compiled successfully:
User.get(by: "userId", completion: { user in
print(user)
})
T must not be optional. You should have:
static func get<T: Decodable>(by identifier: String, completion: #escaping (T) -> ())
been called
User.get(by: "userId", completion: { (value: User) in ... })

How to get generics with completion and resultType working?

I'm currently writing and database access class for two database APIs (realm and Firestore). With the intention to slim down the code i try to solve the whole thing a little sleaker with generics (#1). Unfortunately, it's not working. Where do I miss the point?
I tried with defining associatedtypes (#2) and setting them within the RealmAccessStragy class. But on this point the compiler returns error if try to access the protocol via the PersistenceController.
I am grateful for any help!
APPROACH #1
enum DataResult<T> {
case success(T)
case failure(Error)
}
protocol DataApiAccess: AnyObject {
func read<U, T>(primaryKey: U, completion: #escaping ((DataResult<T>) -> Void))
}
class RealmAccessStrategy {
...
func read<U, T>(primaryKey: U, completion: #escaping ((DataResult<T>) -> Void)) {
guard let realmObject = realmInstance.object(ofType: realmObjectType, forPrimaryKey: primaryKey) else {
completion(.failure(RealmAccessError.noObject))
return
}
completion(.success(realmObject)) // ERROR: Member 'success' in 'DataResult<_>' produces result of type 'DataResult<T>', but context expects 'DataResult<_>'
}
}
// Later implementation
class PersistenceController {
private let strategy: DataApiAccess
init(use: DataApiAccess) {
self.strategy = use
}
func load<U, T>(primaryKey: U, completion: #escaping ( (DataResult<T>) -> Void ) ) {
strategy.read(primaryKey: primaryKey, completion: completion)
}
}
🆘 ERROR: Member 'success' in 'DataResult<>' produces result of type 'DataResult', but context expects 'DataResult<>'
APPROACH #2
enum DataResult<T> {
case success(T)
case failure(Error)
}
protocol DataApiAccess {
associatedtype ReturnType
func read(primaryKey: PrimaryKeyType, completion: #escaping DataApiHandler<ReturnType>)
}
class RealmAccessStrategy: DataApiAccess {
...
// Typealias
internal typealias ReturnType = Object
func read(primaryKey: Any, completion: #escaping ((DataResult<Object>) -> Void)) {
guard let realmObject = realmInstance.object(ofType: realmObjectType, forPrimaryKey: primaryKey) else {
completion(.failure(RealmAccessError.noObject))
return
}
completion(.success(realmObject))
}
}
class PersistenceController {
private let strategy: DataApiAccess // ERROR: Protocol 'DataApiAccess' can only be used as a generic constraint because it has Self or associated type requirements
init(use: DataApiAccess) {
self.strategy = use
}
...
}
}
🆘 ERROR: Protocol 'DataApiAccess' can only be used as a generic constraint because it has Self or associated type requirements
You cannot set variable generic protocols but you can set methods
Example code below
Create enum for base result:
enum DataResult<T> {
case success(T)
case failure(Error)
}
///Set a protocol generic methods:
protocol DataApiAccess {
func read<T: Codable>(primaryKey: PrimaryKeyType, completion: #escaping (DataResult<T>) -> Void)
}
class RealmAccessStrategy: DataApiAccess {
func read<T: Codable>(primaryKey: PrimaryKeyType, completion: #escaping (DataResult<T>) -> Void) {
// Read data from database
}
}
class NetworkAccessStrategy: DataApiAccess {
func read<T: Codable>(primaryKey: PrimaryKeyType, completion: #escaping (DataResult<T>) -> Void) {
// Get data from request
}
}
class PersistenceController {
private let strategy: DataApiAccess
init(use: DataApiAccess) {
// Set dependency inversion for offline or online state
self.strategy = use
}
func foo() {
// TODO
//strategy.read(primaryKey: <#T##PrimaryKeyType#>, completion: <#T##(DataResult<Decodable & Encodable>) -> Void#>)
}
}
Enjoy!