Make a shallow copy of collection classes (Array, Dictionary) in swift, not a deep copy. - ios

There seems to be a lot of confusion and different opinions on this out there, I want to know, is that possible in swift to make a shallow copy of an object and not a deep copy.
I checked in JAVA - http://www.jusfortechies.com/java/core-java/deepcopy_and_shallowcopy.php, It's clearly explained the difference between shallow copy and deep copy with example, but I didn't got kind of example in swift.
I tried initWithArray:copyItems: to check what difference does it make when I'm changing the Boolean flag of copyItems, but none of the difference I see. I supposed to have difference of shallow or deep copy by changing the copyItems flag, but I was wrong it creates a deep copy always.
Please check my below code, I cloned the array object arrayObject1 to arrayObject2 using initWithArray:copyItems: by setting copyItems: to true. I changed the arrayObject2 0th string object to new string object and arrayObject2 0th object changed, but arrayObject1 0th object didn't changed. whereas if I'm copying it by assigning copyItems: to false, then also I'm having the same result.
So how to achieve a shallow copy and if it's not by initWithArray:copyItems: then what copyItems: flag making impact on result.
func createAnArrayUsingArrayCopyItems(){
let name = "albort"
let arrayObject1 = NSArray.init(objects: name, 15)
let arrayObject2 = NSMutableArray.init(array: arrayObject1 as [AnyObject], copyItems: true)
arrayObject2[0] = "john"
print(arrayObject1)
print(arrayObject2)
}
Output ->

It happens because name is a Swift String instance. String is struct, thus it's a value type. That is because it's always copied by value. arrayObject1 as [AnyObject] is a conversion to swift Array (a struct too) with String objects within.
So it's not trivial to get a shallow copy using such structures like Array and String in Swift.
I can only come up with an idea of boxing struct instances into a class wrapper:
class StringBox : CustomStringConvertible, NSCopying {
var string: String
init(str: String) {
string = str
}
var description: String {
return "\(string)"
}
#objc func copyWithZone(zone: NSZone) -> AnyObject {
return StringBox(str: string)
}
}
func createAnArrayUsingArrayCopyItems(){
let name = StringBox(str: "albort")
let arrayObject1 = NSArray.init(objects: name, 15)
let arrayObject2 = NSMutableArray.init(array: arrayObject1 as [AnyObject], copyItems: false)
(arrayObject2[0] as! StringBox).string = "john"
print(arrayObject1[0])
print(arrayObject2[0])
}
createAnArrayUsingArrayCopyItems()
It gives me a following output:
john
john

arrayObject2[0] = "john"
This is changing the item at position 0 in array 2 - not the contents of the string which already exists at that location of the array. For that to happen it would need to be an array of mutable strings.
Indeed if you create the array with mutable strings and then append to the string at position 0 in array 2 you will see the same string instance in array 1 is also updated (because it's the same instance).
Editing the array instance isn't the same as editing the instances in the array.
The copy is shallow if you ask it to be. Technically a shallow and deep copy are sometimes the same thing of the data types are immutable.

Related

Swift: Struct Array vs Class Array

I have a swift array of struct and I am unable edit the first property, whereas I am able edit the first property with an array of class.
In order to edit the first object of the struct array, I have to do [0] then .first
I know structs are valued by type, class are value by reference. But I don't understand the different behavior. Can someone explain?
class PersonObj {
var name = "Dheearj"
}
struct Person {
var name = "Dheearj"
mutating func update(name: String){
self.name = name
}
}
var array = [Person(),Person()]
array[0].update(name:"dheeraj")
array[0].name = "yuuu"
array.first?.name = "dddddd" <--- "Error Here"
var array1 = [PersonObj(),PersonObj()]
array1.first!.name = "ttt"
print(array1.first?.name ?? "")
print(array.first?.name ?? "")
print(array.count)
Screenshot of the error message:
Mutating a struct stored within some other property behaves as though you've copied out the value, modified it, and overwrote it back into place.
Take this line for example: (I replaced the optional chaining with force unwrapping, for simplicity)
array.first!.name = "dddddd"
It behaves as though you did:
var tmp = array.first!
tmp.name = "dddddd"
array.first = tmp
It's easy to see what that doesn't work. Array.first, is a get-only property (it doesn't have a setter).
The case for classses works because the value stored in the array is a reference to the object, and the reference isn't changing (only the values within the object it refers to, which the array doesn't know or care about).

iOS Swift 3 - Argument labels '(of:)' do not match any available overloads Error

I'm getting the error message Argument labels '(of:)' do not match any available overloads. Below is the code I'm using.
let prefs = UserDefaults.standard
var id: String!
if var array = prefs.string(forKey: "myArray"){
if let index = array.index(of: id) {
array.remove(at: index)
prefs.setValue(array, forKey: "myArray")
}
}
I've seen a lot of answers on Stack Overflow with very similar code to that. So I'm not quite sure why this wouldn't be working.
Basically I'm just trying to remove the element in the array that = id then set that new array to the user defaults.
Update
Just updated the code above to show how array is getting defined. id is a string that is defined in a separate section.
By accessing prefs.string(forKey: "myArray"), you are getting a String, not an array of strings. You should use this:
if var prefs.array(forKey: "myArray") as? [String] { }
or
if var prefs.value(forKey: "myArray") as? [String] { }
Make sure to not forget putting as! [String], because the first method returns [Any], an which can contain objects of any type, not specifically String. Then your error should be solved, because index(of: ) can only be used on Arrays of specified types.
Hope it helps!
Just make an alt + Click on an "array" variable to make sure it is of type Array ([String]), not a String. To apply .index(of:) method it must be an array.
Like this:
String does not have a method .index(of:). That's what the error is pointing at. And sure make a cast to [String]? if it fits.

Declare an array of Int in Realm Swift

How can I declare an array of integers inside RLMObject?
Like :
dynamic var key:[Int]?
Gives the following error :
Terminating app due to uncaught exception 'RLMException', reason: ''NSArray' is not supported as an RLMObject property. All properties must be primitives, NSString, NSDate, NSData, RLMArray, or subclasses of RLMObject. See https://realm.io/docs/objc/latest/api/Classes/RLMObject.html for more information.'
Lists of primitives are not supported yet unfortunately. There is issue #1120 to track adding support for that. You'll find there some ideas how you can workaround that currently.
The easiest workaround is create a object to hold int values. Then the model to have a List of the object.
class Foo: Object {
let integerList = List<IntObject>() // Workaround
}
class IntObject: Object {
dynamic var value = 0
}
Fortunately arrays of primitive types are now supported in Realm 3.0 and above. (Oct 31 2017)
You can now store primitive types or their nullable counterparts (more specifically: booleans, integer and floating-point number types, strings, dates, and data) directly within RLMArrays or Lists. If you want to define a list of such primitive values you no longer need to define cumbersome single-field wrapper objects. Instead, you can just store the primitive values themselves!
class MyObject : Object {
#objc dynamic var myString: String = ""
let myIntArray = List<Int>()
}
Source: https://realm.io/blog/realm-cocoa-reaches-3-0/
The accepted offer is very costly in term of memory.
You might get a List of very big "n" of objects.
It's not a matter of right and wrong but I think it's good to write here a different workaround.
Another approach:
I decided to use a single string to represent an Int array.
In my Realm class I defined a variable:
dynamic var arrInt: String? = nil
And use it very easily:
let arrToSave = [0, 1, 33, 12232, 394]
<MY_CUSTOM_REALM_CLASS>.arrInt = arrToSave.map { String(describing: $0) }.joined(separator: ",")
And the way back:
let strFetched = <MY_CUSTOM_REALM_CLASS>.arrInt
let intArray = strFetched.components(separatedBy: ",").flatMap { Int($0) }
Will be happy to hear your feedback, as I think this approach is better.
As the error message states, you have to use RLMArray - or rather it's swift equivalent List.
See: Realm docs

Turn a string into a variable

Hello I have a for in loop where elements is the variable being changed and in this case "elements" is a string but there is a corresponding variable out side of the for in loop that has the same name as the string called elements. So what I mean is out side there is a Var time = [some,text,words] and theres a for in loop that calls a STRING named "time" and I would like to know how to convert the string in the for in loop into the variable by some how taking off the "'s (not that simple I know) without specifically saying "time"(the variable) but instead converting the "elements"(which is the string 'time') string into the variable. I hope I was clear enough if I'm not making sense I'll try again.
You cannot refer to local variables dynamically by their names in Swift. This would break a lot of compiler optimizations as well as type safety if you could.
You can refer to object properties by their names if the class conforms to key-value coding. For example:
class X : NSObject {
let time = ["some", "text", "words"]
func readWordsFromProp(name: String) -> String {
guard let list = self.valueForKey(name) as? [String] else {
return ""
}
var result = ""
for word in list {
result += word
}
return result
}
}
let x = X()
print(x.readWordsFromProp("time"))
In general, there are better ways to do things in Swift using closures that don't rely on fragile name-matching. But KVC can be a very powerful tool

Swift: how to make array of mutable dictionaries? [duplicate]

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

Resources