How do I create classes that conform to protocols with nested PATs? - swift

I'm attempting to use type-erasure to create a Repository protocol that can be conformed to (similar to Swift's AnyCollection). This protocol needs to be wrapped in a type-erased class because it contains a PAT.
However, since this protocol has a nested protocol which also has a PAT, it somewhat complicates things.
Keyable is something that provides a key (which eventually I'd like to ensure is also Hashable... one thing at a time).
Repository is my generalized "container" protocol
AnyKeyable type-erased wrapper for Keyable
AnyRepository type-erased wrapper for Repository
I’ve got a playground snippet that ALMOST compiles:
protocol Keyable {
associatedtype KeyType// where KeyType: Hashable
func key() -> KeyType
}
protocol Repository {
associatedtype DataType: Keyable
func all() -> [DataType]
func get(id: DataType.KeyType) throws -> DataType?
func create(object: DataType) throws -> Bool
func update(object: DataType) throws -> Bool
func delete(object: DataType) throws -> Bool
func clear() -> Bool
}
final class AnyKeyable<T>: Keyable {
private let _key: () -> T
init<U: Keyable>(_ keyable: U) where U.KeyType == T {
_key = keyable.key
}
public func key() -> T {
return _key()
}
}
final class AnyRepository<T: Keyable>: Repository {
private let _all: () -> [T]
private let _get: (_ id: T.KeyType) throws -> T?
private let _create: (_ object: T) throws -> Bool
private let _update: (_ object: T) throws -> Bool
private let _delete: (_ object: T) throws -> Bool
private let _clear: () -> Bool
init<U: Repository>(_ repository: U) where U.DataType == T {
_get = repository.get
_create = repository.create
_delete = repository.delete
_update = repository.update
_clear = repository.clear
_all = repository.all
}
func all() -> [T] {
return _all()
}
func get<K: Keyable>(id: K.KeyType) throws -> T? where T.KeyType: Keyable, T.KeyType == K.KeyType {
let anyKeyable = AnyKeyable(id)
return try _get(anyKeyable)
}
func create(object: T) throws -> Bool {
return try _create(object)
}
func update(object: T) throws -> Bool {
return try _update(object)
}
func delete(object: T) throws -> Bool {
return try _delete(object)
}
func clear() -> Bool {
return _clear()
}
}
final class Contact {
var name: String = ""
var email: String = ""
}
extension Contact: Keyable {
public typealias KeyType = String
public func key() -> String {
return "im the key"
}
}
// Just a dummy class to see
final class ContactRepository: Repository {
typealias DataType = Contact
private var someContacts: [Contact] = []
func all() -> [Contact] {
return someContacts
}
func clear() -> Bool {
someContacts.removeAll()
return someContacts.count == 0
}
func get(id: Contact.KeyType) throws -> Contact? {
return nil
}
func update(object: Contact) throws -> Bool {
return false
}
func create(object: Contact) throws -> Bool {
return false
}
func delete(object: Contact) throws -> Bool {
return false
}
}
// Testing
let i = AnyRepository<Contact>(ContactRepository())
i.all()
i.clear()
The problem with that is the Swift compiler complains that I’m not using K in the method signature of get<K: Keyable>(id: K.KeyType)... but it looks to me like I definitely am.
My thought is that the compiler is complaining because of the DataType.KeyType declaration of get() in the protocol, rather than in the AnyRepository concrete subclass, but I'm uncertain on how to correct this to provide more context for the compiler.
Is there a better way to structure this to allow me to accomplish this pattern? What about also allowing for the first associatedtype in Keyable to become associatedtype KeyType where KeyType: Hashable?
Any help is greatly appreciated,
Thank you!

As Hamish pointed out, there was no need to introduce another generic, K. That was complicating things. Cleaning it all up, this now works without the complication of an AnyKeyable class:
public protocol Keyable {
associatedtype KeyType where KeyType: Hashable
func key() -> KeyType
}
public protocol Repository {
associatedtype DataType: Keyable
func all() -> [DataType]
func get(id: DataType.KeyType) throws -> DataType?
func create(object: DataType) throws
func create(objects: [DataType]) throws
func update(object: DataType) throws
func delete(object: DataType) throws
func delete(objects: [DataType]) throws
func clear()
}
public class AnyRepository<T: Keyable>: Repository {
private let _all: () -> [T]
private let _get: (_ id: T.KeyType) throws -> T?
private let _create: (_ object: T) throws -> Void
private let _createAll: (_ objects: [T]) throws -> Void
private let _update: (_ object: T) throws -> Void
private let _delete: (_ object: T) throws -> Void
private let _deleteAll: (_ objects: [T]) throws -> Void
private let _clear: () -> Void
public init<R: Repository>(_ repository: R) where R.DataType == T {
_get = repository.get
_create = repository.create
_createAll = repository.create
_delete = repository.delete
_deleteAll = repository.delete
_update = repository.update
_clear = repository.clear
_all = repository.all
}
public func all() -> [T] {
return _all()
}
public func get(id: T.KeyType) throws -> T? {
return try _get(id)
}
public func create(object: T) throws {
return try _create(object)
}
public func create(objects: [T]) throws {
return try _createAll(objects)
}
public func update(object: T) throws {
return try _update(object)
}
public func delete(object: T) throws {
return try _delete(object)
}
public func delete(objects: [T]) throws {
return try _deleteAll(objects)
}
public func clear() {
return _clear()
}
}

Related

Defining associated type in sub protocol vs generic type constraint

What is the difference in defining the associated types in a protocol directly that it inherits from another protocol vs using generic type constraints? For example B I get this error in TestB:
Member 'read' cannot be used on value of type 'any RepositoryB'; consider using a generic constraint instead
Could somebody explain the difference? From my understanding this should be the same as it also expects the same implementation from entities conforming to it (see RepositoryAImpl and RepositoryBImpl).
public protocol CRUDRepository {
associatedtype Item
associatedtype ReadInput
func create(_ item: Item) async throws
func read(_ input: ReadInput) -> AsyncThrowingStream<[Item], Error>
func update(_ item: Item) async throws
func delete(_ item: Item) async throws
}
public protocol RepositoryA: CRUDRepository where Item == String, ReadInput == Query {}
public protocol RepositoryB: CRUDRepository {
associatedtype Item = String
associatedtype ReadInput = Query
}
public struct Query {}
struct RepositoryAImpl: RepositoryA {
func create(_ item: String) async throws {
}
func read(_ input: Query) -> AsyncThrowingStream<[String], Error> {
AsyncThrowingStream { continuation in
continuation.yield(["Test"])
}
}
func update(_ item: String) async throws {
}
func delete(_ item: String) async throws {
}
}
struct RepositoryBImpl: RepositoryB {
func create(_ item: String) async throws {
}
func read(_ input: Query) -> AsyncThrowingStream<[String], Error> {
AsyncThrowingStream { continuation in
continuation.yield(["Test"])
}
}
func update(_ item: String) async throws {
}
func delete(_ item: String) async throws {
}
}
struct TestA {
private let repository: any RepositoryA
init(repository: any RepositoryA) {
self.repository = repository
}
func start() -> AsyncThrowingStream<[String], Error> {
repository.read(Query())
}
}
struct TestB {
private let repository: any RepositoryB
init(repository: any RepositoryB) {
self.repository = repository
}
func start() -> AsyncThrowingStream<[String], Error> {
repository.read(Query())
}
}
The problem here is that associatedtype Item = String doesn't enforce comforming type's ConformingType.Item == String. It merely gives a default type for ConformingType.Item, and conforming type is still allowed to override it through typealias Item = ....
If you don't want to use same-type constraint via a where clause, you can use typealias instead:
public protocol RepositoryB: CRUDRepository {
typealias Item = String
typealias ReadInput = Query
}

Swift: Can I have a protocol that inherits from a protocol and constrains it?

Say I have the following protocols:
protocol RateableItem {
var identifier: String { get } // placeholder. This could be a lot of properties
var name: String { get set }
var rating: Int { get set }
}
protocol RateableItemManager {
/// get some objects matching query criteria
func objects(matching query: RateableItemQuery) -> [RateableItem]
/// get a specific object
func object(withID identifier: String) -> RateableItem?
/// persists them
func save(_ object: RateableItem) throws
/// deletes the objects.
func delete(_ objects: [RateableItem])
/// creates a new object.
func create() -> RateableItem
}
and
struct RateableItemQuery {
let searchPredicate: NSPredicate? // nil means all
let sortingBlock: ((RateableItem, RateableItem) throws -> Bool)?
let groupingSpecifier: (() -> String)?
init(matching predicate: NSPredicate? = nil,
sort: ((RateableItem, RateableItem) throws -> Bool)? = nil,
groupBy: (() -> String)? = nil) {
self.searchPredicate = predicate
self.sortingBlock = sort
self.groupingSpecifier = groupBy
}
}
I can now implement a concrete type of this which returns concrete types that conform to the protocol. The concrete types that are returned are irrelevant to the rest of my code because the rest of the code only cares that they conform to a protocol. This enables me to make 'production' and 'dummy' versions of the models.
Is there a way where I can define this more generally, such as:
struct Query<T> {
let searchPredicate: NSPredicate? // nil means all
let sortingBlock: ((T, T) throws -> Bool)?
let groupingSpecifier: (() -> String)?
init(matching predicate: NSPredicate? = nil,
sort: ((T, T) throws -> Bool)? = nil,
groupBy: (() -> String)? = nil) {
self.searchPredicate = predicate
self.sortingBlock = sort
self.groupingSpecifier = groupBy
}
}
such that
struct RateableItemQuery: Query<RateableItem> {}
and
protocol ItemManager<T> {
func objects(matching query: Query<T>) -> [T]
func object(withID identifier: String) -> T?
func save(_ object: T) throws
func delete(_ objects: [T])
func create() -> T
}
and
protocol RateableItemManager: ItemManager<RateableItem>
Because I want to use this API paradigm, but don't necessarily want to constrain anything at the 'base protocol' level, as I would often just be re-writing these method signatures for the various Protocol-types I'd want to work with.
If I'm not mistaken, associated types have to be concrete, making their return types also concrete, and then I can't easily work with protocol types.
Sorry if I didn't speak "canonically". I hope I was able to convey my intentions.
Could this be what upcoming Swift 5.1 is offering in terms of opaque types, returning -> some ProtocolType ?
So it turns out Opaque types aren't the answer either.
I solved my problem by making abstract baseclasses for Managers, the Query and QueryResults were generic structs, and the concrete subclasses for the Managers could take and return protocol-based Data types.
public struct Query<T> {
var searchPredicate: NSPredicate?
var sortingBlock: ((T, T) throws -> Bool)?
var groupingSpecifier: (() -> String)?
var results: QueryResults<T>?
init(matching predicate: NSPredicate?,
sort: ((T, T) throws -> Bool)?,
groupBy: (() -> String)?) {
}
}
public struct QueryResults<T> {
public enum ChangeType: UInt {
case insert = 1
case delete
case move
case update
}
public struct Section<T> {
var items: [T]
var title: String?
}
public var sections: [Section<T>] = []
public func object(at indexPath: IndexPath) -> T? {
return nil
}
}
public class AnyObjectManager<ObjectType> {
public enum Error: Swift.Error {
case abstractImplementationRequiresOverride
}
typealias QueryDidChangeObjectBlock = ((
_ query: Query<ObjectType>,
_ didChangeObject: ObjectType,
_ atPath: IndexPath?,
_ forChangeType: QueryResults<ObjectType>.ChangeType,
_ newIndexPath: IndexPath?) -> Void)
typealias QueryDidChangeSectionBlock = ((
_ query: Query<ObjectType>,
_ didChangeSection: QueryResults<ObjectType>.Section<ObjectType>,
_ atSectionIndex: Int,
_ forChangeType: QueryResults<ObjectType>.ChangeType) -> Void)
/// get some objects matching query criteria. nil means return all
func objects(matching query: Query<ObjectType>?) -> [ObjectType] {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
/// get a specific object
func object(withID identifier: String) -> ObjectType? {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
/// deletes the objects. Does it commit that to disk?
func remove(_ objects: [ObjectType]) {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
/// creates a new object but does not save it.
func create() -> ObjectType {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
/// this is basically to mimic the functionality of a fetched results controller...
func monitorQuery(_ query: Query<ObjectType>,
willChangeBlock: ((_ query: Query<ObjectType>) -> Void)?,
didChangeObjectBlock: QueryDidChangeObjectBlock?,
didChangeSectionBlock: QueryDidChangeSectionBlock?,
didFinishChangesBlock:((_ query: Query<ObjectType>) -> Void)?) {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
/// and this is to stop monitoring that.
func stopMonitoringQuery() {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
public func saveChanges() throws {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
public func discardChanges() throws {
fatalError("Abstract implementation. You need to override this method and provide an implementation!")
}
}

Swift - implementing repository pattern with generics

I have been doing some research into repository pattern as want to use it new project I am working on. However I am having issues getting it working with generics
I have been following this guide here as an example
https://medium.com/#frederikjacques/repository-design-pattern-in-swift-952061485aa
Which does a fairly good job of explaining it. However, the guide leaves off one important detail.. which is using dependancy injection with generics.
In example code, he shows this
class ArticleFeedViewModel {
let articleRepo:ArticleRepository
init( articleRepo:ArticleRepository = WebArticleRepository() ) {
self.articleRepo = articleRepo
}
}
Which works fine if you are not using generics. But once you change ArticleRepository to Repository example... so from
protocol ArticleRepository {
func getAll() -> [Article]
func get( identifier:Int ) -> Article?
func create( article:Article ) -> Bool
func update( article:Article ) -> Bool
func delete( article:Article ) -> Bool
}
to this
protocol Repository {
associatedtype T
func getAll() -> [T]
func get( identifier:Int ) -> T?
func create( a:T ) -> Bool
func update( a:T ) -> Bool
func delete( a:T ) -> Bool
}
I can no longer get the dependancy injection working. So If I were to try re-creating the model shown above.
class WebArticleRepository: Repository {
func getAll() -> [Article] {
return [Article()]
}
func get(identifier: Int) -> Article? {
return Article()
}
func create(a: Article) -> Bool {
return true
}
func update(a: Article) -> Bool {
return false
}
func delete(a: Article) -> Bool {
return true
}
}
class ArticleFeedViewModel {
let articleRepo:Repository
init( articleRepo:Repository = WebArticleRepository() ) {
self.articleRepo = articleRepo
}
}
This no longer works. I now get an error saying
Protocol 'Repository' can only be used as a generic constraint because
it has Self or associated type requirements
Any ideas on what I am doing wrong here. It seems adding associatedType causes this to stop working. I would really like to get this functionality working as I want to be able to inject either local or web based repository pattern depending on current state of the app
Any help would be much appriecated
You need to make everything else generic as well:
protocol Repository {
associatedtype RepositoryType
func getAll() -> [RepositoryType]
func get( identifier:Int ) -> RepositoryType?
func create( a:RepositoryType ) -> Bool
func update( a:RepositoryType ) -> Bool
func delete( a:RepositoryType ) -> Bool
}
class WebArticle { }
class WebArticleRepository: Repository {
typealias RepositoryType = WebArticle
func getAll() -> [WebArticle] {
return [WebArticle()]
}
func get(identifier: Int) -> WebArticle? {
return WebArticle()
}
func create(a: WebArticle) -> Bool {
return true
}
func update(a: WebArticle) -> Bool {
return false
}
func delete(a: WebArticle) -> Bool {
return true
}
}
class ArticleFeedViewModel<T : Repository> {
let articleRepo: T
init( articleRepo: T) {
self.articleRepo = articleRepo
}
}
// you cannot have the optional parameter in the init, instead, you can extract the following line to a method
ArticleFeedViewModel(articleRepo: WebArticleRepository())
In Swift you can't use a protocol with associated types as the type of a property/parameter etc. It's supposed to make your code more type-safe.

Obscure low-level fatal error in Swift

I have a Swift implementation of an Ordered Set that I believe is correct (if not very efficient), since NSOrderedSet won't work with non-ObjC structures. However, when using the class in a multi-threaded app, I occasionally get a very low level crash which reports nothing on the console but "fatal error", and shows this in the debugger:
function signature specialization <preserving fragile attribute, Arg[1] =
[Closure Propagated : reabstraction thunk helper from #callee_owned
(#unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> () to
#callee_owned (#unowned Swift.UnsafeBufferPointer<Swift.UInt8>) ->
(#out ()), Argument Types : [#callee_owned (#unowned
Swift.UnsafeBufferPointer<Swift.UInt8>) -> ()]> of generic
specialization <preserving fragile attribute, ()> of
Swift.StaticString.withUTF8Buffer <A>
((Swift.UnsafeBufferPointer<Swift.UInt8>) -> A) -> A
The code being executed is this:
import Foundation
fileprivate struct OrderedSetElementWrapper<T>: Hashable, Equatable where T: Hashable, T: Equatable {
let value: T
let index: Int
var hashValue: Int { return self.value.hashValue }
}
fileprivate func ==<T>(lhs: OrderedSetElementWrapper<T>, rhs: OrderedSetElementWrapper<T>) -> Bool {
return lhs.value == rhs.value
}
public struct OrderedSet<T> where T: Hashable, T: Equatable {
private var _set = Set<OrderedSetElementWrapper<T>>()
init() {}
init(array: [T]) { array.forEach { self.insert($0) } }
var array: [T] {
// ***** Error intermittently on next line ******
let sortedArray = self._set.sorted { $0.index < $1.index }
// **********************************************
let mapped = sortedArray.map { $0.value }
return mapped
//return self._set.sorted(by: { $0.index < $1.index }).map({ $0.value })
}
var set: Set<T> { return Set(self._set.map { $0.value } ) }
var count: Int { return self._set.count }
func contains(item: T) -> Bool { return self.set.contains(item) }
mutating func insert(_ item: T) {
self._set.insert(OrderedSetElementWrapper(value: item, index: self._set.count))
}
mutating func remove(_ item: T) {
var newSet = Set<OrderedSetElementWrapper<T>>()
for element in self._set {
if element.value != item {
newSet.insert(OrderedSetElementWrapper(value: element.value, index: newSet.count))
}
}
self._set = newSet
}
}
The class works perfectly the vast majority of the time, I'm reasonably sure of the logic, and I simply don't understand the error message! Can anyone explain the error message?
The error is triggered by a call to the .array property. The calling class is
public class XXXItemGroupBase: XXXItemBase {
// ***************************************
// MARK: New properties
// ***************************************
private var _theItems = OrderedSet<XXXItemBase>()
// _theItems is used as a private backing store.
// Do NOT access it directly outside of the class -
// use self.components instead...
// ***************************************
// MARK: Overridden properties
// ***************************************
override public var components: [XXXItemBase] { // The public face of self._theItems
get { return self._theItems.array } // **** CRASH HERE ****
set { self._theItems = OrderedSet(array: newValue) }
}

Swift Partial application of protocol method is not allowed

I get a strange error message from Swift
Partial application of protocol method is not allowed
I try to pass a function into a class. My code looks like follow
Condition.swift
public protocol Condition {
var type: String { get set }
init(conditionJson: JSON)
func getExpression() -> NSPredicate
func getConditionAction(actionHandler: () -> Void)
}
TimerCondition.swift
public class TimerCondition: Condition {
public var type: String
public var seconds: Int
required public init(conditionJson: JSON) {
self.type = conditionJson["conditionType"].stringValue
self.seconds = conditionJson["conditionType"].intValue
}
public func getExpression() -> NSPredicate {
return NSPredicate(format: "1 == 1")
}
public func getConditionAction(actionHandler: () -> Void){
actionHandler()
}
}
Now in here im getting the error
public class ConditionHandler {
var test:((actionHandler: () -> Void) -> Void)
public init(eventResponse:JSON) {
//error happens here
test = condition.getConditionAction
}
If I do the whole thing without a protocol it works.
Can some help me out here?
EDIT
The solution described in Partial application of protocol method is not allowed can't be applied to my problem, as I want to use a parameter