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
Related
I have a protocol, structure and instances + protocol and class, which should change the instances of the structure:
protocol Cars {
var car: String { get set }
var accesories: [String] { get set }
}
struct Car: Cars {
var car: String
var accesories: [String]
}
var carOne = Car(car: "Car One", accesories: ["accessoryOne", "accessoryTwo"])
var carTwo = Car(car: "Car Two", accesories: ["accessoryTwo"])
protocol Dealership {
static var cars: [Car] { get set }
static func addAccesories(add: [String])
}
Next, in DealershipOne I want to make a func addAccesories that will add an array of strings to the instance property, I try do it this way and some other ways, but I get the error Cannot use mutating member on immutable value: '$0' is a 'let' constant.
class DealershipOne: Dealership {
static var cars = [carOne, carTwo]
static func addAccesories(add: [String]) {
cars.forEach{ $0.accesories.append(contentsOf: add) } // ERROR
}
}
How do I resolve this?
You can not add new value to direct iterated objects. $0 is a let object so you can not add value.
A solution to use index and update the value.
cars.indices.forEach{ cars[$0].accesories.append(contentsOf: add) }```
The anonymous closure parameter ($0) within your forEach loop is an immutable copy of a car from the cars you’re trying to iterate.
You could make a mutable copy with var car = $0, but even that won’t help, because you would be mutating the copy, and not the actual cars in the static cars array.
If you would like to keep using struct to model your cars, then the only way to modify properties of those cars within the array is to get an l-value (in c++ lingo) via assignment through a subscript:
func add(accessories: [String]) {
for i in cars.insides {
cars[i].accessories += accessories
}
}
(I also made some minor tweaks to make this more idiomatic, such as t changing the function name, and using the += operator for arrays instead of Array.append(contentsOf:))
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
}
I am taking some inspiration from
https://marcosantadev.com/swift-arrays-holding-elements-weak-references/
and I want to be able to maintain an array holding weak references to its elements, so that in case those elements get released elsewhere in my code base, I don't retain them in my array.
I would like the implementation to be as type safe as possible, however should be reusable.
The strategy that I am using is declaring a Weak Reference container as so.
class WeakRefContainer<T> where T: AnyObject {
private(set) weak var value: T?
init(value: T?) {
self.value = value
}
}
Then I want to maintain an array of these WeakRefContainers, so I create an array extension:
extension Array where Element: WeakRefContainer<AnyObject> {
func compact() -> [WeakRefContainer<AnyObject>] {
return filter { $0.value != nil }
}
}
When calling the compact method, I am now able to clear up the array in case stuff needs to be cleaned up.
I am now having some compilation issues which am having trouble understanding.
Lets suppose I have a sample class
class SampleClass {
}
And I try to use everything as follows:
var weakReferencesArray = [WeakRefContainer<SampleClass>]()
let obj1 = WeakRefContainer.init(value: SampleClass())
let obj2 = WeakRefContainer.init(value: SampleClass())
weakReferencesArray.append(obj1)
weakReferencesArray.append(obj2)
weakReferencesArray.compact()
When I try to call compact I get the following error message:
MyPlayground.playground:29:21: 'WeakRefContainer<SampleClass>' is not a subtype of 'WeakRefContainer<AnyObject>'
Can anyone unblock me please? Thanks
Your code doesn't work because WeakRefContainer<SampleClass> is not a subclass of WeakRefContainer<AnyObject> because generics are invariant in Swift. Thus weakReferencesArray can't use the compact method added from the extension.
There is a workaround for this, via a protocol:
protocol WeakHolder {
var hasRef: Bool { get }
}
extension WeakRefContainer: WeakHolder {
var hasRef: Bool { return value != nil }
}
extension Array where Element: WeakHolder {
func compacted() -> [Element] {
return filter { $0.hasRef }
}
mutating func compact() {
self = compacted()
}
}
I also renamed compact to compacted, for better Swift semantics, and replaced the original compact by a mutating version.
You probably want the extension to apply to all [WeakRefContainer<T>] where T can be any type extending AnyObject.
extension Array where Element: WeakRefContainer<T> {
However, currently, parameterised extensions are not possible. See this proposal.
You can kind of work around this by making compact generic:
extension Array{
func compact<T>() -> [Element] where Element == WeakRefContainer<T> {
return filter { $0.value != nil }
}
}
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).
I'm attempting to customize the "Page-Based Application" template and am new to Swift (and all programming for that matter). In my ModelController.swift I've created a 2D array to use as the primary datasource.
However, when I go to print it or use it as label.text it is including the parenthesis. How do I get just the strings of the array element I'm calling for?
class ModelController: NSObject, UIPageViewControllerDataSource {
var pageData: NSArray = [[String]]()
override init() {
super.init()
// Create the data model.
pageData = [["right", "wrong"], ["left", "right"]]
println(pageData[1]) //For
}
I'm not sure what else from this file is relevant but here's another bit.
func indexOfViewController(viewController: DataViewController) -> Int {
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
if let dataObject: AnyObject = viewController.dataObject {
return self.pageData.indexOfObject(dataObject)
} else {
return NSNotFound
}
}
If you just want left, right
println("\(pageData[1][0]), \(pageData[1][1])")
If you literally want "left, right" with the double quotes included it's
println("\"\(pageData[1][0]), \(pageData[1][1])\"")
The reason you got the results you did is because pageData[1] isn't a String it's an NSArray. Because NSArray conforms to the Printable protocol it supplies its .description when used in println.