Array of generic classes and subclassing - arrays

I'm having trouble wrapping my head around generics. What I want is to have an array of generic classes, each with it's own associated type, and call a function accordingly. It would look something like this:
class SomeGenericClass<U> {
func addCallback(callback: (U)->() ) { ... }
}
var array: [SomeGenericClass] // compile error
The last line yields an error, so I found that I needed to have a superclass. I tried something like this:
class SuperClass {
func addCallback<V>(callback: (V)->() ) { ... }
}
class SomeGenericClass<U> {
func addCallback<V: U>(callback: (V)->() ) { ... } // compile error
}
var array: [SuperClass] // no compile error
This yields the error Type 'V' constrained to non-protocol, non-class type 'U'.
Basically I want to be able to do:
array.append(SomeGenericClass<UIImage>()) // array[0]
array.append(SomeGenericClass<Int>()) // array[1]
// Since array[0] is effectively of type SomeGenericClass<UIImage>, the compiler should understand that the close added if of type (UIImage)->(), and therefore that value is of type UIImage
array[0].addCallback { value in
someImageView.image = value
}
Is using a superclass the right approach in this case? How should it be implemented?

I worked around this problem by storing my array members in their own variable. That is, instead of defining my array like:
lazy var array: [SuperClass] = [
SomeGenericClass<UIImage>(),
SomeGenericClass<Int>(),
//etc...
]
I defined it this way:
lazy var genericFirst: SomeGenericClass<UIImage> = SomeGenericClass<UIImage>()
lazy var genericSecond: SomeGenericClass<Int> = SomeGenericClass<Int>()
// etc...
lazy var array: [SuperClass] = [
genericFirst,
genericSecond,
//etc...
]
This way, I can access the generics I want like this:
genericFirst.addCallback { value in
// value is indeed of type UIImage
someImageView.image = value
}

Related

Unwrapping array types recursively in TypeScript

I need to create a function that can get a very "dynamic" parameter. it should be a ble to accept many types of arrays
class NdArray<T> {
}
// need to be able to get
f(number[]) // -> NdArray<number>
f(number[][]) // -> NdArray<number>
f(number[][][]) // -> NdArray<number>
//and so on...
f(string[]) // -> NdArray<string>
f(string[][]) // -> NdArray<string>
f(string[][][]) // -> NdArray<string>
// and generally
f(object[][][]...) // -> NdArray<object>
Recursively unwrap the array:
type UnwrapArray<A> = A extends unknown[] ? UnwrapArray<A[number]> : A;
If A is an array, we unwrap the type of its elements. Otherwise it's just something else we don't need to unwrap.
Your function f here could be something like:
function f<T>(type: T): NdArray<UnwrapArray<T>> {
// a very cool implementation
}
Playground

How to update an array passed as a parameter on init when new items are added on the original array

Say for example I have an array that needs to be used on multiple classes, if I need a worker to update the values of this array how do I make it so that when I add new values to the said array the object worker's value also changes.
Example:
class Object {
var id: Int
var foo: String
var bar: Int
init(id: Int, foo: String, bar: Int) {
self.id = id
self.foo = foo
self.bar = bar
}
}
class ObjectWorker {
var objects: [Object]
init(objects: [Object]) {
self.objects = objects
}
func updateObjects(withId id: Int) {
self.objects.forEach { $0.foo = "a different value" }
}
}
class SomeClass {
// this declaration will happen on more than one class
var objects: [Object] = ... // let's just say there are 10 objects here
lazy var worker = ObjectWorker(objects: self.objects)
init() {
// to initialize the workers
_ = worker
print(objects.count) // 10
print(worker.objects.count) // 10
let newObjects: [Object] = ... // let's say this has 5 new values
objects.append(contentsOf: newObjects)
print(objects.count) // 15
print(worker.objects.count) // 10
}
}
I have tried making the ObjectWorker's init be an inout parameter like this init(objects: inout [Object] but even then the result is still the same. The updateObjects works though even if the init is not an inout parameter.
Note:
I know I can do this by using the ObjectWorker as the container of the objects instead of what is currently going on in here, but is there a way to do this without doing that?
I can also use static functions instead, but let's not go there
As already mentioned, Swift Arrays are value types so ObjectWorker gets a copy of the array. If you don't want to use ObjectWorker as a container, you could use an NSArray instead (which is a reference type).

Address of Array & Remove(at :) IOS

I'm passing an array of a specific model by reference between ViewControllers.
If I change any value of a specific element in the array it reflects well in all ViewControllers but when I remove an element from that array it doesn't reflect to the other controllers.
Does the remove(at: ) function create new array and refer to another address?
And if so how to delete an element without changing the address of array so it can reflect this change on the other view controllers?
Swift Arrays are value types (specifically, an array is a struct), not reference types, so you are mistaken when you say that you are "passing an array of a specific model by reference between view controllers". You can only ever pass a Swift array as a value.
Arrays, like other structs, have copy-on-modify semantics. As soon as you change the array itself a copy is made and the change is made to the copy.
Now, in your case the array contains references to model objects; When you update the model object you change the object itself, not the reference held in the array, so you see the change reflected in all of your view controllers.
An analogy might be the difference between adding a house to a street (which changes the street itself) versus changing the occupants of an existing house on the street.
I would suggest you implement a model object that provides abstraction from the underlying array so that you have better code and avoid the issue with array references.
One approach could be something like:
struct MyModel {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func removeModel(at: Int) {
guard at >= 0 && at < _models.count else {
return
}
self._models.remove(at: at)
}
}
Although this isn't ideal as it still requires model consumers to know indices in the underlying array. I would prefer something like this:
struct MyModel: Hashable {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func remove(model: MyModel) -> Bool {
if let index = self._models.index(of: model) {
_models.remove(at: index)
return true
} else {
return false
}
}
}
Now I don't need to know what internal collection MyData uses to store the models.
If you need to pass an array (or any other value type) by reference, you could go through an intermediate structure that manages the indirection for you.
[EDIT] changed to use KeyPaths available in Swift 4.
// Generic class to hold a "weak" reference to a property from an object
// including properties that are valued types such as arrays, structs, etc.
// This is merely an encapsulation of Swift's native KeyPath feature
// to make the code a bit more readable and simpler to use
//
class ReferenceTo<ValueType> { var value:ValueType! { get { return nil} set {} } }
class Reference<OwnerType:AnyObject,ValueType>:ReferenceTo<ValueType>
{
internal weak var owner:OwnerType!
internal var property:ReferenceWritableKeyPath<OwnerType,ValueType>! = nil
internal var valueRef:KeyPath<OwnerType,ValueType>! = nil
init(_ owner:OwnerType, _ property:ReferenceWritableKeyPath<OwnerType,ValueType>)
{ (self.owner,self.property) = (owner,property) }
init(_ owner:OwnerType, get valueRef:KeyPath<OwnerType,ValueType>)
{ (self.owner,self.valueRef) = (owner,valueRef) }
override var value:ValueType!
{
get { return valueRef != nil ? owner?[keyPath:valueRef] : owner?[keyPath:property] }
set { owner?[keyPath:property] = newValue }
}
}
With this generic class you can create references to valued type properties of object instances and manipulate them anywhere in your code as if the valued type property was a reference type.
// Example class with a read/write and a read-only property:
class MyObject
{
var myArray = [1,2,3,4]
var total:Int { return myArray.reduce(0,+) }
}
var instance:MyObject! = MyObject()
// create a reference to the array (valued type)
// that can be used anywhere and passed around as a parameter
let arrayRef = Reference(instance, \.myArray)
// the value is accessed and manipulated using the
// "value" property of the reference
arrayRef.value.remove(at:2)
arrayRef.value.append(5)
print(instance.myArray) // [1,2,4,5]
// Read-only properties can also be manipulated as
// references
let valueRef = Reference(instance, get:\.total)
print(valueRef.value) // 12
The Reference class allows passing the value as a reference to function parameters
// a function that expects a reference to an array
// would be declared as follows
func changeArray(_ array:ReferenceTo<[Int]>)
{ array.value.insert(9, at: 1) }
// the reference can also be used as an inout parameter
func shift(_ array:inout [Int])
{ array = Array(array.dropFirst()) + Array(array.prefix(1)) }
changeArray(arrayRef)
shift(&arrayRef.value!)
print(instance.myArray) // [9,2,4,5,1]
...
// the reference uses a weak link to the owner
// of the referenced property or value
// so there will be no strong reference cycle issues even
// if the reference is used in an object held strongly
// by the owner itself
instance = nil
print(arrayRef.value) // none ... no more value after the owner is gone

Swift 3: Different kind of elements in an array are not acceptable

Up until Swift 2.2 I was able to do this:
for each in [myUIButton,myUILabel] {
each.hidden = true
}
but in Swift 3 this is not acceptable because label, button etc are not the same kind of element. I have already changed line 2 to each.isHidden = true
It throws "Heterogeneous collection literal..." error. When you fix it by adding [Any], it throws "Cast 'Any' to 'AnyObject.." error.
Is there an easy fix to this problem?
Find a common ancestor class having isHidden property, and explicitly cast to it:
for each in [myUIButton, myUILabel] as [UIView] {
each.isHidden = true
}
All items in your array must have a common subclass, like UIView in the case of myButton and myLabel (presumably) in order for type inference to take place.
let label = UILabel()
let button = UIButton()
let collectionView = UICollectionView()
let tableView = UITableView()
let array = [label, button, collectionView, tableView] // Type: [UIView]
for item in array {
item.isHidden = true
}
This code will work for your purposes.
Furthermore, if they all conform to the same protocol, you must explicitly name the protocol they conform to.
protocol Commonality {
func commonMethod() { ... }
}
class ThingA: Commonality { ... } // Correctly conform to Commonality
class ThingB: Commonality { ... } // Correctly conform to Commonality
class ThingC: Commonality { ... } // Correctly conform to Commonality
let array: [Commonality] = [ThingA(), ThingB(), ThingC()]
for item in array {
item.commonMethod()
}
This should work as well, but you must explicitly name the common protocol. Otherwise (at least in my tests), it downcasts everything down to Any.
Tell Swift it's an [Any] array:
for each in [myButton,myLabel,x,y,z] as [Any] {
each.hideen = true
}
But then you will get an error cause Any doesn't have a property called hideen (typo?).

Using functions in arrays Swift

i use the following function to retrieve a random person from an array:
func getRandomPerson() -> String{
if(personArray.isEmpty){
return ""
} else {
var tempArray: [String] = []
for person in personArray{
tempArray += [person.getName()]
}
var unsignedArrayCount = UInt32(tempArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
if tempArray.isEmpty {
return ""
} else {
return tempArray[randomNumber]
}
}
}
I would like to use this function inside an array of strings, Like this:
var theDares: [String] = ["Dare1 \(getRandomPerson())", "Dare2", "Dare3", "Dare4", "Dare5"]
But when i use the functions, it only runs the function once. Can you make the function run everytime you use the "Dare1" in this instance.
Thanks in advance
I think you are asking if you can set up your array so every time you fetch the object at index 0, it re-builds the value there.
The short answer is no. Your code is creating an array of strings, and the item at index 0 is built ONCE using a function call.
However, it is possible to make a custom class implement the subscript operator. You could create a custom object that looks like an array and allows you to index into it using an Int index. In response to the index operator you could run custom code that built and returned a random string.
Since it sounds like you're a beginning programmer creating a custom class the implements the subscript operator might be beyond your current abilities however.
Try like this:
let personArray = ["John", "Steve", "Tim"]
var randomPerson: String {
return personArray.isEmpty ? "" : personArray[Int(arc4random_uniform(UInt32(personArray.count)))]
}
println(randomPerson) // "Steve"

Resources