iOS - Error with Protocols and generics when used as a dependency - ios

I have a protocol ContainerService whose sessionService object is used in our network layer. I'm trying to inject the sessionService into an object that encapsulates the network layer. I used a base protocol to avoid the error
Protocol `SessionService` can only be used as a generic constraint because it has Self or associated type requirements
This issue was fixed by adding
var sessionService: SessionServiceBase { get set }
as a requirement for ContainerService
However when I try to use sessionService as a property of TestClass, I come across the same error. Also I ran into another error
Member connectSession cannot be used on value of Protocol typeSessionService; use a generic constraint instead
Here is a sample code
protocol NetworkService: AnyObject {
var sessionService: SessionServiceBase { get set }
}
protocol SessionServiceBase: AnyObject { }
protocol SessionService: SessionServiceBase {
associatedtype T: Tokenable
func connection(credential: T)
}
protocol Tokenable {
var token: String { get set }
var key: String { get set }
}
struct CustomToken: Tokenable {
var token: String
var secretKey: String
}
class CustomSessionService: SessionService {
func connection(credential: CustomToken) {
print(credential.token)
}
}
class CustomNetworkService: NetworkService {
var sessionService: SessionServiceBase = CustomSessionService()
}
class ConsumerClass {
var networkService: NetworkService?
init(networkService: NetworkService) {
self.networkService = networkService
}
func test(){
let tokens = CustomToken(token: "", key: "")
guard let sessionService: SessionService = networkService?.sessionService as? SessionService else {
return
}
sessionService.connection(credential: tokens)
}
}
Is there a work around this generic error issue? If this code is run on a playground there will be two error inside test func.
1.
Protocol SessionService can only be used as a generic constraint because it has Self or associated type requirements
2.
Member `connection` cannot be used on value of protocol type `SessionService`; use a generic constraint instead.

The SessionService protocol can only be used a generic type constraint because the associated type implies that there is not one specific type of SessionService but any number, analogous to how there is not one Array type in Swift. See my answer here for some more details about this.
For instance, we could not guarantee that the sessionService instance actually connected with a CustomToken because its associated type is not specified. Hence, Swift does not allow it.
There are some alternatives though. At some point you have to make something concrete. To implement what you may be looking for with generics, observe the following:
// Protocols
protocol SessionService: AnyObject {
associatedtype T: Tokenable
func connection(credential: T)
}
protocol NetworkService: AnyObject {
associatedtype Service: SessionService
var sessionService: Service { get set }
}
protocol Tokenable {
var token: String { get set }
var key: String { get set }
// Provide an initializer to require that a token can be made in this way
init(token: String, key: String)
}
// Custom types
class CustomSessionService: SessionService {
func connection(credential: CustomToken) {
print(credential.token)
print(credential.doSomething())
}
}
class CustomNetworkService: NetworkService {
var sessionService: CustomSessionService = CustomSessionService()
}
struct CustomToken: Tokenable {
var token: String
var key: String
var secretKey: String = ""
init(token: String, key: String) {
self.token = token
self.key = key
}
var myStr: String = "aaa"
func doSomething() -> String { myStr }
}
// Type erasure
class AnyNetworkService<ServiceType>: NetworkService where ServiceType: SessionService {
typealias Service = ServiceType
var sessionService: ServiceType
init(service: ServiceType) { self.sessionService = service }
convenience init<T: NetworkService>(networkService: T) where T.Service == ServiceType { self.init(service: networkService.sessionService) }
}
// Combines everything
class ConsumerClass<Service: SessionService> {
var networkService: AnyNetworkService<Service>?
init(networkService: AnyNetworkService<Service>) {
self.networkService = networkService
}
func test() {
let tokens = Service.T(token: "asd", key: "fff") // create a token for the specific Service passed in
guard let sessionService = networkService?.sessionService else { return }
sessionService.connection(credential: tokens)
}
}
// Example
let networkService = CustomNetworkService()
let consumer = ConsumerClass<CustomSessionService>(networkService: .init(networkService: networkService))
consumer.test()
The last line will call connection(credential:) in CustomSessionService.
The only potential drawback is that there is no longer a single ConsumerClass but instead many. Also, a Token created in test() cannot have its other properties assigned for the its type is not known to you. However, given its flexibility for using different Network and SessionService concrete types, it may be of use.

Related

iOS - How to avoid type casting with protocols Swift

Is there a way to avoid type casting when protocols are used. For example:
protocol SomeService: AnyObject {
func process(token: Token)
}
protocol Token {
var token: String { get set }
}
class TestService: SomeService {
func process(token: Token) {
guard let token = token as? TestToken else {
return
}
// Do something with TestToken
}
}
class TestToken: Token {
var token: String = "tokenValue"
var key: String = "keyValue"
}
If there are more classes that are to conform to SomeService protocol with their own Token conformed objects and additional values (like key), I don't want to type cast for each class.

Swift protocol with associatedtype as a parameter type

So here I have this protocol
public protocol UseCase {
associatedtype ResponseType
associatedtype Parameters
func build(params: Parameters) -> Single<ResponseType>
}
public extension UseCase {
func execute(params: Parameters) -> Single<ResponseType> {
return build(params: params)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: DispatchQoS.background))
.observeOn(MainScheduler.instance)
}
}
And I have a Struct that implement the UseCase protocol like this
public struct CreateNewAccount: UseCase {
private let repository: AuthRepository
public init(repository: AuthRepository) {
self.repository = repository
}
public func build(params: Params) -> Single<User> {
return repository.register(params: params)
}
public struct Params: RequestParams {
...
}
}
And I want to use this CreateNewAccount on another class, but I don't want to directly use CreateNewAccount and I want to pass it as a UseCase instead, because since It's a protocol It can be easily mocked for testing.
But when I do something like this
class RegisterViewModel: ViewModel {
private let createNewAccount: UseCase // Error on this line
init(createNewAccount: UseCase) { // Error on this line
self.createNewAccount = createNewAccount
}
}
That gave me an error like this
Error:(34, 35) protocol 'UseCase' can only be used as a generic constraint because it has Self or associated type requirements
So, is there's something that I can change from my code to make this kind of case works? Thanks in advance.
You cannot use protocols with associated type as a field.
You have to use them only as an implementation of classes. In most cases, those classes should are generic.
For instance, this code is allowed:
public struct CreateNewAccount<T, K>: UseCase {
public typealias ResponseType = T
public typealias Parameters = K
}
and so,
private let createNewAccount: CreateNewAccount<YouClass1,YouClass2>
or wrap it somehow by another protocol.

How to determine the generic type from protocol implementation

I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
extension Test {
func returnSomething() -> T {
let valueToReturn = readValueFromPLISTthatCanReturnAStringOrDictionary() as T
return valueToReturn
}
}
So finally I have 2 clases, TestString and TestDictionary that both implements Test protocol and I want to indicate the T parameter and I want to use the default implementation. How I do this?
class TestString: Test {}
class TestDictionary: Test { }
class TestString: Test where Test.T = String or similar?
I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
No problem. Let's write that down.
enum StringOrDictionary {
case string(String)
case dictionary([String: String])
}
protocol Test {
func returnSomething() -> StringOrDictionary
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
Sounds good. I'll assume that readValueFromPLISTthatCanReturnAStringOrDictionary() actually returns Any, since that's what is returned by propertyList(from:).
extension Test {
func returnSomething() -> StringOrDictionary {
let value = readValueFromPLISTthatCanReturnAStringOrDictionary()
switch value {
case let string as String: return .string(string)
case let dictionary as [String: String]: return .dictionary(dictionary)
default: fatalError() // Or perhaps you'd like to do something else
}
}
}
It'd probably be nice to name your type something more meaningful than StringOrDictionary, but other than that, it should be pretty straightforward. Just make a type that means what you say. You want a type that means "OR" and that is an enum. (If you want a type that means "AND" that's a struct BTW.)
Regarding your answer, this isn't legal:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
The way to define your T is to implement the required method.
class RandomClass: Test {
func returnSomething() -> String {
return ""
}
}
If you wanted to share some common code, then you can attach that as an extension rather than a default implementation. You could write a returnString() method and call it from the RandomClass.returnSomething(). This is all very useful in some cases, but I definitely wouldn't use it in this case. You don't mean "returns any possible type (T)." You mean "returns one of two possible types" and that's an enum, not a generic.
Update: Apparently they've added a new feature that they've talked about but I thought wasn't in yet. You could now implement RandomClass this way:
class RandomClass: Test {
typealias T = String
}
(Which is a very nice new feature, even if it's not a good answer for this problem.)
Here's a solution to your immediate problem:
Create 2 subtypes of your protocol, each with a different definition of the associated type, and a different default implementation. You select which default implementation you'd like your classes to use by picking between the 2 sub types.
The next issue here is that [String: String] isn't Hashable. This is due to a lack of support for conditional conformances (e.g. the ability to express that a Dictionary is Hashable iff the keys and values are both Hashable), one of Swift's largest downfalls, IMO. You'll probably want to use the type erasing wrapper AnyHashable.
protocol ResultProvider {
associatedtype Result: Hashable
func getResult() -> Result
}
protocol StringResultProvider: ResultProvider {
typealias Result = String
}
extension StringResultProvider {
func getResult() -> String {
return "A string result"
}
}
protocol IntResultProvider: ResultProvider {
typealias Result = Int
}
extension IntResultProvider {
func getResult() -> Int {
return 123
}
}
class TestIntResult: IntResultProvider {}
class TestString: StringResultProvider {}
print(TestString().getResult())
print(TestIntResult().getResult())
// protocol DictionaryResultProvider: ResultProvider {
// typealias Result = [String: String]
// }
// extension DictionaryResultProvider {
// func getResult() -> [String: String] {
// return ["A dictionary": "result"]
// }
// }
// class TestDictionaryProvider: DictionaryResultProvider {}
You need to specify the typealias when you extend the class, like so:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
extension String: Test {
typealias T = Int
}
func def() -> Int {
return 6
}
extension Test {
func returnSomething() -> T {
return def() as! Self.T
}
}
"".returnSomething()
6
However, I couldn't find a way to do it without force casting.
The only working solution is made the generic in the function and specify the variable type when calling the function. I was wondering if i could specify the T type when i implement the protocol in the class, similar like this:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
But previous example just don't work, so an alternative could be this:
protocol Test {
func doSomething<T>() -> T
}
extension Test {
func doSomething<T>(key: String) -> T {
return returnDictOrStringFromPLIST(key: key) as! T
}
}
class TestString: Test {
func getValue() {
let bah: String = doSomething()
}
}
class TestDict: Test {
func getValue() {
let bah: [String: String] = doSomething()
}
}

Swift 2 - Protocol conforming to Equatable issue

I have an issue with a protocol I've defined below. I've got two requirements:
I'd like to be able to use the protocol Peer as a type in other classes while keeping the concrete class private.
I'd like to store the protocol in an array and be able to determine the index of an instance.
In order to satisfy the second point, I need to make the protocol conform to the Equatable protocol. But when I do that, I can no longer use Peer as a type, since it needs to be treated as a generic. This means I cannot have the concrete implementation private anymore, and requirement 1 is broken.
Wondering if anyone else has encountered this problem and gotten around it somehow. Maybe I'm misinterpreting the error I'm getting at indexOf...
Group.swift
import Foundation
class Group {
var peers = [Peer]()
init() {
peers.append(PeerFactory.buildPeer("Buddy"))
}
func findPeer(peer: Peer) -> Bool {
if let index = peers.indexOf(peer) {
return true
}
return false
}
}
Peer.swift
import Foundation
protocol Peer {
var name: String { get }
}
class PeerFactory {
static func buildPeer(name: String) -> Peer {
return SimplePeer(name: name)
}
}
private class SimplePeer: Peer {
let name: String
init(name: String) {
self.name = name
}
}
Error at indexOf if Peer is not Equatable:
cannot convert value of type 'Peer' to expected argument type '#noescape (Peer) throws -> Bool'
So I found a solution to get around the Equatable requirement by extending CollectionType to define a new indexOf for elements are of Peer type, which takes advantage of the other closure-based indexOf. This is essentially a convenience function which saves me from using the closure indexOf directly. Code below:
extension CollectionType where Generator.Element == Peer {
func indexOf(element: Generator.Element) -> Index? {
return indexOf({ $0.name == element.name })
}
}
This of course assumes everything I need to test equality can be obtained from the Peer protocol (which is true for my specific use case).
EDIT: Update for Swift 3:
extension Collection where Iterator.Element == Peer {
func indexOf(element: Iterator.Element) -> Index? {
return index(where: { $0.name == element.name })
}
}
I would suggest you use public super class, so the class can conform to Equatable
class Peer: Equatable {
// Read-only computed property so you can override.
// If no need to override, you can simply declare a stored property
var name: String {
get {
fatalError("Should not call Base")
}
}
// should only be called from subclass
private init() {}
}
private class SimplePeer: Peer {
override var name: String {
get {
return _name
}
}
let _name: String
init(name: String) {
_name = name
super.init()
}
}
func == (lhs: Peer, rhs: Peer) -> Bool {
return lhs.name == rhs.name
}
class PeerFactory {
static func buildPeer(name: String) -> Peer {
return SimplePeer(name: name)
}
}

Swift generics - strategies for handling ARC type modifiers?

I am trying to write a simple property observer that can be used to see updates to a type - ie a public form of didSet. The basic implementation was easy enough, but I then wanted to use it for both strong (value and reference) types and weak (reference) types. The motivation for the latter was to serve as a lazy caching strategy wherein a shared resource would remain in use until the last observer freed it, whereupon it would query for the value again - generally from NSURLCache, but otherwise a remote server. In short, I wanted a transparent multi-tiered cache.
I have done things like this in C++, where I had stored either a type, or a type wrapped in smart pointer, through the use of a trait with typedef's for Type, StoredType, etc, so I could pass a trait for value types, or a trait for ref-counted pointer types etc.
From my understanding of Swift though, the unowned and weak modifiers are applied to the property and not to the type per se, so you can't for example do something like:
typealias StorageType = weak T
To work around this limitation, I abstracted my generic type T to always use a storage container, where the container could use either the direct type, or a weak reference to what would have to be a class-based AnyClass type. (This effort itself was cludged by the fact that you can't override assignment operators and that conversion functions were abandoned in the early betas)
Frustratingly, I ran into compiler bugs where it just segfaulted.
Given that I can't be the first person to have tried to solve this type of problem, has anybody found a clean way through it? Obviously the segfault is just another compiler bug, but perhaps there is a simpler solution?
For reference, my code (with the segfault inducing code in comments) is:
public class ObserverSubscription : Hashable {
// public (hashable)
public var hashValue: Int { return token }
// private
private init(token:Int, observable:ObservableProtocol) {
self.token = token
self.observable = observable
}
deinit {
self.observable.unsubscribe(self)
}
private var token:Int
private var observable:ObservableProtocol
}
public func ==(lhs: ObserverSubscription, rhs: ObserverSubscription) -> Bool {
return lhs.hashValue == rhs.hashValue
}
public protocol Storage {
typealias T
typealias StorageType
init(t:StorageType)
var element:StorageType { get set }
}
public class Weak<Type:AnyObject> : Storage {
typealias T = Type
typealias StorageType = T?
public required init(t:StorageType) { element = t }
public weak var element:StorageType
}
public class Strong<Type> : Storage{
typealias T = Type
typealias StorageType = T
public required init(t:StorageType) { element = t }
public var element:StorageType
}
public protocol ObservableProtocol {
func unsubscribe(subscription:ObserverSubscription)
}
public class Observable<T, Container:Storage where T == Container.T> : ObservableProtocol {
public typealias StorageType = Container.StorageType
public typealias Subscription = ObserverSubscription
public typealias ChangeNotifier = (Container.StorageType) -> ()
public init(_ t:Container.StorageType) {
self.value = Container(t:t)
}
public init(_ source:Observable<T, Container>) {
self.value = Container(t:source.value.element)
}
public func subscribe(notifier:ChangeNotifier) {
let subscription = Subscription(token: token++, observable: self)
subscriptions[subscription] = notifier
}
public func unsubscribe(subscription:Subscription) {
subscriptions.removeValueForKey(subscription)
}
public func subscription(notifier:(Container.StorageType) -> ()) -> Subscription {
let subscription = Subscription(token: token++, observable: self)
subscriptions[subscription] = notifier
return subscription
}
public func update(t:Container.StorageType) {
self.value.element = t
}
public private(set) var value:Container { didSet {
for n in subscriptions.keys {
self.subscriptions[n]!(self.value.element)
}}}
private var token:Int = 0
private var subscriptions: [Subscription: ChangeNotifier] = [:]
}
public class ValueObserver<T> : Observable<T, Strong<T>> {
override init(_ t:StorageType) {
super.init(t)
}
}
// dare ye segfault?
//public class WeakObserver<T:AnyObject> : Observable<T, Weak<T>> {
// override init(_ t:StorageType) {
// super.init(t)
// }
//}
My actual code was a little more involved, I derived a lazily loading class from the observer, but this too, induced a segfault:
//public class LazyLoader<T:AnyObject> : Observable<T, Weak<T>> {
//
// override init(_ t:StorageType) {
// super.init(t)
// }
//
// // do lazy loading here
//}
I have tested the observer in isolation, outside of the missing-trait work around, but has been many characters since I tested it. My goal at this point is to find a solution that compiles (and which could hopefully be simpler)
Thanks!

Resources