I am befuddled by the current array behavior in Swift 2.1. I have read the docs and many posts (which could be out-of-date) and am no closer to understanding.
Here's my code for separating a deck of cards into wildCards and non-wildcards:
let wildCards : [Card] = []
let nonWildCards : [NonJoker] = []
Meld.sortAndSeparateWildCards(wildCardRank, cards: self.cards, nonWildCards: nonWildCards, wildCards: wildCards)
...
static func sortAndSeparateWildCards(wildCardRank : Rank, var cards : [Card], var nonWildCards : [NonJoker], var wildCards : [Card]) {
//cards contains the list to be sorted
if cards.isEmpty {return}
nonWildCards.removeAll()
wildCards.removeAll()
for card in cards {
if (card.isWildCard(wildCardRank)!) {wildCards.append(card)}
else {nonWildCards.append(card as! NonJoker)}
}
cards = nonWildCards.sort(NonJoker.cardComparatorRankFirstDesc)
cards += wildCards
}
What I don't understand:
Xcode insists that I should change wildCards and nonWildCards to let constants, even though I am mutating the arrays by adding values to them (many of the posts I have read say that let behavior prevents adding to or removing elements from an Array)
I originally had these arrays passed as inout variables, because I thought that Arrays were passed by value and not by reference (the docs suggest that they are treated this way; see the bottom of this page https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-ID82)
EDIT: I thought the code was working, but I was confused.
Corrected code, per the answer below, with the bad behavior that isEmpty returns nil.
var wildCards : [Card] = []
var nonWildCards : [NonJoker] = []
Meld.sortAndSeparateWildCards(wildCardRank, cards: &self.cards, nonWildCards: &nonWildCards, wildCards: &wildCards)
...
static func sortAndSeparateWildCards(wildCardRank : Rank, inout cards : [Card], inout nonWildCards : [NonJoker], inout wildCards : [Card]) {
//cards contains the list to be sorted
if cards.isEmpty {return}
nonWildCards.removeAll()
wildCards.removeAll()
for card in cards {
if (card.isWildCard(wildCardRank)!) {wildCards.append(card)}
else {nonWildCards.append(card as! NonJoker)}
}
cards = nonWildCards.sort(NonJoker.cardComparatorRankFirstDesc)
cards += wildCards
}
Arrays are structs in Swift. In the way you've done it, the changes are not persisted out of the method. If you want to alter them in a function, you need to mark them as var in the declaration, and pass their pointer when calling the function. So something like this:
var wildCards = [Card]()
var nonWildCards = [NonJoker]()
static func sortAndSeparateWildCards(wildCardRank : Rank, inout cards : [Card], inout nonWildCards : [NonJoker], inout wildCards : [Card]) {}
Meld.sortAndSeparateWildCards(wildCardRank, cards: &self.cards, nonWildCards: &nonWildCards, wildCards: &wildCards)
You were right when assuming that arrays were being passed by value and not by reference. In Swift, arrays are value types - they are structs - which means that if you want to modify their contents, you need to declare them as var.
Now var has a broader meaning, and if it's applied to function arguments, it allows you to mutate those arguments within the function. However, keep in mind that you are mutating a copy of the array that was passed as argument.
So, in order for the changes that you apply to an array within a function be visible to the caller of that function, you need to declare the parameters as inout, which passes them by reference instead of value.
Related
In my app I have two struct arrays and I want to remove common items from one of them. My struct:
struct PeopleSelectItem {
var name = ""
var id = ""
var added = false
}
My arrays:
var people : [PeopleSelectItem] = []
var selectedPeople : [PeopleSelectItem] = []
I want to remove items from people array if they exist (compare by id) on selectedPeople array.
I tried several array filtering and converting to set but none of them worked. What can I do here?
Thanks!
Get an array of all ids in selectedPeople
let selectedPeopleIDs = selectedPeople.map(\.id)
Filter the items whose id is not in the array
let filteredPeople = people.filter { !selectedPeopleIDs.contains($0.id) }
If you know that people equal each other if the id is the same then you can conform your struct to the Equatable protocol and you can use the array filter method.
struct PeopleSelectItem : Equatable {
var name = ""
var id = ""
var added = false
}
func ==(lhs: PeopleSelectItem, rhs: PeopleSelectItem) -> Bool {
return lhs.id == rhs.id
}
func filterPeople() {
//swift 2, 3:
people = people.filter{!selectedPeople.contains($0)}
//swift older versions:
people = people.filter{!contains(selectedPeople, $0)}
}
If people might have a significant amount of entries, performance should be considered. So, instead of searching with an n^2 algorithm, you should make use of Swifts dictionary and the corresponding hash-search to find items.
If Id is unique for people then I would store them in a dictionary like:
var peopleDict: [String:PeopleSelectItem] = [:]()
You can easily convert from the array you have to this dictionary:
people.foreach {peopleDict[$0.id] = $0}
With this dictionary it's very easy to delete single entries:
selectedPeople.foreach {peopleDict.remove($0.id)}
Optionally to switch back to an array for people you just say:
let filteredPeople = peopleDict.values as [PeopleSelectItem]
Remarks
I assumed, that selectedPeople is smaller then the base of all people. If this is not the case, you should pu selectedPeople in a dictionary.
did I say I like this Spark like api? I think I do so.
I just wrote that code from top of my mind. If it is not completely syntactically correct let me know and I correct it.
I've essentially got two methods:
private func addBagToCollection(bag : VendorBag){
var array = arrayForService(bag.type)
array.append(bag)
}
And
func arrayForService(type : BAG_TYPE) -> Array<VendorBag>{
switch type{
case .bagTypeDryCleaning:
return dcBags
case .bagTypeWashAndFold:
return wfBags
case .bagTypeLaunderedShirt:
return lsBags
case .bagTypeHangDry:
return hdBags
case .bagTypeUnknown:
return unBags
}
}
The issue is that the array being referenced in addBagToCollection isn't saving the appended items, meaning that every time that I call the method, the size of the array is 0.
I initialize all my arrays at the top of the class:
var hdBags : Array<VendorBag> = [VendorBag]()
var wfBags : Array<VendorBag> = [VendorBag]()
var lsBags : Array<VendorBag> = [VendorBag]()
var dcBags : Array<VendorBag> = [VendorBag]()
var unBags : Array<VendorBag> = [VendorBag]()
but for some reason, the arrayForService function seems to either only return a copy of the array, or a newly initialized one. How do I return a reference to that array so that all changes made to it are kept even after exiting the function?
Got some more info:
So indeed it seems like arrays are copied when returned from functions--
Swift’s Array types are implemented as structures. This means that arrays
are copied when they are assigned to a new constant or variable, or when they are passed to a function or method.
So how do I return a reference to the actual array rather than a copy of it?
Thanks!
Unlike NSArray class, Swift arrays are struct types which means they are copied when they are passed into functions.
From docs:
Note
Structures are always copied when they are passed around in your code, and do not use reference counting.
You need to use inout keyword if you want your array to be passed by reference, not by copy:
private func addBagToCollection(inout array: Array<VendorBag>, bag: VendorBag) {
array.append(bag)
}
You can call that function as follows:
addBagToCollection(&arrayForService(bag.type), bag)
I'm trying to cast a 'filtered' array of a protocol type
I have a series of structs (Assessment, Level and Gate) that conform to several different Protocols - Stageable, Repeatable and Testable:
protocol Stageable
{
var index : Int { get }
var steps : [Step] { get }
}
protocol Testable
{
var threshold : Float { get }
}
protocol Repeatable
{
var sessions: Int { get }
}
struct Gate : Stageable, Testable, Repeatable
{
private(set) var index : Int
private(set) var steps : [Step]
private(set) var threshold : Float
private(set) var sessions : Int
}
struct Assessment : Stageable, Testable
{
private(set) var index : Int
private(set) var steps : [Step]
private(set) var threshold : Float
}
struct Level : Stageable, Repeatable
{
private(set) var index : Int
private(set) var steps : [Step]
private(set) var sessions : Int
}
Step is another struct. There are no classes being used.
These structs are populated just before being added to an array.
An array takes the form usually of [Assessment, Gate, Level, Level]. All the structs data is populated from an XML file.
In some instances I only want to review the 'Levels' in the array, so I do:
// stages = [Assessment, Gate, Level, Level, ...]
let levels = stages.filter{ $0 is Level }
If I query this, it looks fine e.g. levels.count is what I expect.
However, if I now want to cast the array to [Level] it crashes with the following error:
fatal error: can't unsafeBitCast between types of different sizes
Is this because I'm casting from a protocol to a struct type? I also feel like I've missed the key benefits of Protocols here and there must be a better way to do this.
Currently using Xcode 7 beta 5.
Casting arrays of structs is problematic because structs are value types. That means each element of an array of structs takes up the size of the struct in memory. This is different for arrays of standard objects because these are passed by reference. Each element in an array of objects is a reference (a pointer to a specific memory area).
To demonstrate this, consider the following
class ABC {
private var i = 0
private var j = 1
private var k = 2
}
print(sizeof(UIViewController))
print(sizeof(UIImage))
print(sizeof(NSObject))
print(sizeof(ABC))
Each of the print statements outputs 8 on my platform which is the size of a memory address (which is obviously different from the amount of memory occupied by instances of this class).
On the other hand, when I take the code from your question and do
print(sizeof(Stageable))
print(sizeof(Level))
I get 40 and 24 respectively which are the actual sizes of instances of these structs in memory. That means an array of type [Stageable] consists of chunks of 40 byte elements whereas an array of type [Level] consists of chunks of 24 byte elements. As a result you cannot cast between such array types because that would require the array's memory to be rewritten.
As an alternative, you can use the map method to force type conversion:
let levels = stages.filter({ $0 is Level }).map({ $0 as! Level })
The above can also be simplified by leveraging the flatMap method:
let levels = stages.flatMap({ $0 as? Level })
Well, when you execute this code:
let levels = stages.filter{ $0 is Level }
your levels type become [Stageable]. Now, to convert [Stageable] to [Level] you can use this code:
var l = levels.map{ $0 as! Level }
I’m new to Swift and have been having some troubles figuring out some aspects of Arrays and Dictionaries.
I have an array of dictionaries, for which I have used Type Aliases - e.g.
typealias myDicts = Dictionary<String, Double>
var myArray : [myDicts] = [
["id":0,
"lat”:55.555555,
"lng”:-55.555555,
"distance":0],
["id":1,
"lat": 44.444444,
"lng”:-44.444444,
"distance":0]
]
I then want to iterate through the dictionaries in the array and change the “distance” key value. I did it like this:
for dict:myDicts in myArray {
dict["distance"] = 5
}
Or even specifically making sure 5 is a double with many different approaches including e.g.
for dict:myDicts in myArray {
let numberFive : Double = 5
dict["distance"] = numberFive
}
All my attempts cause an error:
#lvalue $T5' is not identical to '(String, Double)
It seems to be acting as if the Dictionaries inside were immutable “let” rather than “var”. So I randomly tried this:
for (var dict:myDicts) in myArray {
dict["distance"] = 5
}
This removes the error and the key is indeed assigned 5 within the for loop, but this doesn't seem to actually modify the array itself in the long run. What am I doing wrong?
The implicitly declared variable in a for-in loop in Swift is constant by default (let), that's why you can't modify it directly in the loop.
The for-in documentation has this:
for index in 1...5 {
println("\(index) times 5 is \(index * 5)")
}
In the example above, index is a constant whose value is automatically
set at the start of each iteration of the loop. As such, it does not
have to be declared before it is used. It is implicitly declared
simply by its inclusion in the loop declaration, without the need for
a let declaration keyword.
As you've discovered, you can make it a variable by explicitly declaring it with var. However, in this case, you're trying to modify a dictionary which is a struct and, therefore, a value type and it is copied on assignment. When you do dict["distance"] = 5 you're actually modifying a copy of the dictionary and not the original stored in the array.
You can still modify the dictionary in the array, you just have to do it directly by looping over the array by index:
for index in 0..<myArray.count {
myArray[index]["distance"] = 5
}
This way, you're sure to by modifying the original dictionary instead of a copy of it.
That being said, #matt's suggestion to use a custom class is usually the best route to take.
You're not doing anything wrong. That's how Swift works. You have two options:
Use NSMutableDictionary rather than a Swift dictionary.
Use a custom class instead of a dictionary. In a way this is a better solution anyway because it's what you should have been doing all along in a situation where all the dictionaries have the same structure.
The "custom class" I'm talking about would be a mere "value class", a bundle of properties. This was kind of a pain to make in Objective-C, but in Swift it's trivial, so I now do this a lot. The thing is that you can stick the class definition for your custom class anywhere; it doesn't need a file of its own, and of course in Swift you don't have the interface/implementation foo to grapple with, let alone memory management and other stuff. So this is just a few lines of code that you can stick right in with the code you've already got.
Here's an example from my own code:
class Model {
var task : NSURLSessionTask!
var im : UIImage!
var text : String!
var picurl : String!
}
We then have an array of Model and away we go.
So, in your example:
class MyDict : NSObject {
var id = 0.0
var lat = 0.0
var lng = 0.0
var distance = 0.0
}
var myArray = [MyDict]()
let d1 = MyDict()
d1.id = 0
d1.lat = 55.55
d1.lng = -55.55
d1.distance = 0
let d2 = MyDict()
d2.id = 0
d2.lat = 44.44
d2.lng = -44.44
d2.distance = 0
myArray = [d1,d2]
// now we come to the actual heart of the matter
for d in myArray {
d.distance = 5
}
println(myArray[0].distance) // it worked
println(myArray[1].distance) // it worked
Yes, the dictionary retrieved in the loop is immutable, hence you cannot change.
I'm afraid your last attempt just creates a mutable copy of it.
One possible workaround is to use NSMutableDictionary:
typealias myDicts = NSMutableDictionary
Have a class wrapper for the Swift dictionary or array.
class MyDictionary: NSObject {
var data : Dictionary<String,Any>!
init(_ data: Dictionary<String,Any>) {
self.data = data
}}
MyDictionary.data
Today I was just going through some basic swift concepts and was working with some examples to understand those concepts. Right now I have completed studying tuples.
I have got one doubt i.e, what is the need of using tuples ? Ya I did some digging on this here is what I got :
We can be able to return multiple values from a function. Ok but we can also do this by returning an array.
Array ok but we can return an multiple values of different types. Ok cool but this can also be done by array of AnyObject like this :
func calculateStatistics (scores:[Int])->[AnyObject]
{
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores
{
if score > max{
max = score
}
else if score < min{
min = score
}
sum += score
}
return [min,max,"Hello"]
}
let statistics = calculateStatistics([25,39,78,66,74,80])
var min = statistics[0]
var max = statistics[1]
var msg = statistics[2] // Contains hello
We can name the objects present in the tuples. Ok but I can use a dictionary of AnyObject.
I am not saying that Why to use tuples when we have got this . But there should be something only tuple can be able to do or its easy to do it only with tuples. Moreover the people who created swift wouldn't have involved tuples in swift if there wasn't a good reason. So there should have been some good reason for them to involve it.
So guys please let me know if there's any specific cases where tuples are the best bet.
Thanks in advance.
Tuples are anonymous structs that can be used in many ways, and one of them is to make returning multiple values from a function much easier.
The advantages of using a tuple instead of an array are:
multiple types can be stored in a tuple, whereas in an array you are restricted to one type only (unless you use [AnyObject])
fixed number of values: you cannot pass less or more parameters than expected, whereas in an array you can put any number of arguments
strongly typed: if parameters of different types are passed in the wrong positions, the compiler will detect that, whereas using an array that won't happen
refactoring: if the number of parameters, or their type, change, the compiler will produce a relevant compilation error, whereas with arrays that will pass unnoticed
named: it's possible to associate a name with each parameter
assignment is easier and more flexible - for example, the return value can be assigned to a tuple:
let tuple = functionReturningTuple()
or all parameters can be automatically extracted and assigned to variables
let (param1, param2, param3) = functionReturningTuple()
and it's possible to ignore some values
let (param1, _, _) = functionReturningTuple()
similarity with function parameters: when a function is called, the parameters you pass are actually a tuple. Example:
// SWIFT 2
func doSomething(number: Int, text: String) {
println("\(number): \(text)")
}
doSomething(1, "one")
// SWIFT 3
func doSomething(number: Int, text: String) {
print("\(number): \(text)")
}
doSomething(number: 1, text: "one")
(Deprecated in Swift 2) The function can also be invoked as:
let params = (1, "one")
doSomething(params)
This list is probably not exhaustive, but I think there's enough to make you favor tuples to arrays for returning multiple values
For example, consider this simple example:
enum MyType {
case A, B, C
}
func foo() -> (MyType, Int, String) {
// ...
return (.B, 42, "bar")
}
let (type, amount, desc) = foo()
Using Array, to get the same result, you have to do this:
func foo() -> [Any] {
// ...
return [MyType.B, 42, "bar"]
}
let result = foo()
let type = result[0] as MyType, amount = result[1] as Int, desc = result[2] as String
Tuple is much simpler and safer, isn't it?
Tuple is a datastructure which is lighter weight than heterogeneous Array. Though they're very similar, in accessing the elements by index, the advantage is tuples can be constructed very easily in Swift. And the intention to introduce/interpolate this(Tuple) data structure is Multiple return types. Returning multiple data from the 'callee' with minimal effort, that's the advantage of having Tuples. Hope this helps!
A tuple is ideally used to return multiple named data from a function for temporary use. If the scope of the tuple is persistent across a program you might want to model that data structure as a class or struct.