Same reference for all the objects in iOS swift - ios

In swift 4.2 I am facing the problem while handling the two array objects, When I am removing objects from another array, the values are removed from the all the objects.
1) Below is my closure
func GetChatBotData(completion: #escaping (_ result: ChatV_1_Model) -> Void) {
var ChatBotData : ChatV_1_Model! = nil
ApiHelper.sharedSession.postLoacl("http://localhost:3000/posts/", postData: NSDictionary(), methodtype: Constant.API.httpGet) { (isError, data, errorDescription) in
DispatchQueue.main.async(execute: {
if isError == false {
ChatBotData = ChatV_1_Model.init(fromDictionary: data!)
completion(ChatBotData)
}
else {
//completion("Error to get result" as AnyObject)
completion(ChatBotData)
}
})
}
}
Now In my controller
var PKComponents = [Chatbot_V_1_DataModel]()
var ChatMessages = [Chatbot_V_1_DataModel]()
override func viewDidLoad() {
super.viewDidLoad()
GetChatBotData() {(result: ChatbotV_1_Model!) in
print("Call Plans: \(result!)")
self.PKComponents = result.data
self.ChatMessages = result.data
self.ChatMessages[0].component.removeAll()
}
In Viewdidload I am removing objects from self.ChatMessages array but it removes from all the objects like, PKComponents and result.data as well.
Note: I have seen the reference of the result is same as PKComponents and Chatmessages.
How to resolve this?

Here's the simplified example, where I can reproduce your problem:
class Component {
}
class SomeData {
var components: [Component]
init(components : [Component]) {
self.components = components
}
}
class Result {
var data: [SomeData]
init(data: [SomeData]) {
self.data = data
}
}
let someData = SomeData(components: [Component()])
let result = Result(data: [someData])
//problem begins here
let pkCompent = result.data
var chatMsgs = result.data
print(pkCompent[0].components.count)
chatMsgs[0].components.removeAll()
print(pkCompent[0].components.count)
Inorder to avoid the reference issue, convert SomeData to struct
struct SomeData {
var components: [Component]
init(components : [Component]) {
self.components = components
}
}

You have 2 suggestions.
1- Deep copy .
2- Use struct instead of class since its value type.
Incase of deep copy this is a simple example,
when you assign something to new instance use this way.
// Deep copy
var foo = Foo()
var otherFoo = Foo(foo)
rather than this.
var fee = foo // shallow copy still the same referance
Note: this is handled by swift you don't have to add any inits to the class.

component is what you are removing it from and not the Array to
be precise.
While arrays implementation in swift is using a Struct which is a
value type and not a Object type, what your array is holding, i.e
Object of Chatbot_V_1_DataModel might as well be a class thus, the
elements held in your array are references to object of type
Chatbot_V_1_DataModel.
the way you can work around this is by having Chatbot_V_1_DataModel
defined as a struct thus, a value type OR by making a deep copy of
you array and then using that copy in you closure as you modify it.
I am talking about something on these lines:
var copie = arr.map{$0.mutableCopy()}
better yet:
var PKComponents = [Chatbot_V_1_DataModel]()
var ChatMessages = [Chatbot_V_1_DataModel]()
override func viewDidLoad() {
super.viewDidLoad()
var copie = arr.map{$0.mutableCopy()} // use this copy now elsewhere!!!
GetChatBotData() {(result: ChatbotV_1_Model!) in
print("Call Plans: \(result!)")
self.PKComponents = result.data
self.ChatMessages = result.data
self.ChatMessages[0].component.removeAll()
}

This is the problem of deep copying. Either you write a complete initializer that copies everything and create a new object and use that instead of just assignment. Or use struct instead of class. But as a quick fix you can explicitly only copy the component array like this:
self.PKCOMPONENTS = results.data
self.PKCOMPONENTS.components = Array(results.data.components)

Related

Swift protocol generics error(ORM implementation)

I'm trying to implement a ORM layer on top of Couchbase Lite iOS 2.0, now it did removed the CBLModel apis which make it a little bit difficult to use.
I'm using a Reflection lib and its Mappable protocol(which is renamed to ORMMappable in the following code) to simplify the mapping apis.
Here's the error message:
let t = Self.cast_id_type(type: link, obj: value)
Cannot invoke 'cast_id_type' with an argument list of type '(type: ORMMappable.Type, obj: Any?)'
Expected an argument list of type '(type: D.Type, obj: Any?)'
And here's the problematic code
typealias MappableDictionary = [String : Any]
class IDString<T:ORMMappable> : NSString{
func load_object(){
}
}
struct MapMeta{
var ignores : [String] = []
var mapping : [String:ORMMappable.Type] = [:]
}
protocol ORMMappable {
var id : NSString {get set}
static var _meta : MapMeta{get}
init(dictionary: MappableDictionary) throws
}
extension ORMMappable {
init() throws{
try self.init(dictionary: [:] as MappableDictionary)
}
static func cast_id_type<D:ORMMappable>(type: D.Type,obj: Any?) -> IDString<D>?{
if let o = obj as? IDString<D>{
return o
}
return nil
}
init(dictionary: MappableDictionary) throws {
self = try construct { property in
let meta = Self._meta
if let value = dictionary[property.key] {
if let type = property.type as? ORMMappable.Type, let value = value as? MappableDictionary {
return try type.init(dictionary: value)
}
else if let link = meta.mapping[property.key]
{
let t = Self.cast_id_type(type: link, obj: value)
print(link)
//return t
return nil
}
else {
return value
}
} else {
return nil
//throw Error.missingRequiredValue(key: property.key)
}
}
}
}
A example of usage is
struct TestObject : ORMMappable{
static var _meta: MapMeta{
return MapMeta(ignores: [], mapping: ["link_id":TestObject2.self])
}
var id : NSString
var name : String?
var age : Int?
var link_id : IDString<TestObject2>?
}
IDString is holder for a link to other ORMMappable compatible class, mapping maps from String(property name) to a ORMMappable compatible class, and cast_id_type does check the mapping and trying to cast from the pointer of value to the StringID object. The error itself makes me quite confused here,
static func cast_id_type<D:ORMMappable>(type: D.Type,obj: Any?) -> IDString<D>?
D should be a ORMMappable compatible class, where I give is a value of a [String:ORMMappable.Type], but it rises ORMMappable.Type is not D.Type, how does this comes from?
Also I'm looking forward if there any better ways to do ORM in swift, currently the code does working with dynamic object creation, but when comes with ORM relation, it really drove me nuts here, just looking for ways to manage it in a easier and more manageable ways, where currently it looks like there are not much functionalities to do meta programming, a lot of other ORM libs still using objc, which is much more easier(but boilerplate) on dynamic instance creation or class inspection.
Thanks very much for the help, any hints will be real appreciated :)
Removed all generics solved the problem, it won't inferred in runtime environment.

How would one create a List<AnyRealmObject> in Swift?

OK, first, I know that there is no such thing as AnyRealmObject.
But I have a need to have something the behaves just like a Realm List, with the exception that any kind of Realm Object can be added to the list -- they don't all have to be the same type.
Currently, I have something like this:
enter code here
class Family: Object {
var pets: List<Pet>
}
class Pet: Object {
var dog: Dog?
var cat: Cat?
var rabbit: Rabbit?
}
Currently, if I wanted to add in, say, Bird, I'd have to modify the Pet object. I don't want to keep modifying that class.
What I really want to do is this:
class Family: Object {
var pets: List<Object>
}
Or, maybe, define a Pet protocol, that must be an Object, and have var pets: List<Pet>
The point is, I want a databag that can contain any Realm Object that I pass into it. The only requirement for the databag is that the objects must be Realm Objects.
Now, since Realm doesn't allow for this, how could I do this, anyway? I was thinking of creating something like a Realm ObjectReference class:
class ObjectReference: Object {
var className: String
var primaryKeyValue: String
public init(with object: Object) {
className = ???
primaryKeyValue = ???
}
public func object() -> Object? {
guard let realm = realm else { return nil }
var type = ???
var primaryKey: AnyObject = ???
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
}
The stuff with the ??? is what I'm asking about. If there's a better way of doing this I'm all ears. I think my approach is ok, I just don't know how to fill in the blanks, here.
(I'm assuming that you are writing an application, and that the context of the code samples and problem you provided is in terms of application code, not creating a library.)
Your approach seems to be a decent one given Realm's current limitations; I can't think of anything better off the top of my head. You can use NSClassFromString() to turn your className string into a Swift metaclass object you can use with the object(ofType:...) API:
public func object() -> Object? {
let applicationName = // (application name goes here)
guard let realm = realm else { return nil }
guard let type = NSClassFromString("\(applicationName).\(className)") as? Object.Type else {
print("Error: \(className) isn't the name of a Realm class.")
return nil
}
var primaryKey: String = primaryKeyValue
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
My recommendation is that you keep things simple and use strings exclusively as primary keys. If you really need to be able to use arbitrary types as primary keys you can take a look at our dynamic API for ideas as to how to extract the primary key value for a given object. (Note that although this API is technically a public API we don't generally offer support for it nor do we encourage its use except when the typed APIs are inadequate.)
In the future, we hope to offer enhanced support for subclassing and polymorphism. Depending on how this feature is designed, it might allow us to introduce APIs to allow subclasses of a parent object type to be inserted into a list (although that poses its own problems).
This may not be a complete answer but could provide some direction. If I am reading the question correctly (with comments) the objective is to have a more generic object that can be the base class for other objects.
While that's not directly doable - i.e. An NSObject is the base for NSView, NSString etc, how about this...
Let's define some Realm objects
class BookClass: Object {
#objc dynamic var author = ""
}
class CardClass: Object {
#objc dynamic var team = ""
}
class MugClass: Object {
#objc dynamic var liters = ""
}
and then a base realm object called Inventory Item Class that will represent them
class InvItemClass: Object {
#objc dynamic var name = ""
#objc dynamic var image = ""
#objc dynamic var itemType = ""
#objc dynamic var book: BookClass?
#objc dynamic var mug: MugClass?
#objc dynamic var card: CardClass?
}
then assume we want to store some books along with our mugs and cards (from the comments)
let book2001 = BookClass()
book2001.author = "Clarke"
let bookIRobot = BookClass()
bookIRobot.author = "Asimov"
let item0 = InvItemClass()
item0.name = "2001: A Space Odyssey"
item0.image = "Pic of Hal"
item0.itemType = "Book"
item0.book = book2001
let item1 = InvItemClass()
item1.name = "I, Robot"
item1.image = "Robot image"
item1.itemType = "Book"
item1.book = bookIRobot
do {
let realm = try Realm()
try! realm.write {
realm.add(item0)
realm.add(item1)
}
} catch let error as NSError {
print(error.localizedDescription)
}
From here, we can load all of the Inventory Item Objects as one set of objects (per the question) and take action depending on their type; for example, if want to load all items and print out just the ones that are books.
do {
let realm = try Realm()
let items = realm.objects(InvItemClass.self)
for item in items {
switch item.itemType {
case "Book":
let book = item.book
print(book?.author as! String)
case "Mug":
return
default:
return
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
As it stands there isn't a generic 'one realm object fits all' solution, but this answer provides some level of generic-ness where a lot of different object types could be accessed via one main base object.

Realm - list of objects in an object (Swift 3)

I have a Models class defined like this:
class BaseModel: Object {
var data: JSON = JSON.null
convenience init(_ data: JSON) {
self.init()
self.data = data
}
override static func ignoredProperties() -> [String] {
return ["data"]
}
}
class RecipeModel: BaseModel {
dynamic var title: String {
get { return data["fields"]["title"].stringValue }
set { self.title = newValue }
}
... more vars ...
var ingredients: List<IngredientsModel> {
get {
let ingredients = List<IngredientsModel>()
for item in data["fields"]["ingredients"] {
ingredients.append(IngredientsModel(item.1))
}
return ingredients
}
set { self.ingredients = newValue }
}
}
class IngredientsModel: BaseModel {
dynamic var text: String {
get { return data["text"].stringValue }
set { self.text = newValue }
}
... more vars ...
}
And I would like to use it something like this:
Api.shared.fetchAllEntries().call(onSuccess: {response in
print(response.json)
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
for item in response.json["items"].arrayValue {
let recipe = RecipeModel(item)
try! realm.write {
realm.add(recipe)
}
}
}, onError: {
print("error")
})
So basically the idea is to just pass the whole JSON to the initial RecipeModel class, and it should parse it out and create the objects I need in the Realm database. It works quite well except for the nested list of IngredientsModel. They do not get added to the realm database.
What I see as a potential problem is that I call self.init() before I call self.data in the convenience init, but I do not see any way to work around this. Do you guys please know how I could achieve that also the IngredientsModel would have its contents set up properly and I would have a list of ingredients in the RecipeModel?
Your current implementation doesn't work, because you are not calling the getter/setter of ingredients in the init method of RecipeModel and hence the IngredientsModel instances are never persisted in Realm.
Moreover, using a computed property as a one-to-many relationship (Realm List) is a really bad idea, especially if you are parsing the results inside the getter for this property. Every time you call the getter of ingredients, you create new model objects instead of just accessing the existing ones that are already stored in Realm, but you are never deleting the old ones. If you were actually saving the IngredientsModel instances to Realm (which you don't do at the moment as mentioned above) you would see that your database is full of duplicate entries.
Your whole approach seems really suboptimal. You shouldn't store the unparsed data object in your model class and use computed properties to parse it. You should parse it when initializing your models and shouldn't store the unparsed data at all. You can use the ObjectMapper library for creating Realm objects straight away from the JSON response.

Add arrays to Realm with swift 3

I'm new in Realm and I tried to add an Array as I did with strings and I ended up with some errors. So after a little search I found out a solution:
class Sensors : Object {
dynamic var name = ""
dynamic var message = ""
var topic: [String] {
get {
return _backingNickNames.map { $0.stringValue }
}
set {
_backingNickNames.removeAll()
_backingNickNames.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
let _backingNickNames = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["topic"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
This is working very good, now I want to add another array inside this class.
If someone knows any other ways to add arrays with realm please share it.
Thanks in advance
As a general rule it's way more efficient to use the one-to-many relationships provided by Realm instead of trying to emulate them by using arrays (Realm's collections are lazy, the objects contained are instantiated only when needed as opposed to plain Swift arrays).
In your case, if I understand correctly what you're trying to do, you want to add [RealmString] Swift arrays to the _backingNickNames list.
Why not use the append(objectsIn:) method of Realm's List class (see here), like this:
// Dog model
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person?
}
// Person model
class Person: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
let dogs = List<Dog>()
}
let jim = Person()
let dog1 = Dog()
let dog2 = Dog()
// here is where the magic happens
jim.dogs.append(objectsIn: [dog1, dog2])
If you want to do the opposite (convert from a List to an Array) just do :
let dogsArray = Array(jim.dogs)
• • • • • • • •
Back to your own posted solution, you could easily refactor the model to accommodate this. Each Sensor object could have several Topic and several Message objects attached.
Just ditch the message and topic computed properties and rename topicV and messageV to topics and messages respectively. Also rename RealmString to Topic and RealmString1 to Message.
Now, you could easily iterate through the, say, topics attached to a sensor like this :
for topic in sensor1.topics { ... }
Or if you want to attach a message to a sensor you could do something like this (don't forget to properly add the newly created object to the DB first):
let message1 = Message()
message1.stringValue = "Some text"
sensor2.messages.append(message1)
So, no need to use intermediary Swift Arrays.
After testing I managed to add another array like that:
class Sensors : Object {
dynamic var type = ""
dynamic var name = ""
dynamic var badge = 0
var topic: [String] {
get {
return topicV.map { $0.stringValue }
}
set {
topicV.removeAll()
topicV.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
var message: [String] {
get {
return messageV.map { $0.stringValue1 }
}
set {
messageV.removeAll()
messageV.append(objectsIn: newValue.map({ RealmString1(value: [$0]) }))
}
}
let topicV = List<RealmString>()
let messageV = List<RealmString1>()
override static func ignoredProperties() -> [String] {
return ["topic", "message"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
class RealmString1: Object {
dynamic var stringValue1 = ""
}
What bogdanf has said, and the way you've implemented it are both correct.
Basic value types aside, Realm can only store references to singular Realm Object objects, as well as arrays of Objects using the List type. As such, if you want to save an array of types, it's necessary to encapsulate any basic types you want to save (like a String here) in a convenience Realm Object.
Like bogdanf said, it's not recommended to convert Realm Lists to standard Swift arrays and back again, since you lose the advantages of Realm's lazy-loading features (which can cause both performance and memory issues), but memory issues can at least be mitigated by enclosing the code copying data out of Realm in an #autoreleasepool block.
class MyObject: Object {
dynamic var childObject: MyObject?
let objectList = List<MyObject>()
}
So in review, it's best practice to work directly with Realm List objects whenever possible, and to use #autoreleasepool any time you do actually want to loop through every child object in a Realm. :)

Do I really need a shared instance on a singleton?

I have written a helper struct for saving and loading stuff to NSUserDefaults.
import UIKit
struct Database {
static let defaults = NSUserDefaults.standardUserDefaults()
static var myVariable: AnyObject?
static func save() {
defaults.setObject(myVariable, forKey: "myVariable")
}
static func load() {
if let myVariable = defaults.objectForKey("myVariable") {
self.myVariable = myVariable
}
}
static func clear() {
defaults.removeObjectForKey("myVariable")
}
}
Now I can simply use Database.load() to load myVariable from NSUSerDefaults.
However, the same is achievable with this code:
struct Database2 {
static var sharedInstance = Database()
let defaults = NSUserDefaults.standardUserDefaults()
var myVariable: AnyObject?
func save() {
defaults.setObject(myVariable, forKey: "myVariable")
}
func load() {
if let myVariable = defaults.objectForKey("myVariable") {
self.myVariable = myVariable
}
}
func clear() {
defaults.removeObjectForKey("myVariable")
}
}
Now I would use Database2.sharedInstance.load().
Which one is seen as a better practice and why? What's the use of a sharedInstance, if I can do everything I want with the static declaration?
A shared instance is recommendable, at least for the following reasons:
class methods make unit testing harder
you need class instances for dependency injection
if later on you decide that a non-singleton is more suitable - e.g. you decide to have two persistence storages for "myVariable", then you're stuck
and not lastly, class members live in the global space, and we should avoid using globals
The real question you should ask, is if you really need a singleton (with or without a shared instance) for your problem. If the only reason to have a singleton is ease-of-access, then you don't really need a singleton.
P.S. There is a very good article on objc.io about singletons, and although it was written for Objective-C, many concepts from there apply in Swift too.
// with singleton pattern, there exist only one copy of the object
// sigleton pattern can be applied for reference type only
// let st1 = Singleton(); let st2 = Sigleton(); st1 === st2
// in your example, S is value type. All instances of S share only type properties, here only i
struct S {
static var i: Int = 100
var j: Int
func foo() {
//print(i) // error: static member 'i' cannot be used on instance of type 'S'
print(S.i)
}
init(_ j: Int) {
self.j = j
}
}
var s1 = S(1)
var s2 = S(2)
//s1.i // error: static member 'i' cannot be used on instance of type 'S'
S.i // 100
s1.foo() // 100
s1.j // 1
s2.foo() // 100
s2.j // 2
S.i = 200
s1.foo() // 200
s2.foo() // 200
by the way, this (your) approach can be very useful and could be preferred in some situations.

Resources