Swift subclasses complex subtype - ios

I have this design problem in Swift. Here is my root class and it's subclasses:
enum VisualItemType {
case video, picture, text,...
}
class VisualItem {
public var itemType: VisualItemType
...
}
class PictureItem:VisualItem {
private var subItems:[PictureItem OR TextItem But NOT VideoItem]? //Overlay of picture can be picture or text but not video
}
class VideoItem: VisualItem {
private var subItems:[VisualItem]? //Overlay of video can be any type of visual item
}
How do I cleanly implement subItems of PictureItem in Swift without creating a new enum of subtypes?

You can do that by introducing a new protocol that only TextItem and PictureItem will conform:
protocol PictureOverlayable: VisualItem {}
class TextItem: VisualItem, PictureOverlayable {}
class PictureItem: VisualItem, PictureOverlayable {
private var subItems: [PictureOverlayable]?
}

Related

Setting protocol which inherits from another protocol as associated type produces error

I'm trying to learn protocols and associatedtypes. I have couple of protocols which declare associatedtypes, starting with:
protocol MasterViewModel {
associatedtype Item: AWMediaItem
...
}
AWMediaItem is another protocol
protocol AWMediaItem {
var name: String { get }
var source: AWMediaSource { get }
}
And AWAlbum is yet another protocol which inherits from AWMediaItem
protocol AWAlbum: AWMediaItem {
var albumName: String { get }
...
}
For some reason, in a class implementing MasterViewModel protocol, I cannot set the AWAlbum to be the Item.
final class AlbumsMasterViewModel: MasterViewModel {
typealias Item = AWAlbum // Error
...
}
The warning I get is
Possibly intended match 'AlbumsMasterViewModel.Item' (aka 'AWAlbum') does not conform to 'AWMediaItem'
If I understand correctly, all AWAlbum's will implement AWMediaItem so why is this not working?
I think you meant to write
final class AlbumsMasterViewModel<Item: AWAlbum>: MasterViewModel {
}
I assume that when you write :
typealias Item = AWAlbum // Error
you want AlbumsMasterViewModel's item to conform to your AWAlbum protocol but you just create a typelias meaning that Item is just an alias for AWAlbum .
If you want to use a type alias you need a concrete type conforming to AWMediaItem, not a protocol inheriting from it. eg :
class ConcreteAlbum: AWAlbum {
var albumName: String
var name: String
var source: AWMediaSource
...
}
final class AlbumsMasterViewModel: MasterViewModel {
typealias Item = ConcreteAlbum // No Error
}
Edit
if you want to use AlbumsMasterViewModel with multiple Item types you can also declare it that way :
final class AlbumsMasterViewModel<Item: AWMediaItem>: MasterViewModel {
}

How do I get get set to work with protocols?

I'm confused when using get set in protocols. Using only get works fine, but the set part doesnt'.
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
...
Then when I use it in the viewController
self.mainViewModel.localDoor = $0
But this gives me the error
Cannot assign to property: 'mainViewModel' is a get-only property
How do I set it up properly?
EDIT
Initiation of the viewModel is done with factory based dependency injection
protocol MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { get }
}
extension MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}
It is totally depends on how you create object for mainViewModel.
Let's create some cases with your code:
import UIKit
typealias LocalDoorCoreDataObject = String
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
static let instance = MainViewModel()
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
}
protocol MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { get }
}
extension MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}
Case 1
Here we are creating an object and assigning object through getter as a closure.
So, here mainViewModel has only getter not setter i.e. it'a get-only property.
var mainViewModel: MainViewModelProtocol { MainViewModel.instance }
mainViewModel.localDoor = "assign some thing" // Error: Cannot assign to property: 'mainViewModel' is a get-only property
Case 2
Here we are directly assigning object to mainViewModelOther. So, this will be a normal property and you can make changes in properties of model.
var mainViewModelOther: MainViewModelProtocol = MainViewModel.instance
mainViewModelOther.localDoor = "assign some thing"
Case 3
You can also create a class that will hold your model object, and created another object of your class. You can make changes in properties of model.
class MyClass {
var mainViewModel: MainViewModelProtocol = MainViewModel.instance
}
let myClassObj = MyClass()
myClassObj.mainViewModel.localDoor = "assign some thing"
TL;DR
Mark your MainViewModelProtocol as being class-only (i.e. protocol MainViewModelProtocol: class { ... }) to solve the issue.
The long answer
To understand why marking your MainViewModelProtocol as class-only fixes the problem, we need to take couple steps back and look at how structs and classes are stored internally.
Case 1: MainViewModelProtocol is a reference-type (i.e. class)
First, let's consider the case where MainViewModel is a class: Classes are reference-types, which means that after you retrieve the your view model through the mainViewModel property, you have a pointer to the same view model that is stored inside your view controller. Modifying the referenced type will also modify the view model of the view itself (since they both point to the same object). As an example
/* ... */
class MainViewModel: MainViewModelProtocol { /* ... */ }
var viewModel = myViewController.mainViewModel
viewModel.localDoor = /* something */
modifies the view model that's shared between the local variable viewModel and the view controller. This is exactly what you want.
Case 2: MainViewModelProtocol is a value type (i.e. struct)
Now let's consider if the MainViewModel was a struct: structs are value-types, so retrieving the view model through the mainViewModel computed property essentially clones the view model. Now you might modify the retrieved view model as much as you like locally, but there is no way assign it back to your view controller
/* ... */
struct MainViewModel: MainViewModelProtocol { /* ... */ }
var viewModel = myViewController.mainViewModel
viewModel.localDoor = /* something */
just modifies the local copy of the view model stored in the viewModel variable. There is no way to assign the local variable back to myViewController.
Conclusion
I hope this illustrates why your pattern only works with reference-types and not value types.
Now the Swift compiler needs to be conservative and consider both cases since it doesn't know if all types conforming to MainViewModelProtocol will be classes or structs (consider public protocols vended as a library to which library-users can conform). If you add the class-constraint to the protocol, you tell the compiler that using the pattern from Case 1 is totally fine – just grab a shared instance and modify it – and that there is no need for a setter to modify the view model.
No need to mark MainViewModelProtocol as class only, when the compiler says :
Cannot assign to property: 'mainViewModel' is a get-only property
it's actually complaining about your view controller implementation. I assume mainViewModel is a computed property so you can't assign it.
I managed to reproduce your error with the following playground :
typealias LocalDoorCoreDataObject = String
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
static let instance = MainViewModel()
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
}
final class FakeVC {
var mainViewModel: MainViewModelProtocol {
MainViewModel.instance
}
}
var viewController = FakeVC()
viewController.mainViewModel.localDoor = "foo" // Cannot assign to property: 'mainViewModel' is a get-only property
I got rid of the error by changing FakeVC implementation to :
final class FakeVC {
var mainViewModel: MainViewModelProtocol = MainViewModel()
}

Get data from class with two array class Swift

I have a class with two array, i need this class to work with it in tableview so this is my code
class X {
private var abc: [Demo]!
private var def: [Project]!
init() {
}
init(abc:[Demo], def:[Project]) {
self.abc = abc
self.def = def
}
}
So how can i get access to class Demo and Class Project, i have already the data in class X
class Demo like this
class Demo {
private var nom:String
init(nom:String) {
self.nom = nom
}
and class Project like this
class Project {
private var title:String
init(title:String){
self.title = tile
}
You haven't posted the code where you're trying to access the data, but your problem may be that you have declared nom and title as private which makes them inaccessible to other classes. Try deleting private.
If you just want to prevent other classes from changing them, you can change private to public private(set).
what 's mean of "get access"?
If your property in class Demo and Project is private , so you must have a get function to access to property in it

Make class type a Dictionary key (Equatable, Hashable)

Say I have a class named LivingCreature
And other classes that inherit from it:
Human
Dog
Alien
This is what I'm trying to accomplish:
let valueForLivingCreature = Dictionary<Alien, String>
And access it like so:
let alienValue = livingCreatureForValue[Alien]
But this means the class should conform to Equatable and Hashable, but the class itself, not the class instance.
I've tried various ways of accomplishing this, but no luck.
As a compromise I've came up with is:
typealias IndexingValue = Int
class LivingCreature {
static var indexingValue: IndexingValue = 0
}
And then I can use the class as a key like so:
let livingCreatureForValue = Dictionary<IndexingValue, String>
Access:
let alienValue = livingCreatureForValue[Alien.indexingValue]
But, this way the IndexingValue should be set per class, by hand.
I would like to make a hash from the class itself like so:
class LivingCreature {
static var indexingValue: IndexingValue {
return NSStringFromClass(self).hash
}
}
This is not possible because self is not accessible is static var.
My question is, is there a better way of addressing this kind of issue?
Edit:
#Paulw11 Asked me why not make LivingCreature confirm to Equatable and Hashable,
The reason is I would not be able to access the value by the class type reference.
I would have to alloc an instance every time:
let alienValue = livingCreatureForValue[Alien()]
I do not want to call "Alien()" every time for finding a value.
And the component that uses it, doesn't care about the livingCreature instance, only about the class type.
I assume your are trying something like:
let valueForLivingCreature = Dictionary<LivingCreature.Type, String>
and:
let alienValue = valueForLivingCreature[Alien.self]
Then you can use ObjectIdentifier:
class LivingCreature {
class var classIdentifier: ObjectIdentifier {
return ObjectIdentifier(self)
}
//...
}
class Human: LivingCreature {
//...
}
class Dog: LivingCreature {
//...
}
class Alien: LivingCreature {
//...
}
let valueForLivingCreature: Dictionary<ObjectIdentifier, String> = [
Human.classIdentifier: String(Human),
Dog.classIdentifier: String(Dog),
Alien.classIdentifier: String(Alien),
]
let alienValue = valueForLivingCreature[Alien.classIdentifier] //->"Alien"
But in most use cases when you want to use meta-class as a dictionary key, you can find another way around:
class LivingCreature {
class var classValue: String {
return String(self)
}
//...
}
class Human: LivingCreature {
//...
//Override `classValue` if needed.
}
class Dog: LivingCreature {
//...
}
class Alien: LivingCreature {
//...
}
let alienValue = Alien.classValue //->"Alien"

Are private swift protocols possible?

After reading the docs about this I discovered
In Swift, as in Objective-C, protocol conformance is global—it is not possible for a type to conform to a protocol in two different ways within the same program.
So what is the purpose of the private prefix here
private protocol PartyFormViewControllerDelegate: class {
func partyFormViewController(controller: PartyFormViewController, cancelButtonPressed button: UIBarButtonItem)
}
class PartyFormViewController: GenericViewController {
//...
}
In Swift private means: visible within the current source file.
A private Type could conform to a private protocol, look here:
private protocol Animal { }
private class Dog: Animal { }
class Zoo {
private var animals = [Animal]()
var count : Int { return animals.count }
}
Here, Animal and Dog are visible only within the current files. However, they are used by Zoo which has internal visibility and exposes the count of the animals to the whole module.

Resources