I have a protocol:
protocol ReduxTransition {}
I have a enums:
enum ProfileTransition: ReduxTransition {
case authorization
...
case showNotification(NotificationType)
}
enum RatingTransition: ReduxTransition {
case pop
...
case showNotification(NotificationType)
}
And I want to do stuff like this to avoid using similar implementations for showing notifications
func processError(performTransition: #escaping (ReduxTransition) -> ()) {
var notification: NotificationType!
performTransition(.showNotification(notification))
}
If anyone is interested, I applied the following solution:
protocol NotificationPresentable {
static func getNotificationTransition(of type: NotificationType) -> Self
}
extension ProfileTransition: NotificationPresentable {
static func getNotificationTransition(of type: NotificationType) -> ProfileTransition {
return .showNotification(type)
}
}
extension RatingTransition: NotificationPresentable {
static func getNotificationTransition(of type: NotificationType) -> RatingTransition {
return .showNotification(type)
}
}
func processError<Transition: NotificationPresentable>(performTransition: #escaping (Transition) -> ()) {
let notification: NotificationType!
...
performTransition(Transition.getNotificationTransition(of: notification))
}
Maybe someone knows how to make this solution even more better?
Related
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!")
}
}
My data access layer consists of a generic Repository protocol
protocol Repository {
associatedtype T
func getAll() -> Promise<[T]>
}
and its concrete implementation:
class FirebaseRepository<T: Model>: Repository {
func getAll() -> Promise<[T]> {
fatalError()
}
}
Basically, Repository can be RestRepository, FirebaseRepository, PlistRepositry etc. Repository is used by the business logic:
/// My business logic
class ModelService<T: Repository> {
private let repository: T
public init(repository: T) {
self.repository = repository
}
}
The problem comes when I'm trying to apply a factory pattern to a repository. Here's what I came in first:
/// Returns a concrete Repository implementation
class RepositoryFactory {
func makeRepository<T: Model>(type: T.Type) -> Repository {
return FirebaseRepository<T>()
}
}
and this definitely gets a compiler error:
Protocol 'Repository' can only be used as a generic constraint because it has Self or associated type requirements
The only viable option I came to is this:
func makeRepository<T: Model>(type: T.Type, U: Repository) -> U {
return FirebaseRepository<T>() as! U
}
but as you understand, the force optional unwrapping is not acceptable in the production code.
How to make protocols with associated types work with factory design pattern?
You can use Type erasure. Here is an example:
protocol CustomProtocol {
associatedtype AssociatedType
func foo(argument: AssociatedType)
func bar() -> AssociatedType
}
If you want to use CustomProtocol directly, you will receive your error:
let array = [CustomProtocol]()
Protocol 'CustomProtocol' can only be used as a generic constraint because it has Self or associated type requirements
So you can make the same trick, as Swift does with their sequences:
public struct AnyCustomProtocol<T>: CustomProtocol {
func bar() -> T {
fatalError("Needs implementation")
}
func foo(argument: T) {
}
}
let array = [AnyCustomProtocol<Any>]() // works fine
Your problem solution in this case will look somehow like this:
class Promise<T> {
}
protocol Model {
}
protocol Repository {
associatedtype T
func getAll() -> Promise<[T]>
}
class FirebaseRepository<T: Model>: AnyRepository<T> {
override func getAll() -> Promise<[T]> {
fatalError()
}
}
class AnyRepository<T>: Repository {
func getAll() -> Promise<[T]> {
fatalError()
}
}
class RepositoryFactory {
func makeRepository<T: Model>(type: T.Type) -> AnyRepository<T> {
return FirebaseRepository<T>()
}
}
__
For further reading you can check this and Official docs on Generics
I have the following protocol:
protocol RESTAPIprotocol {
associatedtype T: Object, Decodable
}
extension RESTAPIprotocol {
func getList(sinceSyncToken: String = "",
pageLimit: Int = 100,
progress: Moya.ProgressBlock? = nil,
completion:#escaping (_ list: [T]?, _ error: AppError?) -> Void) { ... }
and object(s):
final class RLMOrganization: RLMDefaults {
typealias T = RLMOrganization
}
final class RLMProject: RLMDefaults {
typealias T = RLMProject
}
final class RLMLocation: RLMDefaults {
typealias T = RLMLocation
}
wanting to use it like this:
class SyncEngine {
let listCompletionClosure = { (_ list: [T]?, _ error: AppError?) -> Void in ... }
func syncOrganizations() {
// Sync down from server and update our local DB.
organizationsDAL.getList(sinceSyncToken: organizationsDAL.getLastSyncToken(), completion: listCompletionClosure)
}
But get the error:
Which sort of makes sense, but don't understand how I can pass along the generic used as part of RESTAPIprotocol into a generic closure?
The goal is to try to accomplish the following:
func syncOrganizations() {
organizationsDAL.getList(sinceSyncToken: organizationsDAL.getLastSyncToken(), completion: listCompletionClosure)
}
func syncProjects() {
projectsDAL.getList(sinceSyncToken: projectsDAL.getLastSyncToken(), completion: listCompletionClosure)
}
func syncLocations() {
locationsDAL.getList(sinceSyncToken: locationsDAL.getLastSyncToken(), completion: listCompletionClosure)
}
How about change it to"
let listCompletionClosure = { (_ list: [RLMOrganization]?, _ error: AppError?) -> Void in ... }
The completion handle requires a concrete type and you have defined T = RLMOrganization within the context of the RLMOrganization class.
Edit: a closure can’t be generic but a function can:
func listCompletion<T: Decodable>(list: [T]?, error: AppError?) {
// you must cast `list` to a concrete type
}
func syncOrganizations() {
organizationsDAL.getList(sinceSyncToken: organizationsDAL.getLastSyncToken(), completion: listCompletion)
}
func syncProjects() {
projectsDAL.getList(sinceSyncToken: projectsDAL.getLastSyncToken(), completion: listCompletion)
}
func syncLocations() {
locationsDAL.getList(sinceSyncToken: locationsDAL.getLastSyncToken(), completion: listCompletion)
}
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.
I have a function that is only valid to call within the context of another function, so I want swift stop consumers from capturing the inner function:
protocol Foo {
func bar(onlyValidToCallInsideBar: () -> Void)
}
class GoodFooImpl {
func bar(onlyValidToCallInsideBar: () -> Void) {
// do some stuff
onlyValidToCallInsideBar()
// do some more stuff
}
}
class CapturingBadFooImpl {
var badCaptureOfOnlyValidToCallInsideBar: (() -> Void)?
func bar(onlyValidToCallInsideBar: () -> Void) {
badCaptureOfOnlyValidToCallInsideBar = onlyValidToCallInsideBar
}
func fizz() {
badCaptureOfOnlyValidToCallInsideBar!()
}
}
class AsyncBadFooImpl {
func bar(onlyValidToCallInsideBar: () -> Void) {
dispatch_async(dispatch_get_main_queue()) {
onlyValidToCallInsideBar()
}
}
}
class FooConsumer {
func buzz(foo: Foo) {
bar() {
// This should only be called inside bar
}
}
}
I want to prevent impls like CapturingBadFooImpl and AsyncBadFooImpl.
Swift has nested functions. It sounds like you want to make your function nested inside another. Or, if you are creating a framework, you can make your function private. That prevents it from being used outside of its module (the framework).
If you don't mean one of those things you're going to have to explain what you want more clearly.