How do you specify a generic property in a protocol where the conforming type specifies the array type?
Info: Lets say we want to create a protocol which defines an array
property where the type of the array is unknown. The conforming types
specify the type of this array. This is a possible solution:
protocol Wallet {
associatedtype Value
var moneyStack: [Value] {get set}
}
struct BitcoinWallet: Wallet {
var moneyStack: [String]
}
struct EthereumWallet: Wallet {
var moneyStack: [Int]
}
The problem with this approach is that we can't access the moneyStack
property:
let bitcoinWallet = BitcoinWallet(moneyStack: ["A","B"])
let etheureumWallet = EthereumWallet(moneyStack: [1,2])
let wallets: [Wallet] = [bitcoinWallet, etheureumWallet]
for wallet in wallets {
print(wallet.moneyStack) // not possible
}
We can try another approach where the type of the array is any:
protocol Wallet {
var moneyStack: [Any] {get set}
}
struct BitcoinWallet: Wallet {
var moneyStack: [Any]
}
struct EthereumWallet: Wallet {
var moneyStack: [Any]
}
This is a possible solution but an array which holds [Any] is too
generic. I can't specify the type. Example:
let bitcoinWallet = BitcoinWallet(moneyStack: ["A","B"])
let ethereumWallet = EthereumWallet(moneyStack: [1,2])
var wallets: [Wallet] = [bitcoinWallet, ethereumWallet]
for wallet in wallets {
print(wallet.moneyStack.count) // this works
print(wallet.moneyStack[0]) // this works
}
let badBitcoinWallet = BitcoinWallet(moneyStack: [12.4, 312312.123]) // BitcoinWallets aren't allowed to hold doubles!
wallets.append(badBitcoinWallet) // This shouldn't be allowed!
The first solution is what I want. The type is specified in every struct but Swift complains I can't use generic constraints. The main problem I want to solve is that I want an array which holds different types of wallets, which all have their own array of a different type.
I thought a protocol would make this easy but it doesn't. Am I doing something wrong with my architecture? Should I even use protocols? Maybe subclassing can solve this?
You can't store 2 objects with different types in array (Wallet<String> and Wallet<Int> are different types).
But you can try something like this:
protocol WalletProtocol {
func printMoneyStack()
}
class Wallet<T> {
var moneyStack: [T] = []
init(moneyStack: [T]) {
self.moneyStack = moneyStack
}
}
class BitcoinWallet: Wallet<String>, WalletProtocol {
func printMoneyStack() {
print(moneyStack)
}
}
class EthereumWallet: Wallet<Int>, WalletProtocol {
func printMoneyStack() {
print(moneyStack)
}
}
let bitcoinWallet = BitcoinWallet(moneyStack: ["A","B"])
let etheureumWallet = EthereumWallet(moneyStack: [1,2])
let wallets: [WalletProtocol] = [bitcoinWallet, etheureumWallet]
for wallet in wallets {
wallet.printMoneyStack()
}
Related
I know comparing protocols doesn't make any sense but my situation is dictated by choices and decisions taken before me.
A table view's data source is an array of RowViewModel.
protocol RowViewModel {}
there's nothing in there (yet) to make it even conform to Equatable.
Then my table has different cells, all of which implement that protocol:
func getCells() -> [RowViewModel] {
var rows = [RowViewModel]()
rows.append(Cell1ViewModel())
rows.append(Cell2ViewModel())
rows.append(Cell3ViewModel())
return rows
}
Cell's view model:
class Cell1ViewModel: RowViewModel {
var cellTitle: String
...
}
This structure is convenient but it now shoots me in the back because I now need to calculate delta to send specific tableView indexes to insert / delete rows. To calculate delta I need RowViewModel to conform to Equatable, which is possible but seems like a workaround that defies the initial point of using this approach. I'd like to do something like this:
let oldList = rows
let newList = getCells()
let deltaAdded = newList.filter { !oldList.contains($0) }.compactMap { newList.firstIndex(of: $0) }
let deltaRemoved = oldList.filter { !newList.contains($0) }.compactMap { oldList.firstIndex(of: $0) }
What is the best practice here? Is there a way to write a comparison function for concrete types conforming to the RowViewModel?
As I told in comment you would have something like:
class CellViewModel1: Equatable {
// classes need explicit equatable conformance.
static func == (lhs: CellViewModel1, rhs: CellViewModel1) -> Bool {
// your implementation
return true
}
}
enum RowViewModel: Equatable {
// enum automatically is Equatable as long as all cases have Equatable associated types
case cell1(CellViewModel1)
}
func test() {
let oldList = [RowViewModel]()
let newList = [RowViewModel]()
let deltaAdded = newList.filter { !oldList.contains($0) }.compactMap { newList.firstIndex(of: $0) }
let deltaRemoved = oldList.filter { !newList.contains($0) }.compactMap { oldList.firstIndex(of: $0) }
}
Notice that both enum and ViewModels must conform to Equatable.
Still not 100% sure if this fits your necessities.
for postdata in postdata {
if index < tableViewItems.count {
tableViewItems.insert(postdata, at: index)
index += adInterval
} else {
break
}
}
I'll need to add both PostData ads and Native Ads on the same AnyObject Var for me to get it to work and I can't find a way to add the Post Data because it says an error appears saying "Argument type 'PostData' expected to be an instance of a class or class-constrained type." Assistance would be very much appreciated, thank you.
edit 1
class Ad {
var postimage: String!
var publisher: String!
var timestring: String!
var timestamp = Date().timeIntervalSince1970
}
class PostDataAd: Ad {
// Declare here your custom properties
struct PostData1
{
var postimage: String
var publisher: String
var timestring : String
var timestamp = Date().timeIntervalSince1970
}
}
class NativeAd: Ad {
// Declare here your custom properties
struct NativeAd
{
var nativeAds: [GADUnifiedNativeAd] = [GADUnifiedNativeAd]()
}
}
My model class to merge both Data into one AnyObject Var
and then trying to append the Data from Firebase by doing this
var ads: [Ad] = [PostDataAd(), NativeAd()]
let postList = PostDataAd.PostData1(postimage: postimage, publisher:
postpublisher, timestring: postid, timestamp: timestamp)
self.ads.insert(postList, at:0)
an error occurs saying Cannot convert value of type 'PostDataAd.PostData1' to expected argument type 'Ad'
I hope I got what you want correctly. So basically you have two objects which you want to store in one array, under AnyObject. If that is correct, I recommend you to go in a bit of different direction. It is a nice example where you can use subclassing. You can declare a class called Ad, where you define the common properties which will be true for both PostDataAds and NativeAds.
class Ad {
// Add here the common elements you want to retrieve from both classes
var name: String = ""
}
After you define your PostDataAds and NativeAds inheriting from Ad:
class PostDataAd: Ad {
// Declare here your custom properties
}
class NativeAd: Ad {
// Declare here your custom properties
}
And if you want to define an array with two types of objects you can go:
let ads: [Ad] = [PostDataAd(), NativeAd()]
When retrieving you can check their type:
if let item = ads.first as? PostDataAd {
// The Ad is a PostDataAd
} else if let item = ad as? NativeAd {
// The Ad is a NativeAd
}
Or at some cases you don't even how to know the exact type, as you can access the properties defined in Ad without checking.
Update:
First of all your PostData1 and Ad objects are the same, you don't need to duplicate them. If you really want to have two classes you can inherit PostData1 from Ad.
class Ad {
var postimage: String
var publisher: String
var timestring: String
var timestamp = Date().timeIntervalSince1970
// You can add timestamp here also if you wish
init(postimage: String, publisher: String, timestring: String) {
self.postimage = postimage
self.publisher = publisher
self.timestring = timestring
}
}
class PostDataAd: Ad {
// Define some custom properties
}
And if you want to append PostData to the [Ad] array, you would do the following:
var ads: [Ad] = []
// Replace with your values
let postList = PostDataAd(postimage: "", publisher: "", timestring: "")
ads.insert(postList, at: 0)
// Appending NativeAd works also
let nativeAdd = NativeAd(postimage: "", publisher: "", timestring: "")
ads.append(nativeAdd)
I have a problem with storing my generic classes in an array. How should I format the type for my array while keeping the reference to the original type (I know I could do var myClasses: [Any] = [] but that wouldn't be helpful when retrieving the variable from my array :(
Example is below:
import UIKit
protocol Reusable { }
extension UITableViewCell: Reusable { }
extension UICollectionViewCell: Reusable { }
class SomeClass<T> where T: Reusable {
init() { }
}
var myClasses: [SomeClass<Reusable>] = []
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
Edit: Just to clarify, I have used the collection and table cells as an example, I am not actually planning on storing them together :)
Edit 2 var myClasses: [SomeClass<Reusable>] = [] generates error: using 'Reusable' as a concrete type conforming to protocol 'Reusable' is not supported
Edit 3 var myClasses: [SomeClass<AnyObject>] = [] generates error: type 'AnyObject' does not conform to protocol 'Reusable'
I think you can create some sort of Holder class that can accept and retrieve your object:
class Holder {
lazy var classes: [Any] = []
func get<T>(_ type: T.Type) -> [T]? {
return classes.filter({ $0 is T }) as? [T]
}
}
And the main part:
let holder = Holder()
holder.classes.append(SomeClass<UITableViewCell>())
if let someTableViewCells = holder.get(SomeClass<UITableViewCell>.self)?.first {
// or filter out again to get specific SomeClass of UITableViewCell
print(someTableViewCells)
}
Or without holder class:
var classes: [Any] = []
classes.append(SomeClass<UITableViewCell>())
if let someTableViewCell = classes.filter({ $0 is SomeClass<UITableViewCell> }).first as? SomeClass<UITableViewCell> {
print(someTableViewCell)
}
You should use array of AnyObject in your case. Because as you know swift is strong typed language and for example
SomeClass<UITableViewCell>
and
SomeClass<UICollectionViewCell>
are different types of objects. As for example Array< Int > and Array< String >, they are both arrays, but still it's a different types of objects. So in this case you'll have to use declaration:
var myClasses: [AnyObject] = []
and check type of object or typecast them every time you'll need.
if (myClasses[0] is SomeClass<UICollectionViewCell>) { do something }
or
if let cell = myClasses[0] as? SomeClass<UICollectionViewCell> { do something }
My suggestion is adding parent protocol SomeClass Container for your SomeClass generic. Then put an array of SomeClass objects inside SomeClass.
protocol Reusable { func cakePlease() }
extension UITableViewCell: Reusable {
func cakePlease() { }
}
extension UICollectionViewCell: Reusable {
func cakePlease() { }
}
protocol SomeClassContainer {
func teaPlease()
func afternoonPlease()
}
class SomeClass<T: Reusable>: SomeClassContainer {
var item: T?
init() { }
func teaPlease() { }
func afternoonPlease() {
teaPlease()
item?.cakePlease()
}
}
var myClasses = [SomeClassContainer]()
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
myClasses[0].teaPlease()
if let item = (myClasses[0] as? SomeClass<UITableViewCell>)?.item {
item.cakePlease()
}
for myClass in myClasses {
if let tableCell = (myClass as? SomeClass<UITableViewCell>)?.item {
tableCell.cakePlease()
} else if let collectionCell = (myClass as SomeClass<UICollectionViewCell>)?.item {
collectionCell.cakePlease()
}
}
myClasses.forEach({ $0.afternoonPlease() })
Generally the way to type your array would be to go as specific as possible whilst still covering all bases.
What I mean by this for example is storing an array of UIViewController objects, even though each will actually be a different type. You wouldn't use Any here because you really don't need to be that general.
In your case, why not use Reusable? Since all your generic classes conform to it anyway, it is as general as you can go whilst still maintaining convenience.
I have an array of EKReminder, like so:
var currentReminders: [EKReminder]? = ....
I want to cast this array to an array of subclasses of EKReminder. Let's say this is the subclass:
import Foundation
import EventKit
class NNReminder: EKReminder {
var additionalNotes: String?
}
How would I cast currentReminders to [NNReminder]? I tried several ways but they all failed.
Provided you are sure that all members of currentReminders are, in fact, NNReminders, you can cast them all like this:
currentReminders = currentReminders.map { $0 as! NNReminder }
Edit: if only some of the reminders are of type NNReminder, or you're not sure of the array's contents, you can use flatMap to remove the nil values:
currentReminders = currentReminders.flatMap { $0 as? NNReminder }
If you are asking how to transform a bunch of objects that were initialized as EKReminder, you should write a custom init in NNReminder that takes an EKReminder, and use this init in the above map method.
I was tired of having cryptic .map clauses all over my code so i wrote a small extension to make things neat:
extension Array{
func cast<T>(type:T.Type? = nil) -> [T]{
return self.map { $0 as! T }
}
}
Example:
class A:B{
var value:String = "default"
init(_ value:String){
self.value = value
}
}
class B{
var someNum:CGFloat = 1
init(){
}
}
var arr1:Array<A> = [A("a"),A("b"),A("c")]
let arr2:Array<B> = arr1.cast(B.self)//neat!
let arr3:Array<B> = arr1.cast()//epic!
NOTE:
the cast method supports protocols as well
Let's say that certain items can appear in a Feed, so long as they implement the necessary properties defined by the Feedable protocol. Let's also say that the Photo object is feed-worthy:
extension Photo: Feedable { }
Is it possible to say that an Array of these photos might also be Feedable?
extension [Photo] : Feedable
Or do I always need some kind of wrapper object, such as a PhotoAlbum, to conform to Feedable?
Edit
To re-iterate, I was curious whether I can make only arrays of Photo objects Feedable. Not making Array of any content type Feedable, not making an array of Feedables itself Feedable (both of which are offered as solutions below if that's what you need).
In other words, a solution (which I doubt exists) would allow me to define a variable of type Feedable with the following outcomes:
var feedable: Feedable
//photo is feedable, so this is fine
feedable = Photo() //ok
//arrays of photos are feedable
let photo1 = Photo()
let photo2 = Photo()
feedable = [photo1, photo2]
//arrays of other things are not
feedable = ["no", "dice"] //nope
//even if the contents of an array are themselves Feedable, that's not sufficient. E.g. Video is Feedable, but Array of Videos is not.
let video1 = Video()
let video2 = Video()
feeble = video1 //fine
feedable = [video1, video2] //nope
Perhaps this gist (which doesn't compile of course) shows the intention more clearly.
You can achieve your goal in this way:
Swift 4:
protocol Feedable {
func foo()
}
extension String: Feedable {
func foo() {
}
}
extension Array: Feedable where Element: Feedable {
func foo() {
}
}
// or in more generic way to support Array, Set and other collection types
extension Collection: Feedable where Element: Feedable {
func foo() {
}
}
If there was an array of Photo and Video,what would you like to be?
1.Every element performs like what they are.
extension Array where Element : Feedable {
func foo() {
if Element.self == Photo.self {
} else {
}
}
}
2.The whole array performs as 'Video'.
extension Array where Element : Photo {
func foo() {
}
}
I think this is currently not possible. In my project I have the same issue with a ModelProducer.
protocol M: ModelType {}
protocol ModelProducerType {
associatedtype M: ModelType
var model: M? { get }
func produce()
}
struct Test: ModelType {}
class TestProducer: ModelProducerType {
var model: Test?
func produce() {
model = Test()
}
}
I use ModelType as a ghost protocol. The problem is I cannot make a model producer that produces multiple ModelTypes, because of the same reason you discovered. The solution in this case was the following:
protocol M: ModelType {}
protocol ModelProducerType {
associatedtype M: ModelType
var model: [M] { get }
func produce()
}
struct Test: ModelType {}
class TestProducer: ModelProducerType {
var model: [Test] = []
func produce() {
model = [Test()]
}
}
This is more flexible from the start. I get rid of the optional variable and single model producers just have one item in the array. Maybe you can use a similar approach.
You can make an array to conform a protocol like this:
typealias PhotoArray = [Photo]
extension PhotoArray: Feedable {}
I didn't try in playground but maybe you can simply make an Array of Feedable:
var myPhotosArray = [Feedable]()
Then everything implementing the Feedable protocol would be allowed in the Array. If you want only a photo array, You can still subclass your Photo object to make a FeedablePhoto object.
Try this in Playground instead of downvoting without even testing.
Seriously 3 downvotes without any reasons and explanations...
import UIKit
protocol Tree: class {
func grow()
}
class BigTree: Tree {
internal func grow() {
print("Big tree growing")
}
}
class SmallTree: Tree {
internal func grow() {
print("Small tree growing")
}
}
class Car {
//not a tree
}
var manyTrees = [Tree]()
manyTrees.append(BigTree())
manyTrees.append(SmallTree())
manyTrees.append(Car()) //This makes an error "Car doesn't conform to expected type 'Tree'"