Having problem calling class methods on an array of classes implementing a protocol. Swift think array contains protocols so it gives the following compile error.
Accessing member of protocol type value 'TransactionHandler.Type' is unimplemented.
I DO NOT want to save actual instances in the array
protocol TransactionHandler {
class func canHandleTransaction(transaction: SKPaymentTransaction) -> Bool
func handleTransaction(transaction: SKPaymentTransaction)
}
class InAppPurchaseManager: NSObject, SKPaymentTransactionObserver {
var handlers = [TransactionHandler.Type]()
override init() {
super.init()
// TransactionHandler implementations
// handlers.append(Product1TransactionHandler.self)
// handlers.append(Product2TransactionHandler.self)
// handlers.append(Product3TransactionHandler.self)
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction in transactions {
for handler in handlers {
// Don't want to save instances in the array, that's waste of memory
// Compile errors all over the place, can be run in playground
if handler.canHandleTransaction(transaction as SKPaymentTransaction) {
let handlerInstance = handler()
handlerInstance.handleTransaction(transaction)
}
}
}
}
}
Why did you named your protocol with the same name of your object TransactionHandler ?
1 / suffix your protocolName : TransactionHandlerProtocol or TransactionHandlerDelegate
2 / and your Class TransactionHandler
Edit: Sorry, i read too fast
Edit 2 :
Remove your .Type
var handlers = [TransactionHandler]()
handlers array of class conform to protocol TransactionHandler
Related
Consider a factory method pattern implementation:
import UIKit
protocol TransportProtocol: CustomStringConvertible {
func techReview()
}
// Make default implemetation opposed to #objc optional methods and properties
extension TransportProtocol {
func techReview() {
print("Reviewing \(type(of: self))")
}
var description: String {
"This is a \(type(of: self))"
}
}
final class Car: TransportProtocol {
func changeOil() {
print("Changed oil")
}
}
final class Ship: TransportProtocol {
}
protocol LogisticProtocol {
associatedtype Transport: TransportProtocol
func createTransport() -> Transport
func delivery(from A: Any, to B: Any)
func techRewiew(for transport: TransportProtocol)
}
extension LogisticProtocol {
func delivery(from A: Any, to B: Any) {
print("Moving \(type(of: self)) from \(A) to \(B)")
}
func techRewiew(for transport: TransportProtocol) {
transport.techReview()
}
}
final class RoadLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Car()
}
}
final class SeaLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Ship()
}
}
// Usage:
class Client {
// ...
static func someClientCode<L: LogisticProtocol>(creator: L) -> some TransportProtocol {
let transport = creator.createTransport()
print("I'm not aware of the creator's type, but it still works.\n"
+ transport.description + "\n")
creator.delivery(from: "Source", to: "Destination")
return transport
}
// ...
}
let someTransport = Client.someClientCode(creator: RoadLogistics())
type(of: someTransport.self) // Car
someTransport.changeOil() // Error: Value of type 'some TransportProtocol' has no member 'changeOil'
The question is why I can't call changeOil() on someTransport if the compiler knows it is a Car not just a TransportProtocol.
Are there any benefits we can get from using some directive, not just bare protocol types?
The return type of the method is some TransportProtocol, not Car. So even though the returned instance is of type Car, you cannot call any methods on it that only exist on Car, but not on TransportProtocol.
The runtime knows that the concrete type of someTransport is Car, but the compiler only knows that the return type conforms to TransportProtocol.
If you want to be able to access Car methods on someTransport, you need to downcast it to Car.
if let car = someTransport as? Car {
car.changeOil()
}
Using the some keyword has type-system benefits when used for opaque returns types (introduced in SE-0244 as part of Swift 5.1), since it actually enables returning a protocol with associated types without having to explicitly make the method generic. This is what drives SwiftUI, since it enables you to return some View.
On the other hand, using opaque return types for protocols without associated types holds no benefits, so in your particular example, there's no reason to use it.
public protocol CDViewModel: class {
var cancellableList: [AnyCancellable] { get set }
}
public extension CDViewModel {
func subscribe(_ callback: #escaping (Project) -> Void) {
cancellableList.append( /*...*/)
}
}
I have a protocol that will be used outside of the module. So I have to declare it public.
But I want the class conforming to that to implement cancellableList with private access.
class MyClass: CDViewModel {
private var cancellableList: [AnyCancellable] = [AnyCancellable]()
}
Property 'cancellableList' must be declared public because it matches a requirement in public protocol 'CDViewModel'
Is this possible?
One workaround to this is to create an intermediate object class that can protect access to its variables using fileprivate. You declare that 'wrapper object type' in your MyClass instance thereby conforming to MyClass's required interface.
Now you have full access to the wrapped object (wrappedList) properties from within the protocol extension, but not from outside the module.
CDViewModel.swift
import Combine
class CDViewModelList {
fileprivate var cancellableList: [AnyCancellable] = [AnyCancellable]()
}
protocol CDViewModelProtocol: AnyObject {
var wrappedList: CDViewModelList { get }
}
extension CDViewModelProtocol {
func subscribe(_ callback: Int) {
self.wrappedList.cancellableList.append(/****/)
}
}
MyClass.swift
class MyClass: CDViewModelProtocol {
let wrappedList = CDViewModelList()
func doStuff () {
self.subscribe(24)
self.wrappedList.cancellableList // 'cancellableList' is inaccessible due to 'fileprivate' protection level
}
}
Thanks for the question it brought me to a good read here:
FULL CREDIT
You cannot have some requirements of a protocol being publicly satisfied and hide other ones, you have to have it all or nothing. More, since the protocol extension needs to work with cancellableList, then this property needs to be public on the type that conforms to the protocol.
If you want to hide the cancellableList property, to avoid extrinsic factors messing with it, one approach I can think of is to split the protocol in two, move the cancellableList to the new protocol, and privately declare a property in MyClass.
CDViewModel.swift
public protocol CDViewModel: class {
// other view model requirements
}
public protocol CDViewModelSource {
var cancellableList: [AnyCancellable] { get set }
}
public extension CDViewModelSource {
func subscribe(_ callback: #escaping (Project) -> Void) {
//cancellableList.append( /*...*/)
}
}
MyClass.swift
class MyClass {
private var source = MyClassSource()
func subscribe(_ callback: #escaping (Project) -> Void) {
source.subscribe(callback)
}
}
fileprivate class MyClassSource: CDViewModelSource {
var cancellableList: [AnyCancellable] = []
}
The downside would be that you'd have to talk to the source every time you need to access the list of cancellables.
As an overview, I'm trying to implement a data abstraction layer in Swift. I'm using two database SDKs but I'm trying to be able to isolate their specific APIs from the rest of the system.
I'm trying to implement a factory pattern that will return the correct object based on a protocol conformance of the supplied concrete type. But the compiler is giving me red flags that I can't wrap my head around.
The Thing class is a first class entity that moves freely through the logic and UI layers of the app, and certain objects persist on a Realm store (which shouldn't matter) and have a specific type that is a subclass of the Realm object. That type is returned by a static function that has to be there to conform with the protocol FromDataSourceA.
protocol FromDataSourceA {
static func A_objectType () -> A_Object.Type
}
class MyObject {
...
}
class Thing: MyObject, FromDataSourceA {
static func A_objectType () -> A_Thing.Type
...
}
// Example usage
let target = DataTarget<Thing>(url: "url")
let dataSource = target.dataSourceBuilder()
As you can see, the concrete type Thing conforms to FromDataSourceA and is passed into the DataTarget initializer. DataSourceBuilder needs to be able to check that Thing conforms to FromDataSourceA to be able to return the correct DataSource.
The DataSourceTypeA class looks like
class DataSourceTypeA<T: MyObject & FromDataSourceA>: DataSource<T> {
let db: DatabaseTypeA // All db-specific APIs contained here
override init (target: DataTarget<T>) {
self.db = try! DatabaseTypeA()
super.init(target: target)
}
override func create<T: MyObject & FromDataSourceA> (object: T) {
db.beginWrite()
db.create(T.A_objectType(), value: object.dict()) // <-- T.A_objectType() is what the conformance to FromDataSourceA is needed for
try! db.commitWrite()
}
override func get<T: MyObject & FromDataSourceA>(id: Int) -> T {
...
}
}
class DataSource<T>: AnyDataSource {
let target: DataTarget<T>
init (target: DataTarget<T>) {
self.target = target
}
func create<T> (object: T) { }
func get<T>(id: Int) -> T { fatalError("get(id:) has not been implemented") }
}
protocol AnyDataSource {
func create<T> (object: T)
func get<T> (id: Int) -> T
}
The problem I'm facing right now is when I check that the metatype conforms to FromDataSourceA, the compiler gives me this warning
class DataTarget<T: MyObject> {
...
func dataSourceBuilder () -> DataSource<T> {
if T.self is FromDataSourceA { // <-- Thing.self conforms to FromDataSourceA
// The Problem:
return DataSourceTypeA(target: self) // Generic class 'DataSourceTypeA' requires that 'MyObject' conform to 'FromDataSourceA'
} else {
...
}
}
}
Why won't the compiler let me return the DataSourceTypeA instance with argument self if the generic T passes the conditional statement and is proven as being in conformance with FromDataSourceA?
The problem is that the call
return DataSourceTypeA(target: self)
is resolved at compile time, therefore it does not help to check
if T.self is FromDataSourceA { }
at runtime. A possible solution is to make the conformance check a compile-time check by defining a restricted extension:
extension DataTarget where T: FromDataSourceA {
func dataSourceBuilder() -> DataSource<T> {
return DataSourceTypeA(target: self)
}
}
If necessary, you can add more implementations for other restrictions:
extension DataTarget where T: FromDataSourceB {
func dataSourceBuilder() -> DataSource<T> {
// ...
}
}
or add a default implementation:
extension DataTarget {
func dataSourceBuilder() -> DataSource<T> {
// ...
}
}
For a call
let dataSource = target.dataSourceBuilder()
the compiler will pick the most specific implementation, depending on the static (compile-time) type information of target.
Usecase
I have a superclass (FirebaseObject) with subclasses for most data items in my Firebase (ex: RecipeItem, User). I made a function in the superclass that automatically updates the data that is in the subclass, now I am trying to make a function with closures that get called when the object is updated.
Code
class FirebaseObject {
private var closures: [((FirebaseObject) -> Void)] = []
public func didChange(completion: #escaping (((FirebaseObject) -> Void))) {
// Save closures for future updates to object
closures.append(completion)
// Activate closure with the current object
completion(self)
}
//...
}
This calls the closure with the initial object and saves it for later updates. In my Firebase observer I can now activate all the closures after the data is updated by calling:
self.closures.forEach { $0(self) }
To add these closures that listen for object changes I need to do:
let recipeObject = RecipeItem(data)
recipeObject.didChange { newFirebaseObject in
// Need to set Type even though recipeObject was already RecipeItem
// this will never fail
if let newRecipeObject = newFirebaseObject as? RecipeItem {
// Do something with newRecipeObject
}
}
Question
Is there a way to have the completion handler return the type of the subclass so I don't have to do as? Subclass even though it won't ever fail? I tried to do this with generic type but I can't figure it out and I am not sure if this is the correct solution.
I would like to keep most code in the FirebaseObject class so I don't need to add a lot of code when creating a new subclass.
Edit
Based on this article I tried to add the type when creating a subclass:
class RecipeItem: FirebaseObject<RecipeItem> {
//...
}
class FirebaseObject<ItemType> {
private var handlers: [((ItemType) -> Void)] = []
public func didChange(completion: #escaping (((ItemType) -> Void))) {
//...
This compiles but it crashes as soon as RecipeItem is initialised. I also tried
class RecipeItem: FirebaseObject<RecipeItem.Type> {
//...
}
But this gives an interesting compiler error when I try to access RecipeItem data in didChange closure:
Instance member 'title' cannot be used on type 'RecipeItem'
Ok, so I've been working on this for a day and I have found a way to do it using the method in this answer for the didChange and initObserver functions and taking inspiration from this way of saving data in extensions.
First off, all the functions that need to use the type of the subclass are moved to a protocol.
protocol FirebaseObjectType {}
extension FirebaseObjectType where Self: FirebaseObject {
private func initObserver(at ref: DatabaseReference) {
//...
}
mutating func didChange(completion: #escaping (((Self) -> Void))) {
if observer == nil {
// init Firebase observer here so there will be no Firebase
// observer running when you don't check for changes of the
// object, and so the Firebase call uses the type of whatever
// FirebaseObject this function is called on eg:
// RecipeItem.didChange returns RecipeItem
// and NOT:
// RecipeItem.didChange returns FirebaseObject
initObserver(at: ref)
}
if closureWrapper == nil {
// init closureWrapper here instead of in init() so it uses
// the class this function is called on instead of FirebaseObject
closureWrapper = ClosureWrapper<Self>()
}
// Save closure for future updates to object
closures.append(completion)
// Activate closure with current object
completion(self)
}
}
To save the closures I now use a wrapper class so I can do type checking on that. In FirebaseObject:
class ClosureWrapper<T> {
var array: [((T) -> Void)]
init() {
array = []
}
}
fileprivate var closureWrapper: AnyObject?
Now I can get the closures with the right type in FirebaseObjectType protocol:
private var closures: [((Self) -> Void)] {
get {
let closureWrapper = self.closureWrapper as? ClosureWrapper<Self>
return closureWrapper?.array ?? []
}
set {
if let closureWrapper = closureWrapper as? ClosureWrapper<Self> {
closureWrapper.array = newValue
}
}
}
I can now use didChange on a FirebaseObject subclass without checking its type every time.
var recipe = RecipeItem(data)
recipe.didChange { newRecipe in
// Do something with newRecipe
}
Is it possible to provide confirming protocol in generic functions of another protocol?
I tried make it work like this, but it's impossible, or I made some mistakes.
My code:
protocol DataModelProtocol {
associatedtype ObjectProtocol: Protocol
func fetchObjects<T: ObjectProtocol>() -> [T]?
func fetch<T: ObjectProtocol>(object: T) -> T?
func delete<T: ObjectProtocol>(allObjectOf type: T.Type)
func insert<T: ObjectProtocol>(_ object: T)
func save<T: ObjectProtocol>(_ object: T)
func update<T: ObjectProtocol>(_ object: T)
func delete<T: ObjectProtocol>(_ object: T)
}
Error message:
inheritance from non-protocol, non-class type 'Self.ObjectProtocol'
Image of Xcode error
It works only like this, but I want to make it more flexible:
protocol DataModelProtocol {
typealias ObjectProtocol = NSManagedObject
...
}
This would perhaps be easier is you gave responsibility of the return types to the object classes themselves.
You would need two protocols but it would avoid mixing protocols and generics:
// The first protocol is for the base class of data objects
protocol DataProtocol
{}
// The protocol provides the "typed" equivalents of the model's
// data manipulation methods.
// By using an extension to DataProtocol, this only needs to
// be done once for all models and data objects.
extension DataProtocol
{
static func fetchObjects(from model:DataModelProtocol) -> [Self]?
{ return model.fetchObjects(object: Self.self) as! [Self]? }
static func fetch(from model:DataModelProtocol) -> Self?
{ return model.fetch(object: Self.self) as! Self? }
// ...
}
// The second protocol is for the data models
// It requires implementation of the data manipulation methods
// using the general "DataProtocol" rather than any specific class
// The actual instances it produces must be of the appropriate class
// however because they will be type casted by the DataProtocol's
// default methods
protocol DataModelProtocol
{
func fetchObjects(object:DataProtocol.Type) -> [DataProtocol]?
func fetch(object:DataProtocol.Type) -> DataProtocol?
// ... and so on
}
...
Here is a simple (naive) example of how the protocols can be used. (I intentionally chose not to use core data to illustrate the generality of the solution)
...
// The base class (or each one) can be assigned the DataProtocol
// (it doesn't add any requirement)
class LibraryObject : DataProtocol
{}
class Author: LibraryObject
{
var name = ""
}
class Book: LibraryObject
{
var title = ""
}
// This is a simple class that implements a DataModelProtocol
// in a naive (and non-core-data way)
struct LibraryModel:DataModelProtocol
{
var authors:[Author] = [ Author(), Author() ]
var books:[Book] = [ Book(), Book(), Book(), Book(), Book() ]
func fetchObjects(object: DataProtocol.Type) -> [DataProtocol]?
{
return object == Book.self ? books
: object == Author.self ? authors
: nil
}
func fetch(object:DataProtocol.Type) -> DataProtocol?
{ return nil }
}
...
Using the protocols will be a bit different from your approach because you would be starting from the object classes rather than passing them as parameter to the model
...
var library = LibraryModel()
let allBooks = Book.fetchObjects(from:library) // this almost reads like english
If you want generic functions of another protocol to be conformed, just simply create a associatedType of T that conforms to the protocol, no need for making an extra ObjectProtocol:
protocol DataModelProtocol {
associatedtype T: Protocol
func fetchObjects<T>() -> [T]?
func fetch<T>(object: T) -> T?
func delete<T>(allObjectOf type: T.Type)
func insert<T>(_ object: T)
func save<T>(_ object: T)
func update<T>(_ object: T)
func delete<T>(_ object: T)
}