Skip to main content
add further details for suggested code improvements
Source Link
Skwiggs
  • 166
  • 5

You should separate concerns whenever possible. Since BLE & HTTP requests are quite different, you could write the following protocols:

And since Swift Enums allow associated values, you can always pass in additional parameters, whenever needed (you could have another case case getAuthToken(byUserID: String), which are then available to you in your implementation)

Now, since you have this all neatly separated, I would go further and pass your Request instances to each specific Provider directly. In effect, you are not the one executing a request, a provider is. That means your requests are agnostic to whatever Provider executes them, which will allow you to set up unit testing easier (because then you can write Mock Providers, and still pass them your requests like usual).

So you should probably extend each provider like such:

extend BLEProvider {
    func execute(request: BLERequest) {
        writeValue(code: request.code, 
                   characteristic: request.characteristic,
                   service: request.service)
    }
}

You could write the following protocols:

And since Swift Enums allow associated values, you can always pass in additional parameters, whenever needed (you could have another case case getAuthToken(byUserID: String), which are then available to you in your implementation)

You should separate concerns whenever possible. Since BLE & HTTP requests are quite different, you could write the following protocols:

And since Swift Enums allow associated values, you can always pass in additional parameters, whenever needed (you could have another case case getAuthToken(byUserID: String), which are then available to you in your implementation)

Now, since you have this all neatly separated, I would go further and pass your Request instances to each specific Provider directly. In effect, you are not the one executing a request, a provider is. That means your requests are agnostic to whatever Provider executes them, which will allow you to set up unit testing easier (because then you can write Mock Providers, and still pass them your requests like usual).

So you should probably extend each provider like such:

extend BLEProvider {
    func execute(request: BLERequest) {
        writeValue(code: request.code, 
                   characteristic: request.characteristic,
                   service: request.service)
    }
}
Source Link
Skwiggs
  • 166
  • 5

You could write the following protocols:

protocol Request {
    associatedtype Provider
    var provider: Provider { get }

    func execute() 
}

protocol BLERequest: Request {
    typealias Provider = BLEProvider

    var code: UInt { get }
    var characteristic: CBCharacteristic { get }
    var service: CBService { get }
}

protocol HTTPRequest: Request {
    typealias Provider = NetworkProvider

    var data: Data { get }
    var accessToken: String { get }
    var method: HTTPMethod { get }
    var params: [String:Any]? { get }
}

Then comes the magic; you can write generic extensions on your Request protocol, for each specific Request type.

extension Request where Self: BLERequest {
    func execute() {
        // provider is available since it is a get-able requirement, 
        // and we know it is a BLEProvider instance
        // same goes for all the other get-able vars
        provider.writeValue(data: code, characteristic: characteristic, service: service)
    }
}

extension Request where Self: HTTPRequest {
    func execute() {
        // Again, same thing, we have all vars we need
        provider.performRequest(data: data, token: accessToken, method: method, params: params)
    }
}

Then, when you are implementing a new Request, all you need to do is implement the requirements for your chosen protocol, and everything else will be handled for you :)

Example:

enum MyBLERequest: BLERequest {
    case getStatus
    case download

    // MARK: BLE Request conformance
    var provider: Provider {
        return BLEProvider(... whatever init is needed)
    }

    var code: UInt {
        switch self {
        case .getStatus: return 0x01
        case .download: return 0x02
        // Any further new case will require you to implement the code here, so you have compile-time safety :)
        }
    }

    var characteristic: CBCharacteristic {
        return CBCharacteristic(.. init)
    }

    var service: CBService {
        return CBService(... more init)
    }
}

Then you can simply do MyBLERequest.getStatus.execute(), and it's all done through the protocol extension code.

And since Swift Enums allow associated values, you can always pass in additional parameters, whenever needed (you could have another case case getAuthToken(byUserID: String), which are then available to you in your implementation)