iOS private/public data management and storage in Swift - ios

Is it possible to display as one array objects retrieved from network and same model but retrieved from core data. Purpose is to have same data possible to be public (then retrieved from network) or private and then this data is stored locally in coredata model. Attributes/Properties will be the same for both.
I plan to display this as swiftUI view (if that matters)
After some search I came with idea to have one struct that based on its privacy property will be translated to core data class model or if public directly connected to networking layer?
for example (some pseudo swift ;) )
struct Note {
let note: String
let isPrivate: Bool
func save(self) {
if self.isPrivate { save to CoreData }
else { send save request with use of networking }
}
}
class coreDataModel: NSManagedObject {
var note: String
let isPrivate = true
}
struct networkingModel {
var note: String
let isPrivate = false
}
class modelManager {
func joinData() {
let joinedModel: Note = coreDataModel + networkingModel
// and so on to display that model
}
}

I think you can try this, first load the ui with the existing local data. At the same time, make a call to your api on a background queue. Once the api results are available, filter for duplicates, then persist it locally. Then the last step is to notify the ui to reload.
This pseudo-code is a bit UIKit specific, however the same logic can be applied to when in SwiftUI. You won't need closure, you will compute directly to your publisher object, and ui will react based any new emits.
/// you can have one model for both api and core data
struct Note: Hashable { let uuid = UUID.init() }
class ModelManager {
private var _items: Array<Note> = []
var onChanged: ((Array<Note>) -> Void)? = nil
// you might also have an init, where you can have a timer running
func start() {
loadFromCoreData { [weak self] (items) in
guard let `self` = self else { return }
self._items.append(contentsOf: items)
self.onChanged?(self._items)
self.loadFromApi()
}
}
private func loadFromCoreData(completion: #escaping (Array<Note>) -> Void) {
// background queue
// logic to load from coredata.
let coreDataResults: Array<Note> = []
completion(coreDataResults)
}
private func loadFromApi() {
// background queue
let apiResults: Array<Note> = []
compute(contents: apiResults)
}
private func compute(contents: Array<Note>) {
let combined = zip(_items, contents).flatMap{[$0.0, $0.1] }
let newItems = Array(Set(combined)) // set doesn't allow duplicates
// save to db
// insert to new to _items
_items.append(contentsOf: newItems)
// sort maybe to place new items on top
self.onChanged?(self._items)
}
}
// usage of this object
let manager = ModelManager()
manager.start()
manager.onChanged = { items in
// [weak self] remember of retain cycles
// make sure you are on main queue when reloading
}

Related

Passing ManagedObject collection from fetch result to closure up to ViewController

I want to perform a background fetch and pass the result to closure. Currently I'm using performBackgroundTask method from NSPersistentContainer which is giving a NSManagedObjectContext as a closure. Then using that context I'm executing fetch request. When fetch is done I'm, passing the result to the completion handler.
func getAllWorkspacesAsync(completion: #escaping ([Workspace]) -> Void) {
CoreDataStack.shared.databaseContainer.performBackgroundTask { childContext in
let workspacesFetchRequest: NSFetchRequest<Workspace> = NSFetchRequest(entityName: "Workspace")
workspacesFetchRequest.predicate = NSPredicate(format: "remoteId == %#", "\(UserDefaults.lastSelectedWorkspaceId))")
do {
let workspaces: [Workspace] = try childContext.fetch(workspacesFetchRequest)
completion(workspaces)
} catch let error as NSError {
// Handle error
}
}
}
I'm going to call this method from ViewModel and use Combine PassthroughSubject to notify the ViewController about the event.
class WorkspaceViewModel {
private var cancellables: Set<AnyCancellable> = []
let resultPassthroughObject: PassthroughSubject<[Workspace], Error> = PassthroughSubject()
private let cacheManager = WorkspaceCacheProvider.shared
static public let shared: WorkspaceViewModel = {
let instance = WorkspaceViewModel()
return instance
}()
func fetchWorkspaces() {
cacheManager.getAllWorkspacesAsync { [weak self] workspaces in
guard let self = self else { return }
self.resultPassthroughObject.send(workspaces)
}
}
}
And the ViewController code:
class WorkspaceViewController: UIViewController {
private var cancellables: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
WorkspaceViewModel.shared.resultPassthroughObject
.receive(on: RunLoop.main)
.sink { _ in } receiveValue: { workspaces in
// update the UI
}
.store(in: &cancellables)
}
}
My question is: is it safe to pass NSManagedObject items?
No, it is not
Do not pass NSManagedObject instances between queues. Doing so can result in corruption of the data and termination of the app. When it is necessary to hand off a managed object reference from one queue to another, use NSManagedObjectID instances.
You would want something like:
func getAllWorkspacesAsync(completion: #escaping ([NSManagedObjectID]) -> Void) {
CoreDataStack.shared.databaseContainer.performBackgroundTask { childContext in
let workspacesFetchRequest: NSFetchRequest<Workspace> = NSFetchRequest(entityName: "Workspace")
workspacesFetchRequest.predicate = NSPredicate(format: "remoteId == %#", "\(UserDefaults.lastSelectedWorkspaceId))")
do {
let workspaces: [Workspace] = try childContext.fetch(workspacesFetchRequest)
completion(workspaces.map { $0.objectID } )
} catch let error as NSError {
// Handle error
}
}
}
You will need to make corresponding changes to your publisher so that it publishes [NSManagedObjectID].
Once you have received this array in your view controller you will need to call object(with:) on your view context to get the Workspace itself. This will perform a fetch in the view context for the object anyway.
You may want to consider whether the time taken to fetch your workspaces warrants the use of a background context. By default Core Data objects are retrieved as faults and the actual attribute values are only fetched when the fault is triggered by accessing an object's attributes.
Alternatively, if the purpose of the view controller is to present a list of workspaces that the user can select from, you could return an array of tuples or structs containing the workspace name and the object id. However, this all sounds like pre-optimisation to me.

Where to store Decoded JSON array from server and how to access it globally in viewControllers?

Currently im creating application which parses JSON from my server. From server I can receive array with JSON models.
Data from this array must be populated in table View.
My question Is simple: where to store decoded array from server, if I want to access it from many viewControllers in my application?
Here is my JSON model, which coming from server.
import Foundation
struct MyModel: Codable {
var settings: Test?
var provider: [Provider]
}
extension MyModel {
struct setting: Codable {
var name: String
var time: Int
}
}
here is how I am decoding it
import Foundation
enum GetResourcesRequest<ResourceType> {
case success([ResourceType])
case failure
}
struct ResourceRequest<ResourceType> where ResourceType: Codable {
var startURL = "https://myurl/api/"
var resourceURL: URL
init(resourcePath: String) {
guard let resourceURL = URL(string: startURL) else {
fatalError()
}
self.resourceURL = resourceURL.appendingPathComponent(resourcePath)
}
func fetchData(completion: #escaping
(GetResourcesRequest<ResourceType>) -> Void ) {
URLSession.shared.dataTask(with: resourceURL) { data, _ , _ in
guard let data = data else { completion(.failure)
return }
let decoder = JSONDecoder()
if let jsonData = try? decoder.decode([ResourceType].self, from: data) {
completion(.success(jsonData))
} else {
completion(.failure)
}
}.resume()
}
}
This is an example of CategoriesProvider. It just stores categories in-memory and you can use them across the app. It is not the best way to do it and not the best architecture, but it is simple to get started.
class CategoriesProvider {
static let shared = CategoriesProvider()
private(set) var categories: [Category]?
private let categoryRequest = ResourceRequest<Category>(resourcePath: "categories")
private let dataTask: URLSessionDataTask?
private init() {}
func fetchData(completion: #escaping (([Category]?) -> Void)) {
guard categories == nil else {
completion(categories)
return
}
dataTask?.cancel()
dataTask = categoryRequest.fetchData { [weak self] categoryResult in
var fetchedCategories: [Category]?
switch categoryResult {
case .failure:
print("error")
case .success(let categories):
fetchedCategories = categories
}
DispatchQueue.main.async {
self?.categories = fetchedCategories
completion(fetchedCategories)
}
}
}
}
I suggest using URLSessionDataTask in order to cancel a previous task. It could happen when you call fetchData several times one after another. You have to modify your ResourceRequest and return value of URLSession.shared.dataTask(...)
Here more details about data task https://www.raywenderlich.com/3244963-urlsession-tutorial-getting-started#toc-anchor-004 (DataTask and DownloadTask)
Now you can fetch categories in CategoriesViewController in this way:
private func loadTableViewData() {
CategoriesProvider.shared.fetchData { [weak self] categories in
guard let self = self, let categories = categories else { return }
self.categories = categories
self.tableView.reloadData()
}
}
In the other view controllers, you can do the same but can check for the 'categories' before making a fetch.
if let categories = CategoriesProvider.shared.categories {
// do something
} else {
CategoriesProvider.shared.fetchData { [weak self] categories in
// do something
}
}
If you really want to avoid duplicate load data() calls, your simplest option would be to cache the data on disk (CoreData, Realm, File, etc.) after parsing it the first time.
Then every ViewController that needs the data, can just query your storage system.
Of course the downside of this approach is the extra code you'll have to write to manage the coherency of your data to make sure it's properly managed across your app.
make a global dictionary array outside any class to access it on every viewcontroller.

Wait for JSON parsing in a different class file in swift 3

I created a class as shown in the code below, and as u can see I am parsing a JSON file in the class outside the viewController.
When I create the AllCards object in the view controller obviously return 0 at the beginning but after a while it returns the correct number of cards.
here my questions:
1) How can I wait the object creation before the viewDidLoad so at the view did load the AllCard object will return the correct number of cards?
2) If I add a button in the viewController updating the number of cards it freezes until all the cards have been created. I think because in my code everything is in the main queue. How can I resolve that?
3) Is it a good practice parsing JSON in a separate class like I did?
AllCards class:
import Foundation
import Alamofire
import SwiftyJSON
class AllCards {
var allCard = [Card]()
let dispatchGroup = DispatchGroup()
//gzt the JSON with Alamofire request
let allCardsHTTP: String = "https://omgvamp-hearthstone-v1.p.mashape.com/cards?mashape"
init() {
dispatchGroup.enter()
Alamofire.request(allCardsHTTP, method: .get).responseJSON { (response) in
if response.result.isSuccess {
let jsonCards : JSON = JSON(response.value!)
print("success")
//create the cards
if jsonCards["messagge"].stringValue != "" {
print(jsonCards["message"].stringValue)
}
else {
for (set, value) in jsonCards {
if jsonCards[set].count != 0 {
for i in 0...jsonCards[set].count - 1 {
let card = Card(id: jsonCards[set][i]["cardId"].stringValue, name: jsonCards[set][i]["name"].stringValue, cardSet: set, type: jsonCards[set][i]["type"].stringValue, faction: jsonCards[set][i]["faction"].stringValue, rarity: jsonCards[set][i]["rarity"].stringValue, cost: jsonCards[set][i]["cost"].intValue, attack: jsonCards[set][i]["attack"].intValue, durability: jsonCards[set][i]["durability"].intValue, text: jsonCards[set][i]["text"].stringValue, flavor: jsonCards[set][i]["flavor"].stringValue, artist: jsonCards[set][i]["artist"].stringValue, health: jsonCards[set][i]["health"].intValue, collectible: jsonCards[set][i]["collectible"].boolValue, playerClass: jsonCards[set][i]["playerClass"].stringValue, howToGet: jsonCards[set][i]["howToGet"].stringValue, howToGetGold: jsonCards[set][i]["howToGetGold"].stringValue, mechanics: [""], img: jsonCards[set][i]["img"].stringValue, imgGold: jsonCards[set][i]["imgGold"].stringValue, race: jsonCards[set][i]["race"].stringValue, elite: jsonCards[set][i]["elite"].boolValue, locale: jsonCards[set][i]["locale"].stringValue)
if jsonCards[set][i]["mechanics"].count > 0 {
for n in 0...jsonCards[set][i]["mechanics"].count - 1 {
card.mechanics.append(jsonCards[set][i]["mechanics"][n]["name"].stringValue)
}
}
else {
card.mechanics.append("")
}
self.allCard.append(card)
}
}
else {
print("The set \(set) has no cards")
}
}
print(self.allCard.count)
}
}
else {
print("No network")
}
self.dispatchGroup.leave()
}
}
}
View Controller:
import UIKit
class ViewController: UIViewController {
let allcards = AllCards()
let mygroup = DispatchGroup()
#IBAction func updateBtn(_ sender: Any) {
print(allcards.allCard.count) //Button is frozen until all the cards have been created then it shows the correct number of cards
}
override func viewDidLoad() {
super.viewDidLoad()
print(allcards.allCard.count) / This returns 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is an example of completion handler.
First you have to write a function in a single class ex: APICall
func getDataFromJson(allCardsHTTP: String, completion: #escaping (_ success: Any) -> Void) {
Alamofire.request(allCardsHTTP, method: .get).responseJSON { response in
if response.result.isSuccess {
completion(response)
}
}
}
and call this method from any class.
let callApi = APICall()
callApi.getDataFromJson(allCardsHTTP: "https://omgvamp-hearthstone-v1.p.mashape.com/cards?mashape",completion: { response in
print(response)
})
1) if you pass an object via a UIStoryboard segue, it's set before viewDidLoad() is called. However if you want to wait for UI elements to be ready, I generally go for a didSet on the UI element, you could add a guardstatement checking your object in there if you want.
2) first of all you'll probably want a closure so maybe read 3) first. you're using dispatchGroup.enter() here, DispatchQueue.global.async { } is the usual way to accomplish what you're doing. Add in a DispatchQueue.main.async { } when done if you want, or dip into the main thread in the view controller, up to you really. Look into the differences between [unowned self]
and [weak self] when you have the time.
3) Give your Card object an init(from: JSON) initialiser where it parses its properties from the JSON object you're passing it.
Let the function responsible for the download (Alamofire in your case) live in a different class (like for example APIClient) and give it a download function with a closure in the argument list like completion: ((JSON?) -> ())? to return the JSON object. Let that class download a JSON object and then initialise your Cardobject with the init(from: JSON) initializer you wrote earlier. Note that this isn't an approach fit for use with Core Data NSManagedObjects so if you'll need local storage maybe keep that in mind.
In the end you should be able to build an array of Cards something like this:
APIClient.shared.fetchCards(completion: { cardJSONs in
let cards = [Card]()
for cardJSON: JSON in cardJSONs {
let card = Card(from; JSON)
cards.append(card)
}
}

How to handle SignalProducer with ReactiveSwift and Firebase asynchronous method calls?

I am working on an iOS App with Swift 3 using ReactiveSwift 1.1.1, the MVVM + Flow Coordinator pattern and Firebase as a backend. I only recently started to adapt to FRP and I am still trying to figure out how to integrate new functionalities into my existing code base.
For instance, my model uses a asynchronous method from Firebase to download thumbnails from the web and I want to provide a SignalProducer<Content, NoError> to subscribe from my ViewModel classes and observe, if thumbnails have been downloaded, which then updates the UI.
// field to be used from the view-models to observe
public let thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
// TODO: send next content via completion below
}
// thumbnail download method
public func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
debugPring("Error id")
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
debugPrint("Error download")
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// TODO: emit signal with content
// How to send the content via the SignalProducer above?
})
}
I have also tried something similar with Signal<Content, NoError>, whereas I used the Signal<Content, NoError>.pipe() method to receive a (observer, disposable) tuple and I saved the observer as a private global field to access it form the Firebase callback.
Questions:
Is this the right approach or am I missing something?
How do I emit the content object on completion?
UPDATE:
After some hours of pain, I found out how to design the SingalProducer to emit signals and to subscribe from the ViewModels.
Maybe the following code snippet will help also others:
// model protocol
import ReactiveSwift
import enum Result.NoError
public protocol ContentService {
func findThumbnail(bucketId: String, contentId: String)
var thumbnailContentProducer: SignalProducer<Content, NoError> { get }
}
// model implementation using firebase
import Firebase
import FirebaseStorage
import ReactiveSwift
public class FirebaseContentService: ContentService {
// other fields, etc.
// ...
private var thumbnailContentObserver: Observer<Content, NoError>?
private var thumbnailContentSignalProducer: SignalProducer<Content, NoError>?
var thumbnailContentProducer: SignalProducer<Content, NoError> {
return thumbnailContentSignalProducer!
}
init() {
thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
self.thumbnailContentObserver = observer
}
}
func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
// TODO handle error
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
// TODO handle error
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// emit signal
self.thumbnailContentObserver?.send(value: content)
})
}
}
// usage from a ViewModel
contentService.thumbnailContentProducer
.startWithValues { content in
self.contents.append(content)
}
Maybe someone can verify the code above and say that this is the right way to do it.
I think you were on the right path when you were looking at using Signal with pipe. The key point is that you need to create a new SignalProducer for each thumbnail request, and you need a way to combine all of those requests into one resulting signal. I was thinking something like this (note this is untested code, but it should get the idea across):
class FirebaseContentService {
// userService and storageThumbnail defined here
}
extension FirebaseContentService: ReactiveExtensionsProvider { }
extension Reactive where Base: FirebaseContentService {
private func getThumbnailContentSignalProducer(bucketId: String, contentId: String) -> SignalProducer<Content, ContentError> {
return SignalProducer<Content, ContentError> { (observer, disposable) in
guard let userId = self.base.userService.getCurrentUserId() else {
observer.send(error: ContentError.invalidUserLogin)
return
}
let ref = self.base.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
observer.send(error: ContentError.contentNotFound)
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
observer.send(value: content)
observer.sendCompleted()
})
}
}
}
class ThumbnailProvider {
public let thumbnailSignal: Signal<Content, NoError>
private let input: Observer<(bucketId: String, contentId: String), NoError>
init(contentService: FirebaseContentService) {
let (signal, observer) = Signal<(bucketId: String, contentId: String), NoError>.pipe()
self.input = observer
self.thumbnailSignal = signal
.flatMap(.merge) { param in
return contentService.reactive.getThumbnailContentSignalProducer(bucketId: param.bucketId, contentId: param.contentId)
.flatMapError { error in
debugPrint("Error download")
return SignalProducer.empty
}
}
}
public func findThumbnail(bucketId: String, contentId: String) {
input.send(value: (bucketId: bucketId, contentId: contentId))
}
}
Using ReactiveExtensionsProvider like this is the idiomatic way of adding reactive APIs to existing functionality via a reactive property.
The actual requesting code is confined to getThumbnailContentSignalProducer which creates a SignalProducer for each request. Note that errors are passed along here, and the handling and conversion to NoError happens later.
findThumbnails just takes a bucketId and contentId and sends it through the input observable.
The construction of thumbnailSignal in init is where the magic happens. Each input, which is a tuple containing a bucketId and contentId, is converted into a request via flatMap. Note that the .merge strategy means the thumbnails are sent as soon as possible in whatever order the requests complete. You can use .concat if you want to ensure that the thumbnails are returned in the same order they were requested.
The flatMapError is where the potential errors get handled. In this case it's just printing "Error download" and doing nothing else.

RealmSwift: Detach an object from Realm, including its properties of List type

I want to create a duplicate of a persisted object such that the new instance has all the same values but is not attached to Realm. Using Object(value: persistedInstance) works great for classes whose properties are all strings, dates, numbers, etc. However, when duplicating an instance of a class with List type properties, the duplicate's List and the List's elements continue to reference the persisted records. How can I create a duplicate that's fully detached from Realm, including any Lists and elements in those Lists?
You can make a deep copy of your object via the following extension functions:
import UIKit
import Realm
import RealmSwift
protocol RealmListDetachable {
func detached() -> Self
}
extension List: RealmListDetachable where Element: Object {
func detached() -> List<Element> {
let detached = self.detached
let result = List<Element>()
result.append(objectsIn: detached)
return result
}
}
#objc extension Object {
public func detached() -> Self {
let detached = type(of: self).init()
for property in objectSchema.properties {
guard
property != objectSchema.primaryKeyProperty,
let value = value(forKey: property.name)
else { continue }
if let detachable = value as? Object {
detached.setValue(detachable.detached(), forKey: property.name)
} else if let list = value as? RealmListDetachable {
detached.setValue(list.detached(), forKey: property.name)
} else {
detached.setValue(value, forKey: property.name)
}
}
return detached
}
}
extension Sequence where Iterator.Element: Object {
public var detached: [Element] {
return self.map({ $0.detached() })
}
}
Use
/// in collections
let data = realm.objects(AbcDfg.self).detached
/// single object
data.first?.detached()
This is not yet supported natively by Realm, but a requested feature tracked by issue #3381.
For now, you would need to implement your own deep copy constructor. A common strategy is to do that on every model and call the deep copy constructors of related objects. You need to pay attention though that you don't run into cycles.
We use ObjectMapper to create a deep copy of the object by turning into JSON and then turn that JSON back into same object except it's not associated with Realm.
Mike.
As mentioned in the issue tracked at #3381, the solution for now is an implementation to create detached copies from Realm objects.
There is a better version of the detachable object implementation at
https://github.com/realm/realm-cocoa/issues/5433#issuecomment-415066361.
Incase the link doesnot work, the code by Alarson93 is:
protocol DetachableObject: AnyObject {
func detached() -> Self
}
extension Object: DetachableObject {
func detached() -> Self {
let detached = type(of: self).init()
for property in objectSchema.properties {
guard let value = value(forKey: property.name) else { continue }
if property.isArray == true {
//Realm List property support
let detachable = value as? DetachableObject
detached.setValue(detachable?.detached(), forKey: property.name)
} else if property.type == .object {
//Realm Object property support
let detachable = value as? DetachableObject
detached.setValue(detachable?.detached(), forKey: property.name)
} else {
detached.setValue(value, forKey: property.name)
}
}
return detached
}
}
extension List: DetachableObject {
func detached() -> List<Element> {
let result = List<Element>()
forEach {
if let detachable = $0 as? DetachableObject {
let detached = detachable.detached() as! Element
result.append(detached)
} else {
result.append($0) //Primtives are pass by value; don't need to recreate
}
}
return result
}
func toArray() -> [Element] {
return Array(self.detached())
}
}
extension Results {
func toArray() -> [Element] {
let result = List<Element>()
forEach {
result.append($0)
}
return Array(result.detached())
}
}
In Realm 5.0.0 freeze() method has been added.
according to release notes:
Add support for frozen objects. Realm, Results, List and Object now have freeze() methods which return a frozen copy of the object. These objects behave similarly to creating unmanaged deep copies of the source objects. They can be read from any thread and do not update when writes are made to the Realm, but creating frozen objects does not actually copy data out of the Realm and so can be much faster and use less memory. Frozen objects cannot be mutated or observed for changes (as they never change). (PR #6427).
Previously answered here
As of now, Dec 2020, there is not proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.

Resources