I created this singleton to access a shared array throughout my app:
class TranslationItems {
var delegate: TranslationItemsDelegate?
static let shared = TranslationItems()
var array = [Translation]() {
didSet {
delegate?.newItemAdded()
}
}
}
The problem is that this allows for duplication (the array may contain multiple items with the same hashValue). If I check for duplication inside the didSet setter and then change the array there (for example by doing array = Array(Set(array))) that leads to an infinite loop.
How do I remove duplicates in my class?
If you want to avoid duplicates why don't you use a Set anyway (Translation must conform to Hashable)?
var set = Set<Translation>()
However if you want to keep the array a more efficient way is to add an add method which filters the duplicates, Translation must conform to Equatable
func add(object: Translation) {
if !array.contains(object) {
array.append(object)
delegate?.newItemAdded()
}
}
Making a Set from the Array and then convert it back to Array is unnecessarily expensive.
You can do it exactly how you suggested. This doesn't lead to an infinite loop
didSet {
array = Array(Set(array))
...
}
Just add one instance method
class TranslationItems {
var delegate: TranslationItemsDelegate?
static let shared = TranslationItems()
private(set) var array = [Translation]() {
didSet {
delegate?.newItemAdded()
}
}
func set(array:[Translation]) {
self.array = Array(Set(array))
}
}
Related
I have a lazy property in a struct and every time I access it, it mutates the struct.
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
print(myStructPropery)
}
}
}
var myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
Result:
Print (didSet) will be called every time.
The ideal behaviour should be first time mutation only (since that's how lazy variables behave). Is this a bug in Swift or am I doing something wrong?
What you are seeing is is that the observed property items has notified its observer that it has been mutated because it was accessed, and a lazy variable is by definition mutating since it will be set at a later time. Therefore didSet gets called to handle this.
The lazy variable is actually only set once even though it signals that it has mutated and the property myStructPropery is only mutated once when the variable is first set but is the same instance after that.
Here is how we can verify this, first change the lazy var declaration so it's more like how we usually declare such a variable
lazy var items: [Int] = { numbers }()
and then add a print statement
lazy var items: [Int] = {
print("inside lazy")
return numbers
}()
If we now run the test code
var myClass = MyClass()
myClass.myStructProperty.items
myClass.myStructProperty.items
myClass.myStructProperty.items
we see that "inside lazy" only prints once. To verify that the property myStructProperty isn't changed we can make the struct conform to Equatable and perform a simple check inside didSet
didSet {
if oldValue != myStructProperty {
print(myStructProperty)
}
}
Now running the test we see that the print inside didSet is never executed so myStructProperty is never changed.
I have no idea if this behaviour is a bug but personally it feels like it might be complicate for the property observer to stop observing a lazy property once it was accessed or for a lazy var to not be defined as mutating once it is set.
I started debugging this with following set up -
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
// Changed this to make sure we are not invoking getter here
print("myStructPropery setter called")
}
}
}
let myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
I can reproduce the problem on Xcode 12.5 using Swift 5.4.
Attempt 1 : Turn var numbers into let numbers - Does NOT work.
let numbers = [1,2,3]
Attempt 2 : Assign the value inline without using an extra variable - Does NOT work.
struct MyStruct {
lazy var items = [1,2,3]
}
Attempt 3 : Assign the value inline using the full blown getter syntax - Does NOT work.
struct MyStruct {
lazy var items: [Int] = {
return [1,2,3]
}()
}
At this point, we are out of options to try. Even though we can clearly see that return [1,2,3] in the last attempt is executed exactly once, the MyClass.myStructPropery.modify is called repeatedly on access to items.
Maybe Swift Forums is a better place to discuss this.
I feel like something is broken with the value semantics here. Consider:
struct A {
var b = 0
mutating func changeB() {
b = 6
}
}
struct B {
var arr = [A]()
func changeArr() {
/* as expected this won't compile
unlss changeArr() is mutating:
arr.append(A())
*/
// but this compiles! despite that changeB is mutating!
for var a in arr {
a.changeB()
}
}
}
Why can this example mutate the struct contents without marking the function as mutating? In true value semantics, any time you change any part of the value, the whole value should be considered changed, and this is usually the behavior in Swift, but in this example it is not. Further, adding a didSet observer to var arr reveals that changeArr is not considered mutation of the value.
for var a in arr {
a.changeB()
}
This is copying an element from arr out into a and leaving arr unchanged.
If you directly access the elements inside arr via their indexes, then it will mutate and require the mutating keyword.
The reason changeArr is not mutating is because it isn't really doing anything since it is working on local copies of the A objects. If you really want the method to do something meaningful it needs to be changed to
mutating func changeArrForReal() {
for index in arr.indices {
arr[index].changeB()
}
}
I have a project where I want there are factories with orders (an array of Ints) that can be mutated.
I want all the code mutating, adding, removing, validating, etc of orders in another class (ie: almost like a proxy pattern) and when ready update the factory with the new orders.
I follow a delegate pattern to kick the new orders back to the factory for updating, however the factory orders never update.
Note: I know this is because the factory is a struct and that it is a value type
I am wondering if its possible to update the struct using a delegate pattern; or must I change it to a reference type (a class) in order to resolve the issue.
In the following code I've stripped out all the validation, push, pop and other features and am keeping it simple for this query by force changing the order array and then using a delegate to kick back the changed orders.
// Swift playground code
protocol OrderUpdatedDelegate {
mutating func ordersDidUpdate(_ orders: [Int])
}
// This class will handle all the validation to do with
// orders array, but for now; lets just force
// change the orders to test the delegate pattern
class OrderBook {
var delegate: OrderUpdatedDelegate?
var orders: [Int] = [Int]()
init(orders: [Int]) {
self.orders = orders
}
func changeOrders() {
self.orders = [7,8,1]
print ("updated orders to -> \(orders)")
delegate?.ordersDidUpdate(orders)
}
}
struct Factory {
var orders: [Int] = [Int]()
init(orders: [Int]) {
self.orders = orders
}
}
extension Factory: OrderUpdatedDelegate {
mutating func ordersDidUpdate(_ orders: [Int]) {
print ("recieved: \(orders)")
self.orders = orders
}
}
var shop = Factory(orders: [1,2,3])
let book = OrderBook.init(orders: shop.orders)
book.changeOrders()
print ("\nBook.orders = \(book.orders)")
print ("Shop.orders = \(shop.orders)")
Output:
Book.orders = [7, 8, 1]
Shop.orders = [1, 2, 3]
Again, I know the reason is because I've declared factory to be a struct; but I'm wondering if its possible to use a delegate pattern to mutate the orders array within the struct?
If not, I'll change it to a class; but I appreciate any feedback on this.
With thanks
There are 2 problems with your code, both of which needs fixing for it to work:
using a value type
not setting the delegate
Once you set the delegate, you'll see ordersDidUpdate actually getting called, but shop.orders will still have its original value. That is because as soon as you mutate your Factory, the delegate set on OrderBook will be a different object from the mutated Factory, which was updated in the delegate call ordersDidUpdate.
Using a reference type fixes this issue.
Couple of things to keep in mind when you switch to a class delegate. Make your OrderUpdatedDelegate be a class-bound protocol, then remove mutating from the function declaration. And most importantly, always declare class-bound delegates as weak to avoid strong reference cycles.
protocol OrderUpdatedDelegate: class {
func ordersDidUpdate(_ orders: [Int])
}
// This class will handle all the validation to do with
// orders array, but for now; lets just force
// change the orders to test the delegate pattern
class OrderBook {
weak var delegate: OrderUpdatedDelegate?
var orders: [Int] = []
init(orders: [Int]) {
self.orders = orders
}
func changeOrders() {
self.orders = [7,8,1]
print ("updated orders to -> \(orders)")
delegate?.ordersDidUpdate(orders)
}
}
class Factory {
var orders: [Int] = []
init(orders: [Int]) {
self.orders = orders
}
}
extension Factory: OrderUpdatedDelegate {
func ordersDidUpdate(_ orders: [Int]) {
print("receieved: \(orders)")
self.orders = orders
print("updated order: \(self.orders)")
}
}
var shop = Factory(orders: [1,2,3])
let book = OrderBook(orders: shop.orders)
book.delegate = shop
book.changeOrders()
print ("Book.orders = \(book.orders)")
print ("Shop.orders = \(shop.orders)")
As you said since Factory is a struct, when setting OrderBook delegate its already copied there so the delegate is actually a copy of your original factory instance.
A class is the appropriate solution for this.
Ok so I have a function in a few of my Classes for different table cells that looks like this.
func open() {
let array = [position, salary, jobDescription, applyButton]
switch openVerb {
case false:
for i in array {
i.hidden = true
}
openVerb = true
case true:
for i in array {
i.hidden = false
}
openVerb = false
}
}
Now this is used a few times so I wanted to make it a universal function so I am not repeating myself. The issue is that each class has a different size array made up of UIButton and UILabel. The function switches on a Bool and then makes all objects in the array hidden or shown.
I tried using generic but I think I have made a big mistake. Help please.
func open(inout theSwitch: Bool, inout array: [<T>:UIView]) {
switch theSwitch {
case false:
for i in array {
i.hidden = true
}
theSwitch = true
case true:
for i in array {
i.hidden = false
}
theSwitch = false
}
}
Why you think you need generics here? Here how I see your function:
func open(inout theSwitch: Bool, array: [UIView]) {
for item in array {
item.hidden = !theSwitch
}
theSwitch = !theSwitch
}
Usage:
var theSwitch = true
var array = [UIButton]()
var mixedViewsArray = [UIButton(), UILabel(), UIView()]
// switch time:
open(&theSwitch, array: array)
open(&theSwitch, array: mixedViewsArray)
You don't need to use reference to original array, since you only change properties in objects in that array, so you only use references from it. open function will work with any subclass of UIView class, like UIButton or UITableViewCell (you don't even need cast mixedViewsArray to proper type, its already [UIView]).
I think you may want to do something similar to this if you want to use generics. See the link for more info on generics and also review swift evolution on GitHub to see if any changes occur in Swift 3. You might also consider using the variant where you declare a variable of Generic type var elements = Array<UIView>() or maybe even var elements = Array<UIControl>() depending on what class best supplies the properties you'd like to access. Followed by passing that variable array to a function with this signature func openTableViewCells(inout theSwitch: Bool, inout array: [UIControl]){}. Please note that I haven't tested these and feel free to refer to the documentation. Another consideration might be to use a set of protocols on the parameter that you are passing... Check the header files for a set of protocols that encompass UIView or UIControl.
Also instead of looping, check out the forEach function where you may be able to update the array contents more efficiently.
...
(generic function)
func openTableViewCells<T:UIView>(inout theSwitch: Bool, inout array: [T]) {
switch theSwitch {
case false:
for i in array {
i.hidden = true
}
theSwitch = true
case true:
for i in array {
i.hidden = false
}
theSwitch = false
}
}
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html
I have a model class written in Objective-C that I'm converting to Swift. It contains an NSMutableArray internally, but the method signature for the getter, as well as the actual return value, are NSArray. When called, it creates an immutable copy to return.
Essentially, I want callers to be able to iterate/inspect the container, but not modify it. I have this test snippet:
class Container {
internal var myItems = [String]()
func sayHello() {
"I have: \(myItems)"
}
}
let cont = Container()
cont.myItems.append("Neat") // ["Neat"]
cont.sayHello() // This causes sayHello() to print: "I have: [Neat]"
var isThisACopy = cont.myItems
isThisACopy.append("Huh") // ["Neat", "Huh"]
cont.sayHello() // This ALSO causes sayHello() to print: "I have: [Neat]"
I've been trying to find a way to override the getter for myItems so that it returns an immutable copy, but can't seem to determine how.
Attempt #1
This produces a compiler error: Function produces expected type '_ArrayBuffer<(String)>'; did you mean to call it with '()'?
internal var myItems = [String]() {
var copy = [String]()
for item in ... { // What to use in the ...?
copy.append(item)
}
return copy
}
Attempt #2
This also produces a compiler error, because I'm (understandably) redefining the generated getter Invalid redeclaration of 'myItems()':
internal func myItems() -> [String] {
var copy = [String]()
for item in myItems {
copy.append(item)
}
return copy
}
Try this:
class Container {
private var _myItems: [String] = ["hello"]
internal var myItems: [String] {
return _myItems
}
}
let cont = Container()
cont.myItems.append("Neat") //not allowed
It uses a private stored property and a computed property that returns an immutable copy. It's not possible for a stored property to use custom getters.
A better way to expose mutable properties as immutable:
class Container {
private (set) internal var myItems: [String]
}