Iterate an Array W/ Explicit Object Type in Swift

Iterate an array w/ explicit object type in Swift

When you type a variable, you do:

var score: Int

And you do the same in a loop:

for score: Int in individualScores {

}

It seems to be pretty consistent in that regard.

Iterating using explicit types in Swift

Well ,for starters, familyNames returns NSStrings, not UIFonts. But, that aside, try casting the array, not the result of the iteration:

for family in UIFont.familyNames() as [String] {
println("fam: \(family)")
}

How iterate through all properties of different classes in Swift?

This is what reflection is for. You can add a property / method to the Car class that list all the properties of the current class, plus those of the superclass. When this property / method is dynamically applied to the subclasses, it will give what you are looking for.

We need an extension to Mirror that returns all properties of the current instance and those of its superclass recursively, taken from Martin R's answer:

extension Mirror {
func toDictionary() -> [String: Any] {
var dict = [String: Any]()

// Attributes of this class
for attr in self.children {
if let propertyName = attr.label {
dict[propertyName] = attr.value
}
}

// Attributes of superclass
if let superclass = self.superclassMirror {
for (propertyName, value) in superclass.toDictionary() {
dict[propertyName] = value
}
}

return dict
}
}

class Car: CustomDebugStringConvertible {
// ...

var debugDescription: String {
let dict = Mirror(reflecting: self).toDictionary()
return dict.map { "\($0.key) = \($0.value)" }.joined(separator: "\n")
}
}

Usage:

var allClassesArrays = [carrsArray, sportCarsArray, tankerCarsArray]
for car in allClassesArrays.flatMap({ $0 }) {
print(car.debugDescription, terminator: "\n\n")
}

You of course don't have to conform Car to CustomDebugStringConvertible or name the property debugDescription. I just find them convenient since the debugger will use the debugDescription when you type po car.

How to iterate a Swift [AnyObject] array with a downcast?

If the array of [AnyObject] is supposed to be entirely composed of the type you want, you can do a en-mass cast:

func tableView(tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [AnyObject]) {
let currentSort = tableView.sortDescriptors as? [NSSortDescriptor]
for s in currentSort ?? [] {
println("keyPath: \(s.key()), ascending: \(s.ascending)")
}
}

This uses [] to substitute an empty array for the nil you get if it turns out one or more of the AnyObject were not of the right type – you’d still need an if…else if you wanted explicit error handling rather than silent failure.

Alternatively if you want your program to trap in case of failure, you could use as! but then it’s a bit of a shame if you find this out in production.

By the way, if the fact that you have to come up with a new name for the unwrapped value was part of what was bothering you, you can just give it the same name:

let x: Int? = 1
if let x = x {
println(x)
}

which declares a local unwrapped x which masks the outer x within the if.

If some but not all might be of the right type and you only want to operate on those, mapSome is a handy array extension to have around:

extension Array {
func mapSome<U>(transform: T->U?) -> [U] {
return lazy(self).map(transform)
.filter { $0 != nil }
.map { $0! }.array
}
}

func tableView(tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [AnyObject]) {
let currentSort = tableView.sortDescriptors.mapSome { $0 as? NSSortDescriptor }
for s in currentSort {
println("keyPath: \(s.key()), ascending: \(s.ascending)")
}
}

Create an object array with few object properties of another object array using map

If you wanted to use map, you could do something like the following:

class Employee {
var id: String?
var name: String?
var role: String?
var age: Int?
var salary: Int?
}

var employeeList: [Employee]

var employeeModifiedList = employeeList.map {
(id: $0.id, name: $0.name, age: $0.age)
}

Type cast array elements in for loop

Try this:

for result in results as [XYZClass] {
// Do stuff to result
}

Iterating with for .. in on a changing collection

The documentation for IteratorProtocol says "whenever you use a for-in loop with an array, set, or any other collection or sequence, you’re using that type’s iterator." So, we are guaranteed that a for in loop is going to be using .makeIterator() and .next() which is defined most generally on Sequence and IteratorProtocol respectively.

The documentation for Sequence says that "the Sequence protocol makes no requirement on conforming types regarding whether they will be destructively consumed by iteration." As a consequence, this means that an iterator for a Sequence is not required to make a copy, and so I do not think that modifying a sequence while iterating over it is, in general, safe.

This same caveat does not occur in the documentation for Collection, but I also don't think there is any guarantee that the iterator makes a copy, and so I do not think that modifying a collection while iterating over it is, in general, safe.

But, most collection types in Swift are structs with value semantics or copy-on-write semantics. I'm not really sure where the documentation for this is, but this link does say that "in Swift, Array, String, and Dictionary are all value types... You don’t need to do anything special — such as making an explicit copy — to prevent other code from modifying that data behind your back." In particular, this means that for Array, .makeIterator() cannot hold a reference to your array because the iterator for Array does not have to "do anything special" to prevent other code (i.e. your code) from modifying the data it holds.

We can explore this in more detail. The Iterator type of Array is defined as type IndexingIterator<Array<Element>>. The documentation IndexingIterator says that it is the default implementation of the iterator for collections, so we can assume that most collections will use this. We can see in the source code for IndexingIterator that it holds a copy of its collection

@frozen
public struct IndexingIterator<Elements: Collection> {
@usableFromInline
internal let _elements: Elements
@usableFromInline
internal var _position: Elements.Index

@inlinable
@inline(__always)
/// Creates an iterator over the given collection.
public /// @testable
init(_elements: Elements) {
self._elements = _elements
self._position = _elements.startIndex
}
...
}

and that the default .makeIterator() simply creates this copy.

extension Collection where Iterator == IndexingIterator<Self> {
/// Returns an iterator over the elements of the collection.
@inlinable // trivial-implementation
@inline(__always)
public __consuming func makeIterator() -> IndexingIterator<Self> {
return IndexingIterator(_elements: self)
}
}

Although you might not want to trust this source code, the documentation for library evolution claims that "the @inlinable attribute is a promise from the library developer that the current definition of a function will remain correct when used with future versions of the library" and the @frozen also means that the members of IndexingIterator cannot change.

Altogether, this means that any collection type with value semantics and an IndexingIterator as its Iterator must make a copy when using using for in loops (at least until the next ABI break, which should be a long-way off). Even then, I don't think Apple is likely to change this behavior.

In Conclusion

I don't know of any place that it is explicitly spelled out in the docs "you can modify an array while you iterate over it, and the iteration will proceed as if you made a copy" but that's also the kind of language that probably shouldn't be written down as writing such code could definitely confuse a beginner.

However, there is enough documentation lying around which says that a for in loop just calls .makeIterator() and that for any collection with value semantics and the default iterator type (for example, Array), .makeIterator() makes a copy and so cannot be influenced by code inside the loop. Further, because Array and some other types like Set and Dictionary are copy-on-write, modifying these collections inside a loop will have a one-time copy penalty as the body of the loop will not have a unique reference to its storage (because the iterator will). This is the exact same penalty that modifying the collection outside the loop with have if you don’t have a unique reference to the storage.

Without these assumptions, you aren't guaranteed safety, but you might have it anyway in some circumstances.

Edit:

I just realized we can create some cases where this is unsafe for sequences.

import Foundation

/// This is clearly fine and works as expected.
print("Test normal")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x {
print(i)
}
}

/// This is also okay. Reassigning `x` does not mutate the reference that the iterator holds.
print("Test reassignment")
for _ in 0...10 {
var x: NSMutableArray = [0,1,2,3]
for i in x {
x = []
print(i)
}
}

/// This crashes. The iterator assumes that the last index it used is still valid, but after removing the objects, there are no valid indices.
print("Test removal")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x {
x.removeAllObjects()
print(i)
}
}

/// This also crashes. `.enumerated()` gets a reference to `x` which it expects will not be modified behind its back.
print("Test removal enumerated")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x.enumerated() {
x.removeAllObjects()
print(i)
}
}

The fact that this is an NSMutableArray is important because this type has reference semantics. Since NSMutableArray conforms to Sequence, we know that mutating a sequence while iterating over it is not safe, even when using .enumerated().

Iterating through references to variables in Swift

var a = false
var b = false
var c = false

mutateValues(&a, &b, &c) { n in
n = true
}

print(a, b, c) // will be printed "true true true"

func mutateValues<Value>(_ values: UnsafeMutablePointer<Value>..., mutate: (inout Value) -> Void) {
values.forEach {
mutate(&$0.pointee)
}
}

How do I work with an array of swift subclass types?

First of all, you did everything right in your creation of an Array of subclasses, and your for-in loop is correct as well. Your error is a simple typo. You wrote registerSubClass when you meant to write registerSubclass. I repeat, this is the proper way to work with an array of subclasses.



Related Topics



Leave a reply



Submit