Swift Array holding elements weakly - arrays

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 }
}
}

Related

How to make a function that changes the property of an instance?

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:))

How can a mutable dictionary be made observable in RxSwift

We would like to use a dictionary as observable to notify any subscribers upon changes. So far this is implemented as BehaviorRelay<[KeyType: ValueObjectType]>
Now we need to add/remove/change values of the dictionary inside the observable. We tried the following (in simplified terms), but it did not work:
let list = BehaviorRelay<[String: MyClassType]>.init([:])
let newElem = MyClassType()
list.value.updateValue(newElem, forKey: "anykey")
The compiler complains: Cannot use mutating member on immutable value: 'value' is a get-only property
The following works, but I find it cumbersome and probably inefficient performance wise:
let list = BehaviorRelay<[String: MyClassType]>.init([:])
let newElem = MyClassType()
let newList = list.value
newList.updateValue(newElem, forKey: "anykey")
list.accept(newList)
Typical subscribers for the list on the UI side would be e.g. a UITableView or UICollectionView.
Is there a better way to handle this?
Your second way is is the way to go. It can be improved cumbersome wise by writing like this:
let list = BehaviorRelay<[String: MyClassType]>.init(value: [:])
list.accept(list.value.merging(["anykey": MyClassType()]){ (_, new) in new })
If this has to be done too many times, the following extension can come in handy
extension Dictionary where Key == String, Value == MyClassType {
static func + (lhs: [String : MyClassType], rhs: [String : MyClassType]) -> [String : MyClassType] {
return lhs.merging(rhs) { (_, new) in new }
}
}
Now you can just do this list.accept(list.value + ["anykey": MyClassType()])
Please note that if the right side operand has a key that is also present in the left side operand, the right side value will override the left one. According to my understanding this is your desired behaviour.
Also the first way that you were trying would work with Variable. But Variable is considered deprecated now in favour of BehaviorRelay and value property of BehaviourRelay is get only.
After discussing with #JR in the comments, a generic extension can be written for BehaviorRelay where the Element is an Array/ Dictionary
extension BehaviorRelay {
func addElement<T>(element: T) where Element == [T] {
accept(value + [element])
}
func removeElement<T>(element: T) where Element == [T], T: Equatable {
accept(value.filter {$0 != element})
}
func addElement<T, U>(key: T, value: U) where Element == [T: U] {
accept(self.value.merging([key: value]) { (_, new) in new })
}
func removeElemnent<T>(key: T) where Element == [T: Any] {
accept(self.value.filter {dictElemnt in dictElemnt.key != key})
}
}

Creating a FIFO fixed size array in Swift

I'm attempting to create a FIFO array in swift. I want to create something that acts like this:
var Arr = FixedFIFOArray<Int>(maxSize:3)
Arr.append(1) //Arr = [1]
Arr.append(2) //Arr = [1,2]
Arr.append(3) //Arr = [1,2,3]
Arr.append(4) //Arr = [2,3,4] <- the max size is fixed to 3, so any
additional values added remove old values
Other than this behavior, it should act like an array: allow slicing, indexing, iterating in for loops, etc.
In any other language, this would be a job for subclassing. We aren't changing much, just adding an initializer and amending a function or two. However, in Swift, we can't subclass array. What is the best way to do this? Do I need to implement every protocol that array implements, and just pass the associated functions off to an array? Something like this:
struct FixedFIFOArray<T> {
var _maxSize: Int
var _array: [T] = []
init(maxSize: Int) {
self._maxSize = maxSize
}
}
extension FixedFIFOArray : Collection {
//...
}
extension FixedFIFOArray : RandomAccessCollection {
//...
}
extension FixedFIFOArray : Sequence {
//...
}
// etc...
This seems like a lot of work to do something so simple. What am I missing?
It is not as bad as it seems, because many protocol requirements have
default implementations.
Unfortunately I do not have the perfect recipe to find a "minimal" implementation.
Some information can be found in the
RandomAccessCollection documentation
where some methods are marked as "Required. Default implementation provided."
You can also start with an empty implementation extension FixedFIFOArray : RandomAccessCollection {} and study the error messages or try the Fix-its.
With "Jump to Definiton" in the Xcode editor one can inspect the protocol definitions and extension methods.
In your case it turned out that it suffices to implement
startIndex, endIndex, and subscript:
extension FixedFIFOArray : RandomAccessCollection {
var startIndex: Int {
return _array.startIndex
}
var endIndex: Int {
return _array.endIndex
}
subscript(i: Int) -> T {
return _array[i]
}
}
Or, if you need a read-write subscript:
subscript(i: Int) -> T {
get {
return _array[i]
}
set {
_array[i] = newValue
}
}

How do I pass a closure that returns an a value that conforms to Equatable into a function

I'm trying to create an extension of Array that returns a new array of unique items based on the items with the closure applied.
For example: If I had an array of Apple where apple has properties name and origin, to get one Apple of each origin I would call apple.uniqued(on: { $0.origin })
Here's the code I have so far:
extension Array where Element: Equatable {
func uniqued(on extract: (Element) -> Equatable) { // A
let elementsAndValues = map { (item: $0, extracted: extract($0)) } // 1
var uniqueValues: [Element] = []
var uniqueExtracts: [Equatable] = [] // A
elementsAndValues.forEach { (item, extracted) in
if !uniqueExtracts.contains(extracted) { // 3, B
uniqueValues += [item]
uniqueExtracts += [extracted]
}
}
return uniqueValues
}
}
This should work as follows:
Create an array of tuples with both the original elements (item) and the elements with the closure applied (extracted)
If uniqueExtracts doesn't contain the item, add it and add the item to the uniqueItems array.
The errors I'm getting are:
A) "Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements" (twice)
B) "Missing argument label 'where:' in call"
I'm using the latest version of Xcode. Any advice would be a lot of help. Many thanks.
You have multiple problems that mix together to create the errors you’re seeing. What you should do is use a generic.
extension Array
{
func uniqued<T:Equatable>(on extract:(Array.Element) -> T) -> [Array.Element]
{
let elementsAndValues = self.map{ (item: $0, extracted: extract($0)) }
var uniqueValues:[Element] = []
var uniqueExtracts:[T] = []
for (item, extracted) in elementsAndValues
{
if !uniqueExtracts.contains(extracted)
{
uniqueValues.append(item)
uniqueExtracts.append(extracted)
}
}
return uniqueValues
}
}
The <T:Equatable> declares a generic type parameter T that conforms to Equatable. Then the function signature can expect a closure that returns some generic type T that we know conforms to Equatable from the type constraint in the angle brackets. You also have to change every occurrence of Equatable to the generic parameter T, since Equatable isn’t a real type; see my answer here. If you do that the code should compile.
You have a few other things you should probably change:
Instead of using elementsAndValues.forEach(:), use a for <pattern> in list {} loop.
Although this is contentious, you should probably use the Array().append(:) method instead of += concatenation when adding one element to an array. In the case of +=, as opposed to +, this is purely to convey intent.
You did not declare a return type for your function, so the compiler assumes it returns Void, and so the return uniqueValues statement will cause a compiler error. Add a -> [Array.Element] to the function to fix this.
where Element:Equatable as a constraint on Array itself is superfluous. You are using a key function to determine equability, so whether the elements themselves are equatable is irrelevant.
You may want to use a Set or some other hashed data structure instead of a uniqueExtracts array. Testing for membership in an array is an O(n) operation.
I would do this with a group(by:) function, which would group each element in the sequence by a given key (e.g. the origin), yielding a Dictionary mapping keys to groups (arrays of elements in the group). From there, I would just map over the dictionary and just get the first element in each group.
public extension Sequence {
public typealias Element = Iterator.Element
public typealias Group = [Element]
public func group<Key: Hashable>(by deriveKey: (Element) -> Key) -> [Key: Group] {
var groups = [Key: Group]()
for element in self {
let key = deriveKey(element)
if var existingArray = groups[key] { // Group already exists for this key
groups[key] = nil //performance optimisation to prevent CoW
existingArray.append(element)
groups[key] = existingArray
}
else {
groups[key] = [element] // Create new group
}
}
return groups
}
}
struct Apple {
let name: String
let origin: String
}
let apples = [
Apple(name: "Foo", origin: "Origin 1"),
Apple(name: "Bar", origin: "Origin 1"),
Apple(name: "Baz", origin: "Origin 2")
]
let firstAppleInEachOrigin = apples.group(by: {$0.origin}).flatMap{ _, group in group.first }
firstAppleInEachOrigin.forEach{ print($0) }

Swift 2 Array Contains object?

Why isn't this working? I can use array.contains() on a String but it doesn't work for an Object.
var array = ["A", "B", "C"]
array.contains("A") // True
class Dog {
var age = 1
}
var dogs = [Dog(), Dog(), Dog()]
var sparky = Dog()
dogs.contains(sparky) // Error Cannot convert value of type 'Dog' to expected argument type '#noescape (Dog) throws -> Bool
Your Dog needs to implement Equatable.
class Dog: Equatable {
var age = 1
}
func == (lhs: Dog, rhs: Dog) -> Bool {
return lhs.age == rhs.age
}
To really explain what's happening there, first we have to understand there are two contains methods on Array (or better said, on SequenceType).
func contains(_ element: Self.Generator.Element) -> Bool
with constraints
Generator.Element : Equatable
and
func contains(#noescape _ predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Bool
The first one basically searches for a given element in the array using ==. The second one uses a closure that returns a Bool to search for elements.
The first method cannot be used because Dog doesn't adopt Equatable. The compiler tries to use the second method but that one has a closure as the parameter, hence the error you are seeing.
Solution: implement Equatable for Dog.
If you are looking for object reference comparison, you can use a simple closure:
let result = dogs.contains({ $0 === sparky })
Swift
If you are not using object then you can user this code for contains.
let elements = [ 10, 20, 30, 40, 50]
if elements.contains(50) {
print("true")
}
If you are using NSObject Class in swift. This variables is according to my requirement. you can modify for your requirement.
var cliectScreenList = [ATModelLeadInfo]()
var cliectScreenSelectedObject: ATModelLeadInfo!
This is for a same data type.
{ $0.user_id == cliectScreenSelectedObject.user_id }
If you want to AnyObject type.
{ "\($0.user_id)" == "\(cliectScreenSelectedObject.user_id)" }
Full condition
if cliectScreenSelected.contains( { $0.user_id == cliectScreenSelectedObject.user_id } ) == false {
cliectScreenSelected.append(cliectScreenSelectedObject)
print("Object Added")
} else {
print("Object already exists")
}
This answer isn't relevant for the OP's question, but might be helpful to others who are confronted with the Swift error message
Cannot invoke 'contains' with an argument list of type '(whatever)'
But first a quick quiz: Can you spot the problem here?
internal class FrameworkAdminConnections {
private var _localConnectionKeys = [Int]()
... other code omitted
public func isLocalConnection(_ connectionKey : Int) {
return _localConnectionKeys.contains(connectionKey)
}
}
Swift kept telling me I couldn't invoke contains() with an argument list of type (Int), which was a very unhelpful error message, and I don't dare admit how long it took me to finally figure it out.
The real problem was that Swift's inference engine couldn't figure out what the result of the contains() method should be - because I'd stupidly not specified "-> Bool" on the isLocalConnection() method signature!

Resources