I'm trying to use protocol in my class here is the code
public protocol Channel {
typealias CallBackType
func onResponse(responseDic: NSDictionary)
func onRequest(responseDic: NSDictionary)
func onBroadcast(responseDic: NSDictionary)
func request(var messageDic: [String:String]) -> Promise<Any>
func broadcast(var messageDic:[String:AnyObject])
func listen(messageDic:[String: AnyObject], callback:CallBackType)
}
Then I have other class where I create the class that use my protocol
public class SocketManager {
var systemChannel:SystemChannel?
public var socket = SocketIOClient(socketURL: hostUrl, options: ["log": false,
"reconnects": true,
"reconnectAttempts": 5,
"reconnectWait": 5,
"connectParams": ["token":tokenSDK]])
public func start_socket() -> Promise<Bool> {
systemChannel = SystemChannel(socket: self.socket)
}
public func getChannel(typeChannel:SOCKET_CHANNELS) -> Channel{
switch typeChannel{
case .SYSTEM:
return systemChannel!
default:
return systemChannel!
}
}
}
Here is the SystemChannel class
public class SystemChannel: Channel {
public typealias CallBackType = [String: AnyObject] -> Void
}
But I get compile error on the getChannel method Protocol 'Channel' can only be used as a generic constraint because it has Self or associated type requirements
Related
I'm trying to make an inheritance generic case and i've been done in Dart and it worked like this:
// DART
class Car<T> implements Vehicle<T> {
EngineInterface? engine;
StorageInterface<T> storage;
Car({
required this.engine,
required this.storage,
});
}
but when i try to make in Swift, it a little bit different:
The error is
Protocol 'StorageProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Here is my other code:
protocol Vehicle {
associatedtype T
func accelerate() -> String
func brake() -> String
func refill(source: T) -> String
}
protocol StorageProtocol {
associatedtype T
func fill(source: T) -> String
func getSource() -> T
}
I tried to refactor the code like this:
class Car<T>: Vehicle where T : StorageProtocol {
var engine: EngineProtocol
var storage: T
init(engine: EngineProtocol, storage: T) {
self.engine = engine
}
func accelerate() -> String {
return ""
}
func brake() -> String {
return ""
}
func refill(source: T.T) -> String {
storage.fill(source: source)
}
}
but, i have to change the Car's generic
let petrolEngine = PetrolEngine()
let tank = Tank()
let car = Car<Tank>(
engine: petrolEngine, storage: tank
)
car.refill(source: Oil())
instead of use Oil inside Car generic
let car = Car<Oil>(
engine: petrolEngine, storage: tank
)
I found the solution,
that we have to make alias and add where clause in generic context
class Car<TypeAlias, EngineAlias: EngineProtocol, StorageAlias: StorageProtocol> : Vehicle
where StorageAlias.T == TypeAlias, EngineAlias.T == TypeAlias {
typealias T = TypeAlias
var engine: EngineAlias
var storage: StorageAlias
init(engine: EngineAlias, storage: StorageAlias) {
self.engine = engine
self.storage = storage
}
func accelerate() -> String {
return "▶️ \(engine.move()) -> Engine Move"
}
func brake() -> String {
return "⏹ Braked"
}
func refill(source: T) -> String {
let fillTheStorage = storage.fill(source: source)
return "💧 \(fillTheStorage)"
}
}
reference: Generic Where Clause
I am working on a project that has a network client that basically follows the below pattern.
protocol EndpointType {
var baseURL: String { get }
}
enum ProfilesAPI {
case fetchProfileForUser(id: String)
}
extension ProfilesAPI: EndpointType {
var baseURL: String {
return "https://foo.bar"
}
}
protocol ClientType: class {
associatedtype T: EndpointType
func request(_ request: T) -> Void
}
class Client<T: EndpointType>: ClientType {
func request(_ request: T) -> Void {
print(request.baseURL)
}
}
let client = Client<ProfilesAPI>()
client.request(.fetchProfileForUser(id: "123"))
As part of tidying up this project and writing tests I have found the it is not possible to inject a client when conforming to the ClientType protocol.
let client: ClientType = Client<ProfilesAPI>() produces an error:
error: member 'request' cannot be used on value of protocol type
'ClientType'; use a generic constraint instead
I would like to maintain the current pattern ... = Client<ProfilesAPI>()
Is it possible to achieve this using type erasure? I have been reading but am not sure how to make this work.
To your actual question, the type eraser is straight-forward:
final class AnyClient<T: EndpointType>: ClientType {
let _request: (T) -> Void
func request(_ request: T) { _request(request) }
init<Client: ClientType>(_ client: Client) where Client.T == T {
_request = client.request
}
}
You'll need one of these _func/func pairs for each requirement in the protocol. You can use it this way:
let client = AnyClient(Client<ProfilesAPI>())
And then you can create a testing harness like:
class RecordingClient<T: EndpointType>: ClientType {
var requests: [T] = []
func request(_ request: T) -> Void {
requests.append(request)
print("recording: \(request.baseURL)")
}
}
And use that one instead:
let client = AnyClient(RecordingClient<ProfilesAPI>())
But I don't really recommend this approach if you can avoid it. Type erasers are a headache. Instead, I would look inside of Client, and extract the non-generic part into a ClientEngine protocol that doesn't require T. Then make that swappable when you construct the Client. Then you don't need type erasers, and you don't have to expose an extra protocol to the callers (just EndpointType).
For example, the engine part:
protocol ClientEngine: class {
func request(_ request: String) -> Void
}
class StandardClientEngine: ClientEngine {
func request(_ request: String) -> Void {
print(request)
}
}
The client that holds an engine. Notice how it uses a default parameter so that callers don't have to change anything.
class Client<T: EndpointType> {
let engine: ClientEngine
init(engine: ClientEngine = StandardClientEngine()) { self.engine = engine }
func request(_ request: T) -> Void {
engine.request(request.baseURL)
}
}
let client = Client<ProfilesAPI>()
And again, a recording version:
class RecordingClientEngine: ClientEngine {
var requests: [String] = []
func request(_ request: String) -> Void {
requests.append(request)
print("recording: \(request)")
}
}
let client = Client<ProfilesAPI>(engine: RecordingClientEngine())
I am trying to work with swift generics, but I am stuck...
It might be impossible to do it this way, but I was hoping someone would have a good suggestion.
So I have this protocol and a type that I want to have internal:
internal protocol ATCoreInstrumentProtocol { // SOME STUFF }
internal typealias AT_1G_WaveInstrumentType = //Somethings that conforms to ATCoreInstrumentProtocol
Then I have this InstrumentType that I want to be public.
The problem here is that ASSOCIATED_INSTRUMENT aka ATCoreInstrumentProtocol
needs to be internal and therefore I cannot use it in this way.
There is not an option to make the ATCoreInstrumentProtocol public.
public protocol InstrumentType {
static var instrumentType: SupportedInstrumentTypes { get }
associatedtype ASSOCIATED_INSTRUMENT: ATCoreInstrumentProtocol
}
public final class InstrumentTypes {
private init() {}
public final class AT_1G_Wave : InstrumentType {
public class var instrumentType: SupportedInstrumentTypes { get { return .wave_1G } }
public typealias ASSOCIATED_INSTRUMENT = AT_1G_WaveInstrumentType
}
}
This is how I want to use it.
internal class ATCoreInteractor<IT: InstrumentType> {
internal var instrumentObservable : Observable<IT.ASSOCIATED_INSTRUMENT> {
return self.instrumentSubject.asObservable()
}
private var instrumentSubject = ReplaySubject<IT.ASSOCIATED_INSTRUMENT>.create(bufferSize: 1)
internal init(withSerial serial: String){
self.scanDisposable = manager
.scanFor([IT.instrumentType])
.get(withSerial: serial)
.take(1)
.do(onNext: { (inst) in
self.instrumentSubject.onNext(inst)
})
.subscribe()
}
And then I have the ATSyncInteractor which is public
public final class ATSyncInteractor<IT : InstrumentType> : ATCoreInteractor<IT> {
public override init(withSerial serial: String) {
super.init(withSerial: serial)
}
}
public extension ATSyncInteractor where IT : InstrumentTypes.AT_1G_Wave {
public func sync(opPriority: BLEOperationPriority = .normal, from: Date, to: Date, callback: #escaping (ATCoreReadyData<AT_1G_Wave_CurrentValues>?, [WaveTimeSeriesCoordinator], Error?) -> Void) {
// DO SOMETHING
}
}
let interactor = ATSyncInteractor<InstrumentTypes.AT_1G_Wave>(withSerial: "12345")
This ended up beeing my solution..
The InstrumentType only contains the SupportedInstrumentTypes.
The ATCoreInteractor Is no longer generic, but the observer is of type ATCoreInstrumentProtocol.
The ATSyncInteractor extensions casts the instrument to its associated type.
I am open to improvements and suggestions.
public protocol InstrumentType : class {
static var instrumentType: SupportedInstrumentTypes { get }
}
public final class InstrumentTypes {
private init() {}
public final class AT_1G_Wave : InstrumentType {
private init() {}
public class var instrumentType: SupportedInstrumentTypes { get { return .wave_1G } }
}
}
public class ATCoreInteractor {
internal var instrumentSubject = ReplaySubject<ATCoreInstrumentProtocol>.create(bufferSize: 1)
internal init(for type: SupportedInstrumentTypes, withSerial serial: String){
self.scanDisposable = manager
.scanFor([type])
.get(withSerial: serial)
.take(1)
.do(onNext: { (inst) in
self.instrumentSubject.onNext(inst)
})
.subscribe()
}
}
public final class ATSyncInteractor<IT : InstrumentType> : ATCoreInteractor {
public init(withSerial serial: String) {
super.init(for: IT.instrumentType, withSerial: serial)
}
}
public extension ATSyncInteractor where IT : InstrumentTypes.AT_1G_Wave {
private func instrument() -> Observable<AT_1G_WaveInstrumentType> {
return self.instrumentSubject.map { $0 as! AT_1G_WaveInstrumentType }
}
public func sync(opPriority: BLEOperationPriority = .normal, from: Date, to: Date, callback: #escaping (ATCoreReadyData<AT_1G_Wave_CurrentValues>?, [WaveTimeSeriesCoordinator], Error?) -> Void) {
}
}
I have a class named UserManager.
public class UserManager{
static let sharedInstance = UserManager()
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, User>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> User?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> User? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = mapJSONToUser(data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
Notice that I have an object called User, and it's used everywhere in this class.
I'd like to have classes called PostManager and AdminManager, which uses the same logic as the class above.
I could simply copy and paste the code above and replace the object User with Post and Admin. But...obviously this is bad practice.
What can I do to this class so that it accepts any resource? Not just User
The most obvious way to do something like this is to embed all of the generic functionality in a generic class, then inherit your UserManager from that:
protocol Managable {
init(json:JSON)
}
public class Manager<T:Manageable> {
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, T>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> T?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> T? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = T(json:data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
class User : Managable {
required init(json:JSON) {
}
}
class UserManager : Manager<User> {
static var instance = UserManager()
}
Now then, any class that implements the Manageable protocol (ie., it has an init(json:JSON) method can have a Manager class variant. Note that since a generic class can't have a static property, that's been moved into the subclass.
Given that inheritance can hide implementation details, if reference semantics are not needed then a protocol + associated type (generics) implementation using structs might be safer and arguably more "Swifty".
Define your protocol with an associated type (Swift 2.2) or type alias (Swift 2.1):
protocol Manager {
associatedtype MyManagedObject // use typealias instead for Swift 2.1
func getResource(id: Int, clientName: String) -> MyManagedObject?
func createResource(data: JSON, clientName: String) -> MyManagedObject?
func releaseResource(id: Int, clientName: String)
}
And then your implementation becomes:
public struct UserManager: Manager {
typealias MyManagedObject = User
func getResource(id: Int, clientName: String) -> User? { ... }
func createResource(data: JSON, clientName: String) -> User? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
And you can further add objects using the same protocol easily, specifying what 'MyManagedObject' should be:
public struct PostManager: Manager {
typealias MyManagedObject = Post
func getResource(id: Int, clientName: String) -> Post? { ... }
func createResource(data: JSON, clientName: String) -> Post? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
I would recommend reading up more on protocols and generics in detail (there are many examples online, Apple's documentation is a good place to start).
I am subclassing a Typhoon assembly such that stubbed implementations are returned, for unit testing purposes.
My assembly looks something like this:
class RealAssembly : TyphoonAssembly {
public dynamic func instanceToStubOut() -> AnyObject {
return TyphoonDefinition.withClass(SomeRealWorldClass.self)
}
public dynamic func instanceToTest() -> AnyObject {
return TyphoonDefinition.withClass(ClassToTest.self, configuration: { (definition : TyphoonDefinition!) -> Void in
definition.useInitializer("initWithObjectToStub:", parameters: { (initializer : TyphoonMethod!) -> Void in
initializer.injectParameterWith(self.instancetoStubOut())
})
})
}
}
My test class is solely for testing the instance of type ClassToTest, and I want to test it with the initializer-injected dependency on the object of type SomeRealWorldClass to be stubbed out. So I subclass RealAssembly so that instanceToStubOut() is overridden to return my stub object.
class MyTestClass : XCTestCase {
var assembly : TestAssembly!
class TestAssembly : RealAssembly {
override dynamic func instanceToStubOut() -> AnyObject {
return TyphoonDefinition.withClass(TestClass.self)
}
}
#objc
class TestClass : NSObject, ClassToStubOut {
func methodToStubOut() { /* do nothing */ }
}
override func setUp() {
super.setUp()
self.assembly = TestAssembly().activate()
}
override func tearDown() {
self.assembly = nil
super.tearDown()
}
func testStuff() {
let testingInstance = self.assembly.instanceToTest()
XCTAssertTrue(testingInstance.doStuff(), "doStuff returns true")
}
}
I expected this to work, but it doesn't. Typhoon seems to inject an uninitialized object instead of ever calling TestAssembly.instanceToStubOut()
Am I doing something wrong? Should I take a different approach?
EDIT: Here is some code you can paste into a Swift Playground that demonstrates the problem. The last line shows c.otherInstance returning nil :
import Typhoon
#objc
class BaseClass : NSObject {
var otherInstance : OtherProtocol!
func doIt() -> String {
return self.otherInstance.doStuff()
}
}
#objc
protocol OtherProtocol {
func doStuff() -> String
}
#objc
class OtherImpl : NSObject, OtherProtocol {
func doStuff() -> String {
return "OtherClass"
}
}
#objc
class StubClass : NSObject, OtherProtocol {
func doStuff() -> String {
return "Stubbed out"
}
}
class BaseAssembly : TyphoonAssembly {
dynamic func baseObject() -> AnyObject {
return TyphoonDefinition.withClass(BaseClass.self,
configuration: { (def : TyphoonDefinition!) -> Void in
def.injectProperty("otherInstance", with: self.otherObject())
})
}
dynamic func otherObject() -> AnyObject {
return TyphoonDefinition.withClass(OtherImpl.self)
}
}
var assembly = BaseAssembly()
assembly.activate()
var b = assembly.baseObject() as! BaseClass
b.doIt()
#objc
class TestAssembly : BaseAssembly {
override func otherObject() -> AnyObject {
return TyphoonDefinition.withClass(StubClass.self)
}
}
var testAssembly = TestAssembly()
testAssembly.activate()
var c = testAssembly.baseObject() as! BaseClass
c.otherInstance // this shouldn't be nil
Edit:
While patching is an option, as outlined in #Herman's answer below, what was attempted in the question is a supported feature, however there was a regression bug preventing it from working correctly.
The regression bug has been fixed in Typhoon 3.2.2 and so now both patching and overriding an assembly are again options for configuring Typhoon for a particular use-case.
Patching
There is a patching feature for this purpose in Typhoon. Look here.
For example:
class StubClass : NSObject, OtherProtocol {
#objc func doStuff() -> String {
return "Stubbed out"
}
}
let assembly = BaseAssembly()
assembly.activate()
let b = assembly.baseObject() as! BaseClass
print(b.doIt())
let testAssembly = BaseAssembly().activate()
let patcher = TyphoonPatcher()
patcher.patchDefinitionWithSelector("otherObject") { () -> AnyObject! in
return StubClass()
}
testAssembly.attachPostProcessor(patcher)
let c = testAssembly.baseObject() as! BaseClass
print(c.doIt())