iOS Swift - Reference between Objects - ios

I have a shared handler class in which I 'manage' objects.
In this shared class, there is a 'main object (mainObject)' and a 'single object (singleData)'.
If I now assign the singleData with the reference to mainObject.data[index] in viewA and then change mainObject.data[index] in viewB, then the singleData object also changes. How can I avoid this strong-reference here?
==> In short: I want to change the mainObject without changing the singleObject. <==
struct kOBJECT {
let name: String
let data: [Int]
}
class HandlerClass {
let shared = HandlerClass()
var mainObject = kOBJECT(name: "AnyName", data: [1,2,3,4,5])
var singleData: Int?
}
class viewA: UIViewController {
.....
func didSelectRow(at indexPath: IndexPath) {
HandlerClass.shared.singleData = HandlerClass.shared.mainObject.data[indexPath.row] // Create Reference
viewB.indexPath = indexPath
pushToViewController(viewB)
}
}
class viewB: UIViewController {
.....
public var indexPath: IndexPath!
func someFunction() {
HandlerClass.shared.mainObject.data[indexPath.row] = 10000 // <- Does this also change the `singleData` Reference? In my case it does......
}
}
I tried the following in 'didSelectRow'
let tempValue = HandlerClass.shared.mainObject.data[indexPath.row]
HandlerClass.shared.singleObject = tempValue

You misunderstand reference meaning. Reference works only of instances - that's a part of memory reserved for specific objects. Structs are also objects, but it works as Type (similar to Int, String, Double, Float, etc), so when you modify a struct as a result you will have a new object, but for class instance, you will still modify the same object as you copied only reference to that object.
Here example below:
class Apple {
init(name: String) {
self.name = name
}
var name: String
}
struct Pear {
var name: String
}
var apple = Apple(name: "Apple 1")
var pear = Pear(name: "Pear 1")
var apple2 = apple // here we copy only reference
apple2.name = "Apple 2"
print(apple.name) // 1st object
print(apple2.name) // the same object
var pear2 = pear // here we create new object
pear2.name = "Pear 2"
print(pear.name) // 1st object
print(pear2.name) // 2nd object
Also a result (run on Playground)

Related

Issue with adding Data to an AnyObject Var so that I could make native ads work

for postdata in postdata {
if index < tableViewItems.count {
tableViewItems.insert(postdata, at: index)
index += adInterval
} else {
break
}
}
I'll need to add both PostData ads and Native Ads on the same AnyObject Var for me to get it to work and I can't find a way to add the Post Data because it says an error appears saying "Argument type 'PostData' expected to be an instance of a class or class-constrained type." Assistance would be very much appreciated, thank you.
edit 1
class Ad {
var postimage: String!
var publisher: String!
var timestring: String!
var timestamp = Date().timeIntervalSince1970
}
class PostDataAd: Ad {
// Declare here your custom properties
struct PostData1
{
var postimage: String
var publisher: String
var timestring : String
var timestamp = Date().timeIntervalSince1970
}
}
class NativeAd: Ad {
// Declare here your custom properties
struct NativeAd
{
var nativeAds: [GADUnifiedNativeAd] = [GADUnifiedNativeAd]()
}
}
My model class to merge both Data into one AnyObject Var
and then trying to append the Data from Firebase by doing this
var ads: [Ad] = [PostDataAd(), NativeAd()]
let postList = PostDataAd.PostData1(postimage: postimage, publisher:
postpublisher, timestring: postid, timestamp: timestamp)
self.ads.insert(postList, at:0)
an error occurs saying Cannot convert value of type 'PostDataAd.PostData1' to expected argument type 'Ad'
I hope I got what you want correctly. So basically you have two objects which you want to store in one array, under AnyObject. If that is correct, I recommend you to go in a bit of different direction. It is a nice example where you can use subclassing. You can declare a class called Ad, where you define the common properties which will be true for both PostDataAds and NativeAds.
class Ad {
// Add here the common elements you want to retrieve from both classes
var name: String = ""
}
After you define your PostDataAds and NativeAds inheriting from Ad:
class PostDataAd: Ad {
// Declare here your custom properties
}
class NativeAd: Ad {
// Declare here your custom properties
}
And if you want to define an array with two types of objects you can go:
let ads: [Ad] = [PostDataAd(), NativeAd()]
When retrieving you can check their type:
if let item = ads.first as? PostDataAd {
// The Ad is a PostDataAd
} else if let item = ad as? NativeAd {
// The Ad is a NativeAd
}
Or at some cases you don't even how to know the exact type, as you can access the properties defined in Ad without checking.
Update:
First of all your PostData1 and Ad objects are the same, you don't need to duplicate them. If you really want to have two classes you can inherit PostData1 from Ad.
class Ad {
var postimage: String
var publisher: String
var timestring: String
var timestamp = Date().timeIntervalSince1970
// You can add timestamp here also if you wish
init(postimage: String, publisher: String, timestring: String) {
self.postimage = postimage
self.publisher = publisher
self.timestring = timestring
}
}
class PostDataAd: Ad {
// Define some custom properties
}
And if you want to append PostData to the [Ad] array, you would do the following:
var ads: [Ad] = []
// Replace with your values
let postList = PostDataAd(postimage: "", publisher: "", timestring: "")
ads.insert(postList, at: 0)
// Appending NativeAd works also
let nativeAdd = NativeAd(postimage: "", publisher: "", timestring: "")
ads.append(nativeAdd)

Why does adding a convenience init to a Realm object declaration mess with private values?

I have created a Realm object that needs to store an enum value. To do that I use a method outlined in this question which involves declaring a private property of type String, and then declaring another property of type Enum that sets/reads the private property using getters and setters.
For ease of reference here is the code for that:
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
override static func primaryKey() -> String? {
return "id"
}
}
I have noticed though that if I add a convenience init to the class declaration (to make it a bit easier to initialise the object) the revisionType properties on the objects I end up with adopt the default value declared in the class, and NOT the revision type value passed to the class using the convenience init.
Here is the class declaration with a convenience init
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
convenience init(title: String, revisionType: RevisionType) {
self.init()
self.title = title
self.revisionType = revisionType
}
override static func primaryKey() -> String? {
return "id"
}
}
And - to make things even more perplexing - if I simply remove the word 'private' from the revisionTypeRaw property, everything works fine!
I am confused. 1) Why does adding a convenience init have this effect? 2) Why does making the property 'public' resolve the issue?
I have created a demo Xcode project to illustrate the issue and can share it if anyone needs it.
Update:
I found the problem. It has nothing to do with the convenience init. I am using #objcMembers at the top of the class as per the Realm docs: https://realm.io/docs/swift/latest/#property-attributes
If you remove this and place #objc in front of the private keyword, everything works as would be expected. I guess the question then is: what explains this behaviour?
This is a good question but I think the issue is elsewhere in the code. Let's test it.
I created a TestClass that has a Realm managed publicly visible var, name, as well as a non-managed public var visibleVar which is backed by a Realm managed private var, privateVar. I also included a convenience init per the question. The important part is the privateVar is being set to the string "placeholder" so we need to see if that is overwritten.
class TestClass: Object {
#objc dynamic var name = ""
#objc private dynamic var privateVar = "placeholder"
var visibleVar: String {
get {
return self.privateVar
}
set {
self.privateVar = newValue
}
}
convenience init(aName: String, aString: String) {
self.init()
self.name = aName
self.visibleVar = aString
}
}
We then create two instances and save them in Realm
let a = TestClass(aName: "some name", aString: "some string")
let b = TestClass(aName: "another name", aString: "another string")
realm.add(a)
realm.add(b)
Then a button action to load the two objects from Realm and print them.
let testResults = realm.objects(TestClass.self)
for test in testResults {
print(test.name, test.visibleVar)
}
and the output:
some name some string
another name another string
So in this case, the default value of "placeholder" is being overwritten correctly when the instances are being created.
Edit:
A bit more info.
By defining your entire class with #objMembers, it exposes your propertites to Objective-C, but then private hides them again. So that property is not exposed to ObjC. To reverse that hiding, you have to say #objc explicitly. So, better practice is to define the managed Realm properties per line with #objc dynamic.

Saving a Realm list made up of an array of struct objects

Problem
I need to save a List in Realm, which is made up of a the properties of an Array of Struct Objects (which has been passed through a Segue and is popualating a tableview). This is in the form of an 'exercise name' and 'number of reps' on each row.
What have I tried?
I have matched the Realm Object with the Struct in terms of fields and format and attempted to save the array as a list e.g. "=List< array >" but this doesn't work ("use of undeclared type"). I've also tried various methods of trying to save the properties of each table row but again, couldn't get that to work (e.g. = cell.workoutname)
Research I found this How to save a struct to realm in swift? however, this isn't for saving arrays of objects I don't think. This did however (first answer), give me the idea of potentially saving the values contained within each row to Realm instead of the actual Struct array. I also found this Saving Array to Realm in Swift? but I think this is for when the array is already made up of Realm Objects, not Struct instances like in my case.
Code and details
Structs
I have a Struct as per below. Another struct, (Workout Generator) has a function which generates x number of instances of these objects. These are then passed via a Segue to a new VC TableView (each row displays a workout name and number of reps):
struct WorkoutExercise : Hashable, Equatable{
let name : String
let reps : Int
var hashValue: Int {
return name.hashValue
}
static func == (lhs: WorkoutExercise, rhs: WorkoutExercise) -> Bool {
return lhs.name == rhs.name
}
}
I then have the following Realm Objects. One is for saving a 'WorkoutSession'. This will contain a Realm List of WorkoutExercise Realm objects.
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutName = ""
let exercises = List<WorkoutExerciseObject>()
var totalExerciseCount: Int {
return exercises.count
}
}
class WorkoutExerciseObject: Object {
#objc dynamic var name = ""
#objc dynamic var reps = 0
}
I have tried the following code when trying to save the Workout details to Realm :
func saveToRealm() {
let workoutData = WorkoutSessionObject()
workoutData.workoutName = "test"
workoutData.workoutID = UUID().uuidString
workoutData.exercises = List<selectedWorkoutExerciseArray>
}
What I think I need to do from reading the other answers
Option 1 - instead of trying to save the actual array, save the 'name' and 'reps' from each table row instead?
Option 2 - somehow convert the 'selectedWorkoutExerciseArray' into a list of realm objects?
of course there might be other options! Any help/ideas appreciated!
Why populate 2 separate lists if it needs to be persistent anyway? Just use the list in Realm to populate your table view. Here's a simple example of populating the list using append (just like any array):
class SomeClass: Object {
#objc dynamic var id: String = ""
var someList = List<SomeOtherClass>()
convenience init(id: String) {
self.init()
self.id = id
}
}
#objcMembers class SomeOtherClass: Object {
dynamic var someValue: String = ""
convenience init(value: String) {
self.init()
someValue = value
}
}
func addToList(someOtherClass: SomeOtherClass) {
let realm = try! Realm()
if let someClass = realm.objects(SomeClass.self).last {
do {
try realm.write({
someClass.someList.append(someOtherClass)
})
} catch {
print("something went wrong")
}
}
}
I have a very similar functionality, that allow the user to select from a table view. What I do is create a List from the selection like:
var arrayForSelectedObjects = [CustomObject]()
...
let aList = List<CustomObject>()
aList.append(objectsIn: arrayForSelectedObjects)
//I then assign the created list to the main object and save it.
let realmObject = MainObject()
realmObject.list = aList
My CustomObject is also stored on the realm db.
My MainObject is defined like so:
class MainObject : Object {
#objc dynamic var title: String?
var list = List<CustomObject>()
}

How to update List as a parameter in another Object in Realm

I have two classes where one of them is as a variable in second class. It looks like that:
class Dogs: Object {
dynamic var name: String?
dynamic var age: String?
}
class Person: Object {
dynamic var owner: String?
var dogs: List<Dogs>() //I would like to UPDATE one of element from this list
}
and now, I would like to update one element from this List<Dogs> but I have a still problem with it. I try to do achieve goal doing it:
class ViewController: UIViewController {
var person: Person?
...
func update(){
let updatedDog = Dogs()
updatedDog.name = "Tinky"
updatedDog.age = "12"
try! realm.write {
person.dogs[0] = updatedDog
}
}
}
this solution doesn't work. Do you have any idea how can I update it?
In your ViewController you need to grab the actual Dog you're updating and update it in a write block.
Realm().write {
updatingDog.name = "Tinky"
updatingDog.age = 12
}
// use `updatingDog` as normal
https://realm.io/docs/swift/latest/#updating-objects

iOS How do I get an object's property's type in Swift?

I'm trying to get property information on an object in Swift. I've been able to get the names of the properties, and certain attributes, but I'm not sure how to extract the type of the property.
I've been trying to follow some of the suggestions on this objective-c related post
class func classOfProperty(parentType: AnyClass, propertyName: String) -> AnyClass?
{
var type: AnyClass?
var property: objc_property_t = class_getProperty(parentType, propertyName);
var attributes = NSString(UTF8String: property_getAttributes(property)).componentsSeparatedByString(",") as [String]
if(attributes.count > 0) {
?????
}
return type
}
Is this possible yet in Swift? If so, how?
In swift 1.2, the following is possible:
import Foundation
func getPropertyType(parentType: NSObject.Type, propertyName: String) -> Any.Type? {
var instance = parentType()
var mirror = reflect(instance)
for i in 0..<mirror.count {
var (key, valueInfo) = mirror[i]
if key == propertyName {
return valueInfo.valueType
}
}
return nil
}
enum EyeColor: Int { case Brown = 1, Blue = 2, Black = 3, Green = 4 }
class Person: NSObject {
var name = "Fred"
var eyeColor = EyeColor.Black
var age = 38
var jumpHeight: Float?
}
println(getPropertyType(Person.self, "name"))
println(getPropertyType(Person.self, "eyeColor"))
println(getPropertyType(Person.self, "age"))
println(getPropertyType(Person.self, "jumpHeight"))
Unfortunately, they must be NSObjects (or some other class that has enforces a default constructor.)

Resources