How to pass array by reference without function ? swift - ios

I have this class:
class MainView:UIView{
var categories:[Category]!
}
i want to set the categories arg, but i need to pass it by reference not value. because it's more efficient and better.
so if i did this:
let mainView = MainView()
mainView.categories = categoriesData.
then it pass it by value.
if i need to pass it by reference i could do that by using function inside the MainView()
class MainView:UIView{
var categories:[Category]!
fun setCategories(inout categories: Int){
self.categories = categories;
}
}
but if i don't want to use set function, How could i pass it by reference.
e.g
mainView.categories = &categoriesData. but that doesn't work ?thanks

Swift uses ARC (Automatic Reference Counting) when dealing with arrays, and it delays copying arrays until one of the copies is modified:
For example:
var a = [1, 2, 3, 4, 5]
let b = a
let c = a // 1
a.append(6) // 2
print(a.count)
print(b.count)
print(c.count)
At step 1 above, there is only one copy of [1, 2, 3, 4, 5] in memory, and a, b, and c are references to it.
When a is modified in step 2, Swift gives a and new copy of the array and b and c continue to reference the original array. So now there are 2 copies of the array in memory.
Let's look at a much more involved example:
class Person: CustomStringConvertible {
let name: String
var friends: [Person] = []
init(name: String) {
self.name = name
}
var description: String { return name }
}
func createFredsFriends() -> [Person] {
let barney = Person(name: "Barney")
let wilma = Person(name: "Wilma")
let betty = Person(name: "Betty")
let friends = [barney, wilma, betty] // 1
return friends
}
func createFred() -> Person {
let fred = Person(name: "Fred")
let friends = createFredsFriends() // 2
fred.friends = friends // 3
return fred
}
let fred = createFred() // 4
print(fred.friends) // [Barney, Wilma, Betty]
At step 1, the array of friends is created. It is referenced by the local variable friends.
This reference goes away when createFredsFriends() returns, and the only reference to the array is held then by the local variable friends at step 2. Ownership of the array has been passed.
At step 3, a second reference to the array has been assigned to the friends property of fred.
At step 4, createFred() has returned, so the local variable friends is gone and no longer references the array. The only reference is in the property of the fred object which is held by the variable fred.
So the array was created once, several references to it were created, and in the end there is a single reference to the array and all of this was done without a single copy operation.
Since Swift arrays are value types and copied when changed, you can't pass an array and then expect the original to be updated when the copy is. If you need that level of functionality, you can create a class wrapper for the array and then always access that array through the instance of that class.
Here I've modified the previous example to show how that would work:
// Class to wrap array so that everyone references the same copy
class FriendsWrapper {
var friends: [Person]
init(friends: [Person]) {
self.friends = friends
}
}
class Person: CustomStringConvertible {
let name: String
var friendsWrapper: FriendsWrapper?
init(name: String) {
self.name = name
}
func addFriend(friend: Person) {
if let wrapper = friendsWrapper {
wrapper.friends.append(friend)
} else {
friendsWrapper = FriendsWrapper(friends: [friend])
}
}
var description: String { return name }
}
func createFredsFriends() -> [Person] {
let barney = Person(name: "Barney")
let wilma = Person(name: "Wilma")
let betty = Person(name: "Betty")
let friends = [barney, wilma, betty]
return friends
}
func createFred() -> Person {
let fred = Person(name: "Fred")
let friendsWrapper = FriendsWrapper(friends: createFredsFriends())
fred.friendsWrapper = friendsWrapper
// Add friend to Fred object
fred.addFriend(Person(name: "Bam Bam"))
// Copy of array in local friendsWrapper is updated
print(friendsWrapper.friends) // [Barney, Wilma, Betty, Bam Bam]
return fred
}
let fred = createFred()

Related

Swift Array append not appending values but replacing it

I am trying to add some dictionaries into an array and save it using UserDefaults, but the problem is the array doesn't append new dictionary but only replaces the dictionary. How can I fix this issue?
here is the code:
class Bookmark: NSObject {
var bookmark: [[String:Any]] = []
override init() {
super.init()
}
func setBookmark(imageURL:String, title:String, description:String, summary:String, date:String, link:String) {
bookmark.append(["imageURL":imageURL , "title":title , "description":description, "summary":summary , "date":date, "link":link])
UserDefaults.standard.set(bookmark, forKey: "bookmark")
}
func getBookrmark() -> [Any] {
let loadedBookmark = UserDefaults.standard.array(forKey: "bookmark")
return (loadedBookmark)!
}
}
Using Bookmark class:
class ReadViewController: UIViewController {
var bookmark = Bookmark()
func save() {
bookmark.setBookmark(imageURL: nImageURL.absoluteString, title: ntitle!, description: nDescription.htmlToString , summary: nSummary!, date: nDate!, link: nLink.absoluteString)
}
There is not enough information but I can guess you where you have made mistake.
Your class Bookmark is not singleton class so,every time Bookmark() create new instance every time. that means it will create new bookmark object for every instance.
What I suggest you is inside func func setBookmark(imageURL:String, title:String, description:String, summary:String, date:String, link:String)
Method 1 : Fetch the latest bookmarks in separate variable and append new object inside it and write to User Default as well as update the global object
Method 2 Or you can make it singleton and use shared instance for every time you perform operation.
Method 3 Another solution can be create global object of Bookmark in AppDelegate or Your singleton class and then use that object
However
func setBookmark(imageURL:String, title:String, description:String, summary:String, date:String, link:String) {
bookmark.append(["imageURL":imageURL , "title":title , "description":description, "summary":summary , "date":date, "link":link])
UserDefaults.standard.set(bookmark, forKey: "bookmark")
}
This is the bad practice to follow. You are directly replacing User default with new value however your intension is to append new element not replace existing data in userdefault.
You should always fetch latest and update it Like method 1 show as well as If you wanted to fetch the data in userdefault there is no use of your global object var bookmark: [[String:Any]] = [] because you have already getBookrmark method there
Hope it is clear to you
I've tried using the above code with an example and its working completely fine.
class ViewController: UIViewController {
let b = Bookmark()
override func viewDidLoad() {
super.viewDidLoad()
b.setBookmark(imageURL: "a", title: "a", description: "a", summary: "a", date: "a", link: "a")
b.setBookmark(imageURL: "b", title: "b", description: "b", summary: "b", date: "b", link: "b")
print(b.getBookrmark())
}
}
Output:
[{
date = a;
description = a;
imageURL = a;
link = a;
summary = a;
title = a;
}, {
date = b;
description = b;
imageURL = b;
link = b;
summary = b;
title = b;
}]
Check if you are using the same way or anything else.

For in loop. How does it works?

I need an explanation about for in loops in swift. Let's consider the following example.
public struct Person {
let name: String
let age: Int
var surname: String?
}
var persons: [Person] = []
for i in 0...5 {
let person = Person("test", i)
persons.append(person)
}
And here is my question. Why this won't work
//first for in loop
for var person in persons {
person.surname = "surname"
}
print(persons[0].surname) // output: nil
And this does
// second for in loop
for i in 1...persons.count {
persons[i].surname = "surname"
}
print(persons[0].surname) // output: 'surname'
I can see that first for in loop is working on copy person object because I can see output while I'm in the loop. But why are we working on copy? And can I somehow change value of person object in the first for in loop?
Reason
Since Person is a struct, the value logic is applied.
Example
It means that when you write
var person = anotherPerson
You are creating a copy. So changing one value does not affect the other value.
Exactly the same thing happens in your for in
for var person in persons {
person.surname = "surname"
}
Solution
Finally you can get a new array of persons with the new surname writing this
let personsWithSurname = persons.map { person -> Person in
var person = person
person.surname = "surname"
return person
}

Group elements with the same property value from an array [duplicate]

This question already has answers here:
How to group by the elements of an array in Swift
(16 answers)
Closed 6 years ago.
I have an array [modelA,modelB,modelC,modelD,modelE], each element in the array is an instance of a Struct. The Struct has a property "name". for example...
modelA.name = "abc"
modelB.name = "efg"
modelC.name = "hij"
modelD.name = "abc"
modelE.name = "efg"
How can I group elements with the same property value into a new array? i.e. put modelA and modelD into a new array,and put modelB and modelE into another array.
Assume the original array is large.
You can achieve this by using filter(_:):
Returns an array containing, in order, the elements of the sequence
that satisfy the given predicate.
For example, consider that the structure looks like:
struct Model {
var name: String?
}
And you have an array of models:
let allModelsArray = [Model(name: "abc"), Model(name: "efg"), Model(name: "hij"), Model(name: "abc"), Model(name: "efg"), Model(name: "efg"), Model(name: "hij")]
So, you can get your arrays by doing (assuming that you want to filter based on the value of the name):
let abcModelsArray = allModelsArray.filter { $0.name == "abc" }
// [Model(name: Optional("abc")), Model(name: Optional("abc"))]
let hijModelsArray = allModelsArray.filter { $0.name == "hij" }
// [Model(name: Optional("hij")), Model(name: Optional("hij"))]
ALSO:
You mentioned that:
how can I put element which has the same property value into a new
array, such as put modelA and modelD into a new array, and put modelB
and modelE into a new array, if array is large.
Somehow, you might want to use the lazy version of the collection.
Hope this helped.
I have not performance tested this
struct Model {
var type : String
var name : String
}
var modelA = Model(type: "A", name: "abc")
var modelB = Model(type: "B", name: "efg")
var modelC = Model(type: "C", name: "abc")
var modelD = Model(type: "D", name: "efg")
let models = [modelA,modelB,modelC,modelD]
let names = Set(models.map({return $0.name}))
var groupedModels : [String:[Model]] = [:]
for var name in names {
let elements = models.filter({$0.name == name})
groupedModels[name] = elements
}
.reduce solution:
let a = [modelA, modelB, modelC, modelD, modelE]
let arr = a.reduce([:]) { (result, currentModel) -> [String: [Model]] in
var mutableDic = result
if ((mutableDic[currentModel.name]) != nil) {
mutableDic[currentModel.name]?.append(currentModel)
} else {
mutableDic[currentModel.name] = [currentModel]
}
return mutableDic
}
It will return the same dictionary as #Grimxn response. or got from this for loop
var mutableDic = [String : [Model]]()
for aModel in a {
if ((mutableDic[aModel.name]) != nil) {
mutableDic[aModel.name]?.append(aModel)
} else {
mutableDic[aModel.name] = [aModel]
}
}
The key is to use a Dictionary to track for Model that need to be put in the same array, by comparing to it's .name.

Find Object with Property in Array

is there a possibility to get an object from an array with an specific property? Or do i need to loop trough all objects in my array and check if an property is the specific i was looking for?
edit: Thanks for given me into the correct direction, but i have a problem to convert this.
// edit again: A ok, and if there is only one specific result? Is this also a possible method do to that?
let imageUUID = sender.imageUUID
let questionImageObjects = self.formImages[currentSelectedQuestion.qIndex] as [Images]!
// this is working
//var imageObject:Images!
/*
for (index, image) in enumerate(questionImageObjects) {
if(image.imageUUID == imageUUID) {
imageObject = image
}
}
*/
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
var imageObject = questionImageObjects.filter( { return $0.imageUUID == imageUUID } )
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
You have no way to prove at compile-time that there is only one possible result on an array. What you're actually asking for is the first matching result. The easiest (though not the fastest) is to just take the first element of the result of filter:
let imageObject = questionImageObjects.filter{ $0.imageUUID == imageUUID }.first
imageObject will now be an optional of course, since it's possible that nothing matches.
If searching the whole array is time consuming, of course you can easily create a firstMatching function that will return the (optional) first element matching the closure, but for short arrays this is fine and simple.
As charles notes, in Swift 3 this is built in:
questionImageObjects.first(where: { $0.imageUUID == imageUUID })
Edit 2016-05-05: Swift 3 will include first(where:).
In Swift 2, you can use indexOf to find the index of the first array element that matches a predicate.
let index = questionImageObjects.indexOf({$0.imageUUID == imageUUID})
This is bit faster compared to filter since it will stop after the first match. (Alternatively, you could use a lazy sequence.)
However, it's a bit annoying that you can only get the index and not the object itself. I use the following extension for convenience:
extension CollectionType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
return try indexOf(predicate).map({self[$0]})
}
}
Then the following works:
questionImageObjects.find({$0.imageUUID == imageUUID})
Yes, you can use the filter method which takes a closure where you can set your logical expression.
Example:
struct User {
var firstName: String?
var lastName: String?
}
let users = [User(firstName: "John", lastName: "Doe"), User(firstName: "Bill", lastName: "Clinton"), User(firstName: "John", lastName: "Travolta")];
let johns = users.filter( { return $0.firstName == "John" } )
Note that filter returns an array containing all items satisfying the logical expression.
More info in the Library Reference
Here is a working example in Swift 5
class Point{
var x:Int
var y:Int
init(x:Int, y:Int){
self.x = x
self.y = y
}
}
var p1 = Point(x:1, y:2)
var p2 = Point(x:2, y:3)
var p3 = Point(x:1, y:4)
var points = [p1, p2, p3]
// Find the first object with given property
// In this case, firstMatchingPoint becomes p1
let firstMatchingPoint = points.first{$0.x == 1}
// Find all objects with given property
// In this case, allMatchingPoints becomes [p1, p3]
let allMatchingPoints = points.filter{$0.x == 1}
Reference:
Trailing Closure
Here is other way to fetch particular object by using object property to search an object in array.
if arrayTicketsListing.contains({ $0.status_id == "2" }) {
let ticketStatusObj: TicketsStatusList = arrayTicketsListing[arrayTicketsListing.indexOf({ $0.status_id == "2" })!]
print(ticketStatusObj.status_name)
}
Whereas, my arrayTicketsListing is [TicketsStatusList] contains objects of TicketsStatusList class.
// TicketsStatusList class
class TicketsStatusList {
internal var status_id: String
internal var status_name: String
init(){
status_id = ""
status_name = ""
}
}

Create Dictionary<String, [SomeStruct]> from [SomeStruct] source-array

var sourceEntries: [Entry] = [entry1, ..., entry14]
var myDict: Dictionary<String, [Entry]> = [:]
for entry in sourceEntries {
if var array = myDict[entry.attribute1] { theArray.append(entry) }
else { myDict[entry.attribute1] = [entry] }
}
I am intending to create a Dictionary, which matches all the objects of the struct "Eintrag" with the same attribute from the source-Array "alleEinträge" to a String containing the value of the shared attribute. For some reason my final Dictionary just matches Arrays of one element to the Strings, although some Arrays ought to contain up to four elements.
The problem is that the array is passed by value (i.e. "copied"), so the array you are writing to when you say array.append is not the array that is "inside" the dictionary. You have to write back into the dictionary explicitly if you want to change what's in it.
Try it in a simple situation:
var dict = ["entry":[0,1,2]]
// your code
if var array = dict["entry"] { array.append(4) }
// so what happened?
println(dict) // [entry: [0, 1, 2]]
As you can see, the "4" never got into the dictionary.
You have to write back into the dictionary explicitly:
if var array = dict["entry"] { array.append(4); dict["entry"] = array }
FURTHER THOUGHTS: You got me thinking about whether there might be a more elegant way to do what you're trying to do. I'm not sure whether you will think this is "more elegant", but perhaps it has some appeal.
I will start by setting up a struct (like your Entry) with a name attribute:
struct Thing : Printable {
var name : String
var age : Int
var description : String {
return "{\(self.name), \(self.age)}"
}
}
Now I will create an array like your sourceEntries array, where some of the structs share the same name (like your shared attribute attribute1):
let t1 = Thing(name: "Jack", age: 40)
let t2 = Thing(name: "Jill", age: 38)
let t3 = Thing(name: "Jill", age: 37)
let arr = [t1,t2,t3]
And of course I will prepare the empty dictionary, like your myDict, which I call d:
var d = [String : [Thing]]()
Now I will create the dictionary! The idea is to use map and filter together to do all the work of creating key-value pairs, and then we just build the dictionary from those pairs:
let pairs : [(String, [Thing])] = arr.map {
t in (t.name, arr.filter{$0.name == t.name})
}
for pair in pairs { d[pair.0] = pair.1 }

Resources