Make `Collection` protocol unique by Element property - ios

I have been trying to make this extension generic, without any success. Any ideas how to improve it? (I would like to get rid of the hardcoded WKBackForwardListItem and url part and just accept any property) But I am afraid this is not possible just yet?
extension Collection where Iterator.Element: WKBackForwardListItem {
func unique() -> [Iterator.Element] {
var seen: [Iterator.Element] = []
for element in self {
if seen.map({ $0.url }).contains(element.url) == false {
seen.append(element)
}
}
return seen
}
}
Ideally the signature will end up like this
func unique(by property: Property) -> [Iterator.Element] {
Update
Given the nature of the array, an WKWebView history. It is very important to preserve the order of the array. So every use of Set is out of the question.
Update 2
Resulting code was, I lost the Collection protocol, but that is ok for this example.
extension Array where Iterator.Element: Equatable, Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
return NSOrderedSet(array: self).array as! [Iterator.Element]
}
}
Thanks!

You don't need to create this extension of Collection. What you need is a Set. The only thing you need to make sure is that the items in your array conform to the Equatable protocol.
Sample:
let food = ["taco", "apple", "pizza", "taco"]
let uniqueFood = Set(food) // ["taco", "apple", "hot dog"]
Then all you need to do is create an Array from that Set:
let foodArray = Array()
For your use case WKBackForwardListItem does conform to Equatable so you won't need to do anything special for that.
In order to maintain the order of your data Apple has provided NSOrderedSet. This has the uniqueness that Set offers while maintaining the order Array provides.
I hope this helps.

You're looking for NSOrderedSet (and NSMutableOrderedSet). It has the uniqueness of Set with the ordering of Array! See Apple's documentation.

Related

iOS13 DiffableDataSource Invalid parameter not satisfying: indexPath || ignoreInvalidItems

I'm converting my collection view to new iOS13 UICollectionViewDiffableDataSource... so I need to update cell information on demand.
Here is my code:
let snap = self.diffDataSouce.snapshot
snap?.reloadItems(withIdentifiers: [itemToUpdate]) //reload cell info
self.diffDataSouce.apply(snap, animatingDifferences: true)
But I get Invalid parameter not satisfying: indexPath || ignoreInvalidItems ...why?
My current snap contains itemToUpdate and also my array of models...
I think it's because snap.indexOfItemIdentifier(itemToUpdate) returns not found (NSNotFound)...but that's should be impossible according data model.
Have you some hints?
Your data model has to conform to Hashable and Equatable, so that the diffing algorithm can track changes between snapshots.
If there is an issue with a collision between two objects, or you have implemented either of those protocols in such a way as to allow for two objects to appear equal to the diffing algorithm, you will get a runtime assert exception.
I'd track down exactly how your model object have inherited or implemented those protocols.
For my case, reloading a hashable item was the problem. Instead I should have deleted hashable item and inserted anew. Following are the specifics.
My diffable ItemIdentifierType was of AnyHashable type as in:
var dataSource: UICollectionViewDiffableDataSource<AHashableEnum, AnyHashable>!
And whenever I wanted to reload a single item like the author of this exchange, I was experiencing the crash:
var currentSnapshot = dataSource.snapshot()
currentSnapshot.reloadItems([hashableItemThatGotUpdated])
dataSource.apply(currentSnapshot, animatingDifferences: true)
I realized that since ItemIdentifierType is of AnyHashable type, reloading an item is not permitted if its hashvalue has changed. Because the hash value of the new item does not exist in the current snapshot and therefore is not reloadable. Instead I should have deleted the old item from the current snapshot and inserted the new Hashable identifer instead:
var currentSnapshot = dataSource.snapshot()
currentSnapshot.insertItems(identifiers: [NewHashableItemIdentifier], beforeItem: OldHashableItemIdentifier)
currentSnapshot.deleteItems(identifiers: [OldHashableItemIdentifier])
dataSource.apply(currentSnapshot, animatingDifferences: true)
Have you implemented the static func == for your model? I had a similar issue using structs where equality is evaluated among all properties
I ran into this problem when I was attempting to reload items that had been deleted from my snapshot. This will get you the same or a similar error. Make sure you data model and snapshot are in sync to avoid this error.
In my case I was comparing two instances of different types.
public enum ChatSectionEnum: Hashable {
case loading
case messages(String)
var sectionId: AnyHashable {
switch self {
case .loading:
return UUID() // UUID type
case .messages(let id):
return id // String type
}
}
public static func == (lhs: ChatSectionEnum, rhs: ChatSectionEnum) -> Bool {
lhs.sectionId == rhs.sectionId // Error
}
public func hash(into hasher: inout Hasher) {
hasher.combine(sectionId)
}
}

How to keep the reference to an array after manipulation?

How do I keep the reference to an array after an items is appended?
Updated code example, because the prior example didn't seem to be clear enough.
class ViewController: UIViewController {
var numbers = Numbers.singleton.numbers
override func viewDidLoad() {
print(numbers.count)
Numbers.singleton.add(1)
print(numbers.count) // prints 0
print(Numbers.singleton.numbers.count) // prints 1
}
}
class Numbers {
static let singleton = Numbers()
var numbers: [Int]!
private init() {
numbers = []
}
func add(number: Int) {
numbers.append(number)
}
}
Arrays in Swift don't have "references". They are structs, and a struct is a value type. Your (badly named) arrayRef is a separate copy, not a reference to self.array.
Moreover, there is no good reason to want to do what you (seem to) want to do. To have two simultaneous references to a mutable array would be unsafe, since the array can be changed behind your back. The Swift design is sensible; use it, don't subvert it.

how to make a deep copy of a swift array of class objects

So I know that swift array is implemented as struct, and it will do automatically copy by itself.
I have write my class MyClass and I have implemented copyWithZone to make copy of it
However, my swift array contains my MyClass objects, like:
var array = [MyClass]()
when I wan to make a copy of that array like
var anotherArray = array
It still does not call MyClassObject.copyWithZone, and later if I change the object property in array, anotherArray will also reflect the change.
How can I make a copy of that without writing a for loop to iterate every object?
It's not duplicated as deep copy for array of objects in swift because I cannot use struct to rewrite my class.
As a simple statement, you could use code like this:
var copiedArray = array.map{$0.copy()}
Note that the term "deepCopy" is a little misleading for what you're talking about. What if the array is heterogeneous and contains other containers like arrays, dictionaries, sets, and other custom container and "leaf" objects? What you should really do is to create a protocol DeepCopiable and make it a requirement that any object that conforms to DeepCopiable require that any child objects also conform to the DeepCopiable protocol, and write the deepCopy() method to recursively call deepCopy() on all child objects. That way you wind up with a deep copy that works at any arbitrary depth.
If Element of an array is of reference type, conform that Element with NSCopying and implement copyWithZone method.
class Book {
var name: String
init(_ name: String) {
self.name = name
}
}
extension Book: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
return Book(name)
}
}
Now implement an array extension to make a deep copy
extension Array where Element: NSCopying {
func copy() -> [Element] {
return self.map { $0.copy() as! Element }
}
}
Test the code base
let b1 = Book("Book1")
let b2 = Book("Book2")
let list = [b1, b2]
let clonedList = list.copy()
clonedList is now copy of list and won't affect each-other if you make change to any of two.
Thanks Duncan for your solution, here is an extension using it.
Note that your class must adopt the NSCopying protocol. For how to do that see here https://www.hackingwithswift.com/example-code/system/how-to-copy-objects-in-swift-using-copy
extension Array {
func copiedElements() -> Array<Element> {
return self.map{
let copiable = $0 as! NSCopying
return copiable.copy() as! Element
}
}
}

How to remove an element of a given custom type value from an array in Swift

I want to remove element of custom type value from an array.
I want to pass a variant instance to function to remove it from array, I don't want to use removeAtIndex().
var favoriteVariants: [Variant]
func removeVariant(variant: Variant)
{
}
If Variant is Equatable and you only want to remove the first one that matches:
if let idx = favoriteVariants.indexOf(variant) {
favoriteVariants.removeAtIndex(idx)
}
If it isn’t Equatable and you have some other matching criteria to find just one to remove:
let idx = favoriteVariants.indexOf {
// match $0 to variant
}
if let idx = idx {
favoriteVariants.removeAtIndex(idx)
}
(these are assuming Swift 2.0 – if 1.2, it’s find(favoriteVariants, variant) instead of indexOf, and there isn’t a version that takes a closure, though it’s not too hard to write one)
If there are multiple ones you want to remove in one go:
favoriteVariants = favoriteVariants.filter {
// criteria to _keep_ any given favorite
}
All of these could be wrapped in extensions if what you want to do is general enough to justify it.

Filter array in a category/extension

For convenience, in a little experiment I am doing, I would like to extend Array to provide some app specific functionalities. This specific extension is not necessary best practice, but I am just curious about solving the Swift issues I am having.
Given a custom class Section, my extension (with partially extended closure) is:
extension Array {
func onlyFullSection() -> Array<Section> {
return self.filter {
(a:Section) -> Bool in
return a.isFullSection()
}
}
}
The error I get is: "T" is not a subtype of "Section".
I tried to fix it with all the sauces (changing types, casting, etc...) but still get similar errors.
This other variant:
extension Array {
func onlyFullSection() -> Array<Section> {
return (self as Array<Section>).filter {
(a:Section) -> Bool in
return a.isFullSection()
} as Array<Section>
}
throws: Cannot convert the expression's type 'Array<Section>' to type 'Array<Section>'
Any clue on what I am doing wrong? Thanks!
It is because you are extending T[] and not Section[]. That means that Int[] will also have your additional method. That might not be the best idea (since it will crash badly).
Swift currently does not allow you to extend a specialised generic type like Section[].
But if you really, really want to do it, here is one way to force a cast, use reinterpretCast, which Apple describes as follows
/// A brutal bit-cast of something to anything of the same size
func reinterpretCast<T, U>(x: T) -> U
You can use it like this:
extension Array {
func onlyFullSection() -> Section[] {
let sections : Section[] = reinterpretCast(self)
return sections.filter{ $0.isFullSection() }
}
}
But please don't.
The problem is that since the Array class is actually a generic Array<T>, you are extending Array<T>. And apparently you can't cast between generic types (i.e. <T> to <Section>), so I believe you'll have to make a new array and just push the appropriate objects into it.
17> extension Array {
18. func onlyFullSection() -> Array<Section> {
19. var ary = Array<Section>()
20. for s in self {
21. if (s as Section).isFullSection() {
22. ary.append(s as Section)
23. }
24. }
25. return ary
26. }
27. }
You could also create a helper method to convert between generic types for you, but in this instance that would just create an unnecessary temporary object.
Remember that the language is still heavily in flux so it's possible this will change. I think it's unlikely that we'll get the ability to cast between generic types, but I hope we'll at least be able to extend particular generics.

Resources