I am trying to port an existing Swift code to Kotlin and I'd like to use best practice for the following Swift code:
struct Deck {
private(set) var cards: [Card]
var cardsCount: Int {
return self.cards.count
}
init(cards: [Card] = []) {
self.cards = cards
}
mutating func add(card: Card) {
self.cards.append(card)
}
}
The design goals are:
cards property is not modifiable outside of the class so its type should be List<Card>
fun add(card: Card) should modify the internal cards list
Is there a way to achieve this in Kotlin without using two separate properties - one private var mutableCards: MutableList<Card> and one computed property val cards: List<Card> get() = this.mutableCards
I need some best practice for such situation.
Since the read-only List is "under the hood" also a mutable list, you may want to leverage on casting to a MutableList, doing so:
class Card {
}
class Deck(cards:List<Card>){
var cards:List<Card>
init {
this.cards = cards
}
public fun add(card:Card) {
(cards as MutableList<Card>).add(card)
}
}
fun main(args: Array<String>) {
var cards:List<Card> = arrayListOf()
// here I can't modify cards
var deck = Deck(cards)
deck.add(Card())
print(deck.cards.count()) // printing 1
}
to be able to test it, copy and paste here.
Related
I'm new to working with swift and have been converting some ios firebase cordova plugin stuff, but ran into a situation that I don't fully understand regarding arrays. I have two snippets of code, one that works and one that doesn't.
Works
var admobTestDevices: [Any] = []
var randomString: String = "abc123"
#objc(FirebasePlugin)
class FirebasePlugin : CDVPlugin {
func something() {
admobTestDevices.append(randomString)
}
}
Doesn't work
#objc(FirebasePlugin)
class FirebasePlugin : CDVPlugin {
var admobTestDevices: [Any] = []
var randomString: String = "abc123"
func something() {
admobTestDevices.append(randomString)
}
}
The one that doesn't work produces Thread 1: EXC_BAD_ACCESS (code=1, address=0x8) as an error. Why does one work and the other doesn't? What is the proper way to have a mutable array as a class property?
For some reason collections become immutable in the subclass in some cases.
You could fix this two ways:
1. override init() initialise the array then call super.init
class FirebasePlugin : CDVPlugin {
var admobTestDevices: [Any] = []
var randomString: String = "abc123"
override init() {
admobTestDevices = [Any]()
super.init()
}
func something() {
admobTestDevices.append(randomString)
}
}
2. Use the lazy modifier so that the array is initialised when first used.
class FirebasePlugin : CDVPlugin {
lazy var admobTestDevices: [Any] = []
var randomString: String = "abc123"
func something() {
admobTestDevices.append(randomString)
}
}
I’m not trying to store an array directly on my CDVPlugin subclass, but I’m storing a struct Queue<T> that has an Array<T> as a top-level member and it was causing the same error that you saw. My solution was to repeat the array initialization in a pluginInitialize method:
struct Queue<T>: ExpressibleByArrayLiteral {
public private(set) var elements: Array<T>
public init() {
self.elements = []
}
public init(arrayLiteral elements: T...) {
self.elements = elements
}
// ...other members...
}
#objc(MyPlugin) class MyPlugin: CDVPlugin {
var requestQueue: Queue<DownloadRequest> = []
override func pluginInitialize() {
requestQueue = Queue<DownloadRequest>()
}
// ...other members...
}
This sort of code would ordinarily not be necessary go in a custom initializer, but the CDVPlugin source warns against subclassing the initializer and says to use pluginInitialize instead. Cordova is clearly doing something unusual here so I’m inclined to trust them on that.
I have a Protocol called Composite.
This protocol has an array composites: [Composite]
I also have a generic subclass GenericSubclass<T>: Composite
When iterating over the array the best I can come up with looks like this:
for item in composites {
if let item = item as? GenericSubclass<A> {
let sc = SomeOtherClass<A>
} else if let item = item as? GenericSubclass<B> {
let sc = SomeOtherClass<B>
} //and so on...
}
Is there any way to get a hold of GenericSubclass without specifying the Generic? In my use case there is absolutely no need for me to know about the T. I just have to instantiate another class with the same generic type.
Any help is much appreciated.
It's not clear what you're trying to accomplish with the "generic" (pun intended) class names you've chosen. I don't think there's a way to directly accomplish what you want. I.e. you can't just leave it as a generic T because the compiler needs some way to determine what T will be in use at runtime.
However, one way to solve the issue is to hoist the API into the Composite protocol:
protocol Composite {
var composites: [Composite] { get set }
func otherClass() -> OtherProtocol
}
protocol OtherProtocol { }
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
func otherClass() -> OtherProtocol {
return SomeOtherClass<T>()
}
}
class SomeOtherClass<T>: OtherProtocol {}
So now when you implement your loop, you can rely on the fact that since each element is a Composite, you know it must provide an instance of OtherProtocol via the otherClass() method:
var c = GenericSubclass<Int>()
c.composites = [GenericSubclass<Double>(), GenericSubclass<Int>(), GenericSubclass<Character>()]
for item in c.composites {
let sc = item.otherClass()
print(sc)
}
Alternatively, if only GenericSubclass should vend an OtherProtocol, you can make the return type Optional and define an extension for all the other implementations of Composite:
protocol Composite {
var composites: [Composite] { get set }
func optionalClass() -> OtherProtocol?
}
extension Composite {
func optionalClass() -> OtherProtocol? {
return nil
}
}
I did some experiment on this in the playground and i came up with this
protocol Composite {
var composites: [Composite] { get set }
}
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
}
let subclass = GenericSubclass<String>()
for item in subclass.composites {
let className = String(describing: type(of: item))
let aClassType = NSClassFromString(className) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(instance) //Output: GenericSubclass<String>
}
Hope this will help someone.
I think it's not possible to do that in array.
While you creat some different GenericSubclass<T> then put it in array , you will lose <T> no matter the composites is [Composite] or [Any].
// this line won't compile
let array = [GenericSubclass<Int>(),GenericSubclass<Double>()]
//error: heterogenous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
You want donging something like this func below, the param should be GenericSubclass<T> to compile success
func genericFunc<T>(param:GenericSubclass<T>) {
let sc = SomeOtherClass<T>()
print(sc)
}
Anyway you can implement it with member var for the instance like the code below:
class Subclass {
var type : Any
init(type : Any) {
self.type = type
}
}
class SomeOtherClass : CustomDebugStringConvertible{
var type : Any
init(type : Any) {
self.type = type
}
var debugDescription: String{
return String(describing: type.self)
}
}
let array : [Subclass] = [Subclass(type : Int.self),Subclass(type : Double.self),Subclass(type : String.self)]
let scArray = array.flatMap {SomeOtherClass(type:$0.type.self)}
print(scArray) // prints [Int, Double, String]
You need to add one method to protocol which creates new item of Type supported this protocol. So now you can use enums, structs and classes without any knowledge of creating object of specific type.
You can play in playground with the following code:
import UIKit
//This is your protocol
protocol MyAwesomeProtocol {
//this methods leaves implementaion detailes
//to concrete type
func createNewObject()->MyAwesomeProtocol
}
//Just create empty string
extension String: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return String()
}
}
//create Enum with default value
extension UIControlState: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return UIControlState.normal
}
}
//create viewController of any type
extension UIViewController: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return type(of:self).init()
}
}
//This is test function
//it creates array of newly created items and prints them out
//in terminal
func doSomeCoolStuffWith(items:[MyAwesomeProtocol]){
var newItems = [MyAwesomeProtocol]()
for anItem in items {
let newOne = anItem.createNewObject()
newItems.append(newOne)
}
print("created new ones:\n\(newItems)\nfrom old ones:\n\(items)\n")
}
doSomeCoolStuffWith(items: [UIControlState.focused,UIControlState.disabled])
doSomeCoolStuffWith(items: [UISplitViewController(),UINavigationController(),UICollectionViewController()])
doSomeCoolStuffWith(items: ["I","love","swift"])
This will produce the following result:
created new ones:
[__C.UIControlState(rawValue: 0), __C.UIControlState(rawValue: 0)]
from old ones:
[__C.UIControlState(rawValue: 8), __C.UIControlState(rawValue: 2)]
created new ones:
[<UISplitViewController: 0x7fa8ee7092d0>, <UINavigationController: 0x7fa8f0044a00>, <UICollectionViewController: 0x7fa8ee705f30>]
from old ones:
[<UISplitViewController: 0x7fa8ee7011e0>, <UINavigationController: 0x7fa8f004e600>, <UICollectionViewController: 0x7fa8ee708fb0>]
created new ones:
["", "", ""]
from old ones:
["I", "love", "swift"]
So, I have a generic (with restrictions) class and a number of subclasses of it that concrete the generic type when they subclass.
I want to store instances of these subclasses in an array so then they can be iterated and treated all of them in the same way, but apparently, there is no way to convert from the subclass to the generic superclass.
Here is some code that illustrates the problem (you can copy-paste it in a playground to see the outcome):
// Lets create regular classes
class Fruit {
var text: String { return "I am some Fruit" }
}
class Apple: Fruit {
override var text: String { return "I am an Apple" }
}
class Orange: Fruit {
override var text: String { return "I am an Orange" }
}
// This obviously works:
let test1: Fruit = Apple()
let test2: Fruit = Orange()
// Let's create some generic class
class Tree<T: Fruit> {
let fruit: T
init(fruit: T) {
self.fruit = fruit
}
}
// Subclasses from the generic class (these work)
class AppleTree: Tree<Apple> {
convenience init() {
self.init(fruit: Apple())
}
}
class OrangeTree: Tree<Orange> {
convenience init() {
self.init(fruit: Orange())
}
}
// This works:
let tree: Tree<Fruit> = Tree(fruit: Apple())
tree.fruit.text // "I am an Apple"
// This works:
let appleTree1: Tree<Apple> = AppleTree()
appleTree1.fruit.text // "I am an Apple"
// This fails: "Cannot convert value of type 'AppleTree' to specified type 'Tree<Fruit>'
let appleTree2: Tree<Fruit> = AppleTree()
// This works:
let fruitArray: [Fruit] = [Apple(), Orange()]
// THIS IS MY GOAL:
// This fails: "Cannot convert value of type 'AppleTree' to specified type 'Tree<Fruit>'
let treeArray: [Tree<Fruit>] = [AppleTree(), OrangeTree()]
// Let's try with a generic subclass
class FruitTree<T: Fruit>: Tree<T>{}
// This works:
let genericTree: Tree<Fruit> = FruitTree(fruit: Apple())
// Let's try with a generic but more concrete subclass
class GenericOrangeTree<T: Orange>: Tree<T>{
convenience init() {
self.init(fruit: Orange() as! T)
}
}
// This works:
let genericOrangeTree1 = GenericOrangeTree(fruit: Orange())
let genericOrangeTree2 = GenericOrangeTree()
// This fails: Cannot invoke initializer for type 'GenericOrangeTree<Orange>' with an argument list of type '(fruit: Orange)'
let genericTree2: Tree<Fruit> = GenericOrangeTree(fruit: Orange())
// Again, this fails: "Cannot convert value of type 'GenericOrangeTree<Orange>' to specified type 'Tree<Fruit>'
let genericTreeArray: [Tree<Fruit>] = [GenericOrangeTree()]
What I am trying to do is illustrated in the sample code by the treeArray variable.
I don't understand why the code fails when it fails. My intuition says that this should work and I cannot find a work around this problem.
TL;DR: I have a Generic class with some subclasses, I want to have an array of the Generic class filled with the subclasses, but the compiler complains.
You are mixing up the type hierarchies before and after Generics have been materialized. With a generic class/func you essentially setup a template (a compiler macro) which is resolved at compile-time.
If you say
Generic<SubClass1>
Generic<SuperClass>
those are resolved by the compiler to types like:
class Generic_SubClass1 {
let property : SubClass1
}
class Generic_SuperClass {
let property : SuperClass
}
After the generics are resolved those two types share no common base type and hence can't be cast to each other. They are completely separate.
Not sure and didn't try, but maybe this is what you want:
class GenericBase {
let property : SuperClass
}
class Generic<T: SuperClass> : GenericBase {
final var typedProperty : T {
get { return property as T }
set { property = T }
}
}
You can then use GenericBase as the common ancestor and use dynamic type to check for subclasses.
P.S.: Your code is a little hard to follow, maybe use something like 'Fruit', 'Apple' and 'Orange' - much easier to read than 'Superclass', 'Subclass1', 'Subclass2' ;-)
I think what you are looking for is type erasure.
For example, imaging you have the following:
protocol ClassWithView {
associatedType: View: UIView
var view: View { get set }
}
class ConcreteWithView {
associatedType View = SubclassOfUIView
var view: SubclassOfUIView
}
// Somewhere this will fail because there is missing associated type information
var array: [ConcreteWithView]
With type erasure you can enforce that any access to the array will only let you access the common UIView stuff. Modifying the above like:
protocol AnyClassWithView {
var baseView: UIView
}
protocol ClassWithView: AnyClassWithView {
associatedType: View: UIView
var view: View { get set }
}
// Default implementation
extension AnyClassWithView {
var baseView: UIView {
return view as UIView
}
}
Now, elsewhere you can define the array like:
var array: [AnyClassWithView]
Which will succeed, and you can access only the UIView type with .baseView. You can add stuff to the AnyClassWithView definition and do stuff with the shared UIView.
If you want access to the individual subtypes, create a function on this that accepts a generic parameter and you should be able to cast to access the subtype information.
Perhaps you could define a protocol named FruitTree that can get a Fruit and return some kind of Fruit:
protocol FruitTree {
associatedType FruitKind
func getFruit() -> Fruit
func setFruit(FruitKind)
}
Then, define classes something like:
class AppleTree: FruitTree {
var apple Apple
typeAlias FruitKind = Apple
func getFruit() -> Apple {return apple}
func setFruit(Apple a} {apple = a}
}
class OrangeTree: FruitTree {
var orange Orange
typeAlias FruitKind = Orange
func getFruit() -> Orange { return orange}
func setFruit(Orange o) {orange= o}
}
let treeArray: [FruitTree] = [AppleTree(), OrangeTree()]
I want to check if elements have been added to an array in swift using KVO, and I essentially copied the example from Apple's documentation, but when the code runs, it does not catch when the size of the array updates. Here is what I have now:
class ShowDirectory: NSObject {
var shows = [Show]()
dynamic var showCount = Int()
func updateDate(x: Int) {
showCount = x
}
}
class MyObserver: NSObject {
var objectToObserve = ShowDirectory()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: "showCount", options: .New, context: &myContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("\(newValue) shows were added")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
}
}
After I add the shows to the array, I set showCount equal to the number of elements in the array, however, it does not print "X shows were added" to console. My viewDidLoad() function simply calls the function that adds elements to the array, and nothing else at the moment.
You unfortunately cannot add as an observer to an Int, as it does not subclass NSObject
See the Apple Docs and search for "Key-Value Observing"
You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class.
Otherwise, your KVO boiler-plate code looks good to me.
If you want to be notified when your array's contents change, you could try what #Paul Patterson recommends and use a proxy object
(Using my usual playing card example)
I am trying to make a generic CardCollection that both a Deck and a Hand would inherit from. Both Decks and Hands would need to be sorted or shuffled, but there would be some differences such as initialisation and whether the method for removing a Card for use elsewhere is Deal (for a Deck), Play, or Discard (for Hands).
class CardCollection: <Some protocol or another that Arrays use> {
var collective = [Card]()
// CardCollection-specific functions
// pass-through functions
func append(newCard: Card) {
collective.append(newCard)
}
}
class Deck: CardCollection {
// deck-specific functions
}
class Hand: CardCollection {
// hand-specific functions
}
The way I'm currently implementing it (see above) is with a Class that contains an Array of Cards, but I can't use my classes like they were Arrays without writing tons of pass-through functions to get my classes to conform to all the protocols as an Array.
What I need is a way that lets me do things like for card in deck (as if deck were simply an Array<Card>) without writing tons and tons of wrapper functions just to get the CardCollection to conform to all the necessary protocols.
How do I make a CardCollection that functions like it's just an Array<Card> without making pass-through functions on every function used by the protocols that Array uses?
You can define a CardCollection protocol which inherits from
RangeReplaceableCollectionType,
and a protocol extension with default implementations to forward all
access methods to the underlying collective array:
struct Card {
// Simplified for demonstration purposes:
let rank : Int
let suit : Int
}
protocol CardCollection : RangeReplaceableCollectionType {
var collective : [Card] { get set }
}
extension CardCollection {
var startIndex : Int { return collective.startIndex }
var endIndex : Int { return collective.endIndex }
subscript(position : Int) -> Card {
get {
return collective[position]
}
set(newElement) {
collective[position] = newElement
}
}
mutating func replaceRange<C : CollectionType where C.Generator.Element == Card>(subRange: Range<Int>, with newElements: C) {
collective.replaceRange(subRange, with: newElements)
}
}
Then
struct Deck: CardCollection {
var collective = [Card]()
}
struct Hand: CardCollection {
var collective = [Card]()
}
both conform to RangeReplaceableCollectionType and can be treated
like an array:
var deck = Deck()
deck.append(Card(rank: 1, suit: 1))
deck[0] = Card(rank: 2, suit: 3)
for card in deck {
print(card)
}
var hand = Hand()
hand.append(deck.first!)
If Deck/Hand are classes instead of structs then they
need to be final or have a required init() method,
compare
Why use required Initializers in Swift classes?.
Slightly more general, you can define an ElementCollection
protocol (independently of the Card type)
which behaves like an array (by conforming to
RangeReplaceableCollectionType) and forwards the access to
an underlying elements array:
protocol ElementCollection : RangeReplaceableCollectionType {
typealias Element
var elements : [Element] { get set }
}
extension ElementCollection {
var startIndex : Int { return elements.startIndex }
var endIndex : Int { return elements.endIndex }
subscript(position : Int) -> Element {
get {
return elements[position]
}
set(newElement) {
elements[position] = newElement
}
}
mutating func replaceRange<C : CollectionType where C.Generator.Element == Element>(subRange: Range<Int>, with newElements: C) {
elements.replaceRange(subRange, with: newElements)
}
}
Which is then used as
struct Card {
// Simplified for demonstration purposes:
let rank : Int
let suit : Int
}
struct Deck: ElementCollection {
var elements = [Card]()
}
struct Hand: ElementCollection {
var elements = [Card]()
}