I am following the current tutorial on creating a large class to handle seeding a large database with data.
http://www.andrewcbancroft.com/2015/02/25/using-swift-to-seed-a-core-data-database/
My database is populated using JSON, though I am wanting to copy the pattern the author uses in the above article.
During the article he mentions this approach violates the single-use responsibility. I am aware that classes should take a single responsibility, but given a situation such as mine where I will need to seed quite a large dataset when the user logs in for example, is there another approach to take?
I apologise if this comes off as inciting a discussion, that isn't my intention, my question is wether this style of seeding is commonplace in production or if not, what is the best pattern to implement this kind of data seeding.
I don't think it's possible to really answer how everyone imports data in production as everyone could do different things.
Instead, I just want to mention that according to Apple's "Core Data Programming Guide" the most efficient way to import data is via a batch import process. This process is detailed here.
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html
With that said I would store your data in a JSON file that is stored either on a web service, or in the app bundle as a resource, and then use the NSJsonSerialization class to convert it to foundation objects that your code can reason with. Then I would use the principals outlined in the guide above to create a bulk import process to seed your database.
That's pretty much it, and Apple's examples are pretty straight forward. I would also state it would be best to run this process on a background thread as the OS may terminate your application if the import takes a long time to complete.
Hope this helps!
* EDIT *
Here is an example of how you can use protocols and generics to perform tasks against any type of object. You can use this pattern to perform any type of operation, so just take the concept and enter your Core Data logic.
This is merely an example of a pattern one could follow, and shouldn't be considered a plug-in-play implementation. It will need to be adapted to support the Core Data bulk import and saving. However, it does clearly show a way to take a dictionary, or array of dictionaries, and decode them to objects. Then what you do with your objects is completely up to you.
protocol JSONDecodable {
// This is used so you can have a unified way to instantiate an instance without relying on sub-classing NSObject
init()
// This can be implemented so you can manually decode a single object of the type from a dictionary
static func decodeFromJSON(json: AnyObject?) -> AnyObject?
// This is used so that you can manually set the values to the object. This is required as Swift reflection doesn't support dynamic property setting
func setValueForKey(value: AnyObject?, forKey: String)
}
// This class is responsible for decoding a JSON dictionary into an object
class JSONDecoder<T:JSONDecodable>: NSObject {
//MARK: Initialization
required override init() {
// do any initialization here
}
//MARK: Public Methods
/**
Creates a single object from the JSON. This method should only be used if the JSON will only ever contain a single object
:json: A dictionary of data
:returns: An object of the given type
*/
func toSingle(json: AnyObject?) -> T? {
// process single object, and return an array with the one object
if let dict = json as? [NSObject: AnyObject] {
return self.makeObject(dict)
}
return nil
}
/**
Creates a list of objects from the JSON. This method should only be used if the JSON will contain multiple objects
:json: A dictionary of data
:returns: An list of objects of the given type
*/
func toArray(json: AnyObject?) -> [T]? {
// process array
if let arr = json as? [AnyObject] {
return self.makeObjects(arr)
} else if let dict = json as? [NSObject: AnyObject] {
// process single object, and return an array with the one object
var arr = [T]()
arr.append(self.makeObject(dict))
return arr
}
return nil
}
//MARK: The Magic
private func makeObjects(jsonArray: [AnyObject]?) -> [T]? {
var returnArray: [T] = [T]()
if let jArray = jsonArray {
for jObject in jArray {
if let dict = jObject as? [NSObject: AnyObject] {
returnArray.append(self.makeObject(dict))
}
}
}
if returnArray.count > 0 {
return returnArray
} else {
return nil
}
}
private func makeObject(jsonDict: [NSObject: AnyObject]) -> T {
var returnObject = T.self() // this is where the init() function in the protocol comes in handy. It allows us to use generics to create a dynamic instance of our object
for (key, value) in jsonDict {
if let k = key as? String {
returnObject.setValueForKey(value, forKey: k) // this is where the setValueForKey(value: AnyObject?, forKey: String) function in the protocol comes in handy. It allows us to let the object it's self set it's own values.
}
}
return returnObject
}
}
// This is an example class that implements the protocol which means it can be sent through the decoding process
class Employee: NSManagedObject, JSONDecodable {
//MARK: - Properties
var employeID: Int!
var name: Int!
var hireDate: NSDate?
var department: Department?
//MARK: - Initialization
override required init() {
// Necessary to satisfy the JSONDecodable protocol
}
static func decodeFromJSON(json: AnyObject?) -> AnyObject? {
var decoder = JSONDecoder<Employee>()
return decoder.toSingle(json)
}
func setValueForKey(value: AnyObject?, forKey: String) {
switch (forKey) {
case "employeID":
self.employeID = value as! Int
case "name":
self.name = value as! String
case "hireDate":
if let v = value as? String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
self.hireDate = dateFormatter.dateFromString(v)
}
case "department":
if let v = value as? [NSObject: AnyObject] {
if let dept = Department.decodeFromJSON(dict) as? Department {
self.department = dept
}
}
default:
NSLog("[setValueForKey] Unable to find property \(forKey)")
}
}
}
Related
I have developed an ios application that allow users to edit a musical score sheet and now i'd like to implement data persistence to prevent the discarding of changes.
Reading on ios documentation i've noticed that exists different ways to improve data persistence and I believe that the best way for my application is Core Data.
Considering that my application use a lot of custom object i met a lot of problems.
I'm trying to use core data to save an entity, referred to a score sheet, composed by two attributes:
name: String
score: Array of another custom object (Measure), composed by other custom object (Score Element)
According to documentation and other q/a I've decided to use a Trasformable type on the model:
So I've declared a generic class used as trasformer for score attribute:
public class NSSecureCodingValueTransformer<T: NSSecureCoding & NSObject>: ValueTransformer {
public override class func transformedValueClass() -> AnyClass { T.self }
public override class func allowsReverseTransformation() -> Bool { true }
public override func transformedValue(_ value: Any?) -> Any? {
guard let value = value as? T else { return nil }
return try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
}
public override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else { return nil }
let result = try? NSKeyedUnarchiver.unarchivedObject(
ofClass: T.self,
from: data as Data
)
return result
}
/// The name of this transformer. This is the name used to register the transformer using `ValueTransformer.setValueTransformer(_:forName:)`
public static var transformerName: NSValueTransformerName {
let className = NSStringFromClass(T.self)
return NSValueTransformerName("DHC\(className)ValueTransformer")
}
/// Registers the transformer by calling `ValueTransformer.setValueTransformer(_:forName:)`.
public static func registerTransformer() {
let transformer = NSSecureCodingValueTransformer<T>()
ValueTransformer.setValueTransformer(transformer, forName: transformerName)
}
}
Using in this way a DHCMeasureValueTransformer as trasformer in DataModel file.
The problem is that when i save, no error occurs but when i fetch data for a new restart of application, i can fetch just the name of score sheet, while the score array it's empty, like if no elements it's been put inside (clearly, before of save, i've try to print array content, that prove that i'm working with a non empty array)
Here is the code of the save:
static func saveContext() {
let context = getContext()
do {
try context.save()
} catch {
print("error during the save.")
}
}
And here is the code of two classes of the entity object:
// DataClass
#objc(ScoreSheet)
public class ScoreSheet: NSManagedObject {
static var supportsSecureCoding: Bool {
return true
}
}
//DataProperties
extension ScoreSheet {
#nonobjc public class func fetchRequest() -> NSFetchRequest<ScoreSheet> {
return NSFetchRequest<ScoreSheet>(entityName: "ScoreSheet")
}
#NSManaged public var name: String
#NSManaged public var score: [Measure]
}
Clearly Measure class implements NSSecureCoding and method for decode and encode the object.
Here is Measure class implementation:
import Foundation
class Measure: NSObject, NSCoding, NSSecureCoding {
var elements : [ScoreElement] = []
var timeSig : TimeSignature
var clef : Clef
static var supportsSecureCoding = true
init(time : TimeSignature, clef : Clef) {
self.timeSig = time
self.clef = clef
}
func encode(with encoder: NSCoder) {
encoder.encode(self.elements, forKey: "elements")
encoder.encode(self.timeSig, forKey: "timeSig")
encoder.encode(self.clef, forKey: "clef")
}
required convenience init? (coder decoder: NSCoder) {
let elements = decoder.decodeObject(forKey: "elements") as! [ScoreElement]
let timeSig = decoder.decodeObject(forKey: "timeSig") as! TimeSignature
let clef = decoder.decodeObject(forKey: "clef") as! Clef
self.init(time: timeSig, clef: clef)
self.elements = elements
}
}
I'm not sure what's going wrong but there are a couple of things that need fixing that might affect your results.
Firstly, the computed transformer name is not the same as the one you're trying to use. When this line executes, and T is Measure,
let className = NSStringFromClass(T.self)
Then className is going to be something like MyProjectName.Measure. The computed transformer name ends up as something like NSValueTransformerName(_rawValue: DHCMyProjectName.MeasureValueTransformer), which doesn't match what you're using in the data model. All of which means that your transformer isn't getting used.
But that probably doesn't matter because if Measure conforms to NSSecureCoding and all of Measure's properties (ScoreElement, TimeSignature, Clef) also conform to NSSecureCoding (which seems to be the case since your code isn't throwing exceptions), then you don't need a custom transformer at all. If a transformable property type conforms to NSSecureCoding then Core Data will automatically use NSSecureCoding. You don't need a custom transformer unless you don't want to or can't conform to NSSecureCoding for some reason. Because of this, it doesn't matter that your transformer isn't being used.
As for why Measure isn't surviving the encode/decode process, I don't know, but you may help clear things up by removing the distraction of the unnecessary encode/decode class. I'd also suggest putting a breakpoint in Measure in the encode(with:) and init(coder:) methods. You should hit those breakpoints when saving and fetching data.
I'm trying to create an array within an array with a GET Request I'm making. I want the data look something like
[heroesOfLOTR[Gandalf, Frodo, Aragorn], heroesOfStarWars[Yoda, Luke, ObiWan],heroesOfWheelOfTime[Rand, Matt, Perrin]]
I've looked through multiple posts about multi-dimensional arrays, but the data is usually with a set number, and does not work dynamically. I also see a lot of results that look like this:
[[1,2,3][1,2,3,4][1,2]]
Currently, thats what my data looks like, and can't figure to a solution to get it to work like the first array mentioned.
I've tried at least 10 or 15 combinations of code, but am unable to find a solution.
JSON
{ HeroLOTR heroes:[ {Sam} {Frodo} {Gandalf}], {HeroesOfStarWars heroes:[ {Yoda} {Luke} {ObiWan}]
There are a few things I'd recommend here:
First, create a more descriptive class or struct for your heroes. This isn't a requirement but will make your code much more readable and understandable as you continue development.
class Hero { // Switch "class" to "struct" if you want a struct
// Add whatever properties you want here
let name: String
init?(json: [String: Any]) {
guard let name = json["name"] as? String else {
return nil
}
self.name = name
}
}
Second, it appears you're trying to put each of the heroes into an array (a set would work here too). You can again create a class or struct to hold these heroes.
class HeroGroup { // Just switch "class" to "struct" if you want a struct
let name: String
let heroes: [Hero]
init?(json: [String: Any?]) {
guard let name = json["name"] as? String else {
return nil
}
self.name = name
self.heroes = (json["heroes"] as? [[String: Any]])?.flatMap { Hero(json: $0) } ?? []
}
}
Lastly, when you receive your response, you can just write code like the following in your completion closure:
guard let groups = response.value.arrayValue as? [[String: Any]] else {
return // Have JSON that you can't parse
}
// This will give you [HeroGroup]
self.groupsOfHeroes = groups.flatMap { HeroGroup(json: $0) } // This is a property on your view controller
tableView.reloadData()
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.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
When I try to connect the database by using external databases with an API on my application, I get an error in loadPostsfunction.
Error:
AnyObject is not convertible to String
Code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
service = PostService()
service.getPosts(){
(response) in
self.loadPosts(response["posts"]! as! NSArray)
}
}
/////////////////// In here ///////////////////////
func loadPosts(posts:NSArray) {
for post in posts {
var id = (post["Post"]!["id"]! as! String).toInt()!
var title = post["Post"]!["title"]! as String
var author = post["Post"]!["author"]! as String
var content = post["Post"]!["content"]! as String
}
}
////////////////////////////////////////////////////
//Link DB
var postsCollection = [Post]()
var service:PostService!
}
Any ideas please?
AnyObject is not convertible to String.
You should let it knows post["Post"] is NSDictionary or what type you define it.
func loadPosts(posts:NSArray) {
for post in posts {
var id = ((post["Post"]! as? NSDictionary)!["id"]! as! String).toInt()!
var title = (post["Post"]! as? NSDictionary)!["title"]! as String
var author = (post["Post"]! as? NSDictionary)!["author"]! as String
var content = (post["Post"]! as? NSDictionary)!["content"]! as String
}
}
There are multiple issues with your code, I will try to teach you how you can make it better so that you no longer receive the compile error, and you better handler various points of failure.
First things first, let's Swiftify your code and make use of the type system:
self.loadPosts(response["posts"]! as! NSArray)
can be better written as
if let postsDicts = response["posts"] as? [[String:AnyObject]] {
loadPosts(postsDicts)
} else {
// the server response didn't send the proper "posts"
}
, the optional binding allowing you to handle the scenario where the server sent an invalid response.
Secondly, let's specify loadPosts to accept an array of dictionaries (exactly the type to which we casted above):
func loadPosts(posts: [[String:AnyObject]])
And not lastly, all that forced unwraps (!) can make your app crash without thinking twice. The problem is that you can't control which data will the server send - a bug in a server can cause your application to crash if you blindly trust it's data, so you need to add safeguards to avoid app crashes due to invalid data:
func loadPosts(posts: [[String:AnyObject]]) {
for post in posts {
guard let postDetail = post["Post"] as? [String:AnyObject],
id = postDetail["id"] as? String,
title = postDetail["title"] as? String,
author = postDetail["author"] as? String,
content = postDetail["content"] as? String else {
continue
}
// I assume here you would construct a Post object
}
}
Keep in mind that NSArray can be safely caster to a Swift array (e.g [String], or as in your case [[String:AnyObject]], similarly a NSDictionary can be safely casted to a Swift dictionary. Working with Swift's data types give you more flexibility and can help you write less and more robust code.
I am attempting to store data in the local data store using parse.com(1.7.3) in Swift iOS. Following the parse.com docs here, I am able to subclass PFObject to store a simple object. However I am hoping to compose a custom object inside this object and am finding difficulties in doing this.
I have stripped this problem back to its most basic and include my code below. Basically I am storing a PFObject subclass called 'Test', which will store a String(testString), and a custom object called Test2. Test2 in turn will also store a String(testString). I assume that Test2 also needs to subclass PFObject.
Test - testString(String)
- test2(Test2)
- testString(String)
AppDelegate.swift
(Registering subclass in AppDelegate instead of Initialise method - see here for more details.)
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Test.registerSubclass()
Test2.registerSubclass()
Parse.enableLocalDatastore()
Parse.setApplicationId("...", clientKey: "...")
}
Test2.swift
class Test2:PFObject, PFSubclassing {
#NSManaged var testString:String
static func parseClassName() -> String {
return "Test2"
}
}
Test.swift
class Test:PFObject, PFSubclassing {
#NSManaged var testString:String
#NSManaged var test2:Test2
static func parseClassName() -> String {
return "Test"
}
}
ViewController.swift
override func viewDidLoad() {
saveTest() //toggle between save and load, don't do both!
//loadTest()
}
func saveTest() {
var test2 = Test2()
test2.testString = "I am Test 2"
var test = Test()
test.testString = "I am Test"
test.test2 = test2
test.pinInBackground()
}
func loadTest() {
let query = PFQuery(className:Test.parseClassName())
query.fromLocalDatastore()
query.findObjectsInBackgroundWithBlock({
(objects:[AnyObject]?, error: NSError?) in
if let error = error {
println("THERE WAS AN ERROR")
// There was an error
} else {
if let objects = objects as? [PFObject] {
for object in objects {
//We have an object!
println("We have a PFObject, now to cast as Test")
if let object = object as? Test {
println(object)
}
}
}
}
})
}
So the println in loadTest outputs the following in the console:
<Test: 0x7ff2da75e810, objectId: new, localId: local_15a085d00030543f> {
test2 = "<Test2: 0x7ff2db90fe50, objectId: new>";
testString = "I am Test";
}
If I try to access the test2 property or a property of test2, the app hangs. I also am not able to 'fetch' the Test object. Curiously If I load any Test2 objects stored locally(identical to the loadTest method above with any mention of Test replaced with Test2), I do find the Test2 object:
<Test2: 0x7f94b1f1e850, objectId: new, localId: (null)> {
testString = "I am Test 2";
}
So in pinning the test object, test2 is being pinned too. However the test2 object is not connecting to the test object for some reason.
I'm starting to get lost, and can't seem to find relevant(or accurate) documentation or sample projects which would probably clear all this up in a second... What is the best approach(if any) to compose a custom object inside a PFObject subclass?
Here's an idea which can help you in this case. Your Test class is a subclass of PFObject. And you have a custom object inside Test class, named Test2.
In your Test2.swift file, create a method that would return an NSDictionary object containing all properties of its object.
Somewhat similar to this :
class func objDetail() -> NSDictionary {
return [
"key1": self.property1,
"key2": self.property2,
// ...
] as NSDictionary
}
And you could use this dictionary to store your object details as an object in your Test object. Now, parse supports dictionary and you've converted it in dictionary. It would save the object.
Now add another method in your swift file to create object based on dictionary values.
convenience init(_ dictionary: Dictionary<String, AnyObject>) {
self.init()
title = dictionary["title"] as? NSString
shortDescription = dictionary["shortDescription"] as? NSString
newsDescription = dictionary["newsDescription"] as? NSString
link = dictionary["link"] as? NSURL
}
For retrieving object, in your loadTest method's query.findObjectsInBackgroundWithBlock call this object's init method and create object with values of dictionary you get from parse.
Hope, my idea helps you.. :)