I'm looking at the Overcoat library which from what I gather is a library that extends the Mantle library.
Mantle: https://github.com/Mantle/Mantle/
Overcoat: https://github.com/gonzalezreal/Overcoat
The Mantle and Overcoat github pages keeps mentioning about creating a Mantle model but I want to know how do I generate the Mantle model? Do I manually type it out or do I use Xcode xcdatamodel file to build it visually, then generate the sublass + modify that file afterwards?
In Core Data do create the entity in the xcdatamodel file using the Interface Builder, then use Xcode's Editor > Create NSManagedObject subclass.
Do we do the same for Mantle and then change from NSManagedObject to MTLModel ?
What happens when we decided to update the Core Data entity in the xcdatamodel file? If we regenerate the model file again, wouldn't we have to re-add all those changes to the NSManagedObject class?
Super confused about the process.
OK, I think I'm starting to grasp it a bit more. After a couple of hours of trial and error, I was able to get a basic Overcoat demo app working with Core Data pulling from my REST API.
I got it to work like so:
1) We create the Entities inside the xcdatamodel file BUT DO NOT generate the NSManagedObject class using Editor > Create NSManagedObject Classes menu.
2) Create the Mantle model subclass per the normal Right Click project folder (in Xcode) > New File > Choose MTLModel as subclass and then manually entering in the properties. Notably, the subclass header should be something like:
#interface Book : MTLModel <MTLJSONSerializing, MTLManagedObjectSerializing>
#property (nonatomic, copy) NSString *title;
...
#end
3) If you accidentally generated the Core Data entity like me, the xcdatamodel file actually adds the class name in the "Default" section under "Configuration" inside the xcdatamodel.
You need to delete any value in the "Class" column otherwise you end up with a bad crash saying:
"XYZ" is not a subclass of NSManagedObject.
4) Ensure in your Mantle model class you implement the serializing methods for MTLJSONSerialization and MTLManagedObjectSerializing.
#pragma mark - MTLJSONSerialization -
+(NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"title": #"title",
...
};
}
#pragma mark - MTLManagedObjectSerializing -
+(NSString *)managedObjectEntityName
{
// ------------------------------------------------
// If you have a Core Data entity called "Book"
// then you return #"Book";
//
// Don't return the Mantle model class name here.
// ------------------------------------------------
return #"TheCoreDataEntityName";
}
+(NSDictionary *)managedObjectKeysByPropertyKey
{
// ------------------------------------------------
// not really sure what this does, I just put
// it in as the example does it too
// ------------------------------------------------
return #{};
}
These methods essentially is the glue mapping the JSON response from the server to the Core Data Entities.
5) One more important thing that got me is the way the server return responses.
Your server might use HTTP Status code and no top level JSON dictionary
e.g.
// no top level JSON dictionary, purely just an array of results
{
{
title: "ABC",
...
},
{
title: "ABC",
...
},
{
title: "ABC",
...
},
}
Whereas, other types of REST server might return a top level JSON dictionary with the results key path at a sub level like so:
{
count: 20,
next: "http://www.server.com/api/resource?page=2",
previous: null,
results:(
{
title: "ABC",
...
},
{
title: "ABC",
...
},
{
title: "ABC",
...
})
}
In the latter case, that's known as an "Envelop" type of response from my vague understanding. For these type of server responses, there is an extra step that involves telling Overcoat where the array of results key path is in the JSON response.
To do this, we need to:
5a) Create a ServerResponse class that is a subclass of OVCresponse:
// .h file
#import "OVCResponse.h"
#interface ServerResponse : OVCResponse
#end
// .m file
#implementation ServerResponse
+(NSString *)resultKeyPathForJSONDictionary:(NSDictionary *)JSONDictionary
{
// --------------------------------------------------------------------
// we're telling Overcoat, the array of entities is found under the
// "results" key-value pair in the server response JSON dictionary
// --------------------------------------------------------------------
return #"results";
}
#end
5b) In your APIClient class (which should be a subclass of OVCHTTPSessionManager), override the method:
+(Class)responseClass
{
// --------------------------------------------------
// ServerResponse class will let Overcoat know
// where to find the results array
// --------------------------------------------------
return [ServerResponse class];
}
Hopefully this helps anyone else who's having the same problem trying to get Overcoat working.
Related
I am trying to implement objects similar to CNContact and CNMutableContact such that I can retrieve my immutable objects from a custom store and make editions only to mutable copies of the objects.
To tackle this I have taken a look at the CNContact source available through Xcode to find this:
open class CNContact : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
/*! The identifier is unique among contacts on the device. It can be saved and used for fetching contacts next application launch. */
open var identifier: String { get }
open var contactType: CNContactType { get }
...
}
However when I try something similar I get an error:
class MyModel: NSObject, NSCoding, NSSecureCoding {
// MARK: - Properties
var name: String { get }
...
}
The error being Expected '{' to start a getter definition. What am I doing wrong here?
The next step then would be to have a mutable subclass, very similar to CNMutableContact which would looks like this:
class MutableMyModel: MyModel {
// MARK: - Properties
var name: String
...
}
If my approach to this problem is sensible then why am I seeing this syntax error, and why is it valid within the Apple written code but not my own?
Finally am I going to hit any issues within my subclass where I am essentially redefining the name property?
var name: String { get } is only a definition in the protocol. A conforming class, such as MyModel needs to implement the definition, like so:
class MyModel: NSObject, NSCoding, NSSecureCoding {
var name: String { return "String Value here" }
}
As being new to Swift
i don't know how to serialize a class into xml
Class Employee
{
var mName : String = ""
var Name : String
{
get
{
return mName
}
set
{
mName = newValue
}
}
var mDesingation : String = ""
var Desingation: String
{
get
{
return mDesingation
}
set
{
mDesingation = newValue
}
}
}
I have searched a lot but haven't come across any XML Serialization Engine for Swift.
XML Serialization
For XML serialization, I suggest you use following library:
https://github.com/skjolber/xswi
As the usage is quite easy but well documented, I won't copy it here, instead you can just use examples that they provide. Since your class is very easy, it is sufficient solution. AFAIK there is no library that provides automatic serialization, because it is not used on iOS. Core data provide you with option to serialize as XML, but that is highly problematic and mostly not usable for what you want.
NSCoding / NSKeyedArchiver
If you need to just store the class on the disk and load it again, there is better option, and that is to use NSKeyedArchiver / NSCoding protocol. Again, there is great article about how to use it with extensive examples, so just the basics:
You extend your class so it conforms to NSCoding protocol
You write implementation of two methods - encodeWithCoder: and initWithDecoder:
You use NSKeyedArchiver to archive your class
You write NSData that you save to disk (and vice versa)
Hope it helps!
I am currently working on an iOS app using Swift. I have a custom DataSource that needs to know the Type of the model that it has to provide. I have a custom Protocol, that has one method.
protocol JSONModel {
init(json: JSON)
}
Then, there are several models that implement the protocol. All of them have different properties, but are the same otherwise.
class CurrentDownload: JSONModel {
let someProperty: String
required init(json: JSON) {
someProperty = json["someProperty"].stringValue
}
}
My DataSource has a property that is of type JSONModel.Type
private let modelClass: JSONModel.Type
When i try to initialize a new instance of my modelClass i get a segmentation fault. Initialization of the model is done by
let model = modelClass(json: modelJSON)
Unfortunately, the compiler crashes on that line.
Swift Compiler Error
Command failed due to signal: Segmentation fault: 11
1. While emitting IR SIL function
#_TFC14pyLoad_for_iOS21RemoteTableDataSourceP33_56149C9EC30967B4CD75284CC9032FEA14handleResponsefS0_FPSs9AnyObject_T_ for 'handleResponse' at RemoteTableDataSource.swift:59:13
Does anybody have an idea on how to fix this or on how to work around this issue?
I believe this problem isn't too hard.
Usually with Swift segmentation faults are when you try to set the value of a constant (let) quantity, or try to set the value of something that hasn't been properly declared.
I can spot one such instance here:
required init(json: JSON) {
bytesLeft = json["someProperty"].stringValue
}
There's two things wrong with this example. You have a (designated) initialiser which terminates without setting the property someProperty and you haven't declared the variable bytesLeft.
So now the problem (which I really should have spotted before) is that * modelClass* just is not a class (or otherwise initialisable type). To cannot directly access a protocol, you can only access a class conforming to a protocol. The compiler didn't spot this because you did something sneaky with .Type.
When I say access, I mean functions and properties, including initialisers.
Only a class can create an instance of a class, which has itself as the type of the instance, and protocols can't have bonafide instances themselves.
If you think carefully about it, it is impossible to well-define what you are trying to do here. Suppose we had the protocol
protocol JSONModel {
init(json: JSON)
}
but then two classes:
class CurrentDownload: JSONModel {
let someProperty: String
required init(json: JSON) {
//Some other code perhaps.
someProperty = json["someProperty"].stringValue
}
}
class FutureDownload: JSONModel {
let differentProperty: String
required init(json: JSON) {
//Different code.
differentProperty = json["differentProperty"].stringValue
}
}
whereas before one might argue that
JSONModel.Type(json: JSON)
(this code is equivalent to your code) should compile and run because we have given an implementation of init, we now can't deny that there is confusion - which implementation should we use here?? It can't choose one, and most would argue that it shouldn't try, and so it doesn't.
What you need to do is initialise with some class.
If you are looking for a minimal class that adheres to JSONModel and no more, you'll need to write such a class e.g.
class BasicJSONModel: JSONModel {
let someProperty: String
required init(json: JSON) {
//Some other code perhaps.
someProperty = json["someProperty"].stringValue
}
}
Then do
private let modelClass: BasicJSONModel//.Type not needed with a class
let model = modelClass(json: modelJSON)
of course this is probably silly and one may just write
let model = BasicJSONModel(json: modelJSON)
Or you could just write a common superclass if the protocol's only use is for this:
class SuperJSONModel {
let someProperty: String//This is not needed of course and you could just strip down to the init only.
init(json: JSON) {
//Some other code perhaps.
someProperty = json["someProperty"].stringValue
}
}
and then is you wanted to check that some object is "conforming" to the what was the JSONModel, you simply check that it is a subclass of SuperJSONModel.
I need your help with something, as I can't get my head around this. I'm using Mantle together with CoreData in iOS.
I have relationships defined that look as follows:
Post 1:N Comment
When I pull the data from my REST Service, I create a Mantle Object Post that has a NSMutableArray of Comments in it. This works flawlessly.
I then store this in Core Data, and this is where I don't know whether I am doing things right.
[MTLManagedObjectAdapter managedObjectFromModel:post insertingIntoContext:[self getManagedObjectContext] error:&error];
So I'm doing this to store my post object into Core Data. The Core Data Model has a relationship called "post_has_comments" which is a cascading One-to-Many relationship. So on the object Post I have "posts_has_comments" -> cascading, on my object "Comment" I have a one-to-one relationship with "Nullify".
Afaik, Core Data treats this as a NSSet. What I'm trying to put in is a NSMutableArray though, as Mantle will take care of this (at least thats what a quick look in its source told me).
Unfortunately, when I get the object back from Core Data with
Post* post = [MTLManagedObjectAdapter modelOfClass:Post.class fromManagedObject:[[self fetchedResultsController] objectAtIndexPath:indexPath] error:nil];
The property comments on the post object is a empty NSSet, and I get quite some errors upon inserting the thing beforehand. The errors I get:
Core Data: annotation: repairing missing delete propagation for to-many relationship post_has_comments on object [...]
I am stuck - Maybe I am missing something huge here?
My Post Class implements the following static methods:
+ (NSDictionary *)managedObjectKeysByPropertyKey {
return #{
#"post_id" : #"id",
#"comments" : #"post_has_comments"
};
}
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"post_id" : #"id",
};
}
+ (NSDictionary *)relationshipModelClassesByPropertyKey {
return #{
#"comments" : IHComment.class
};
}
A simple workaround is to write your own property setter method and if value being set is NSSet then convert it to NSMutableArray before setting it back to your property ivar.
For example:
- (void)setComments:(NSMutableArray *)comments {
if ([comments isKindOfClass:NSSet.class]) {
_comments = [NSMutableArray arrayWithArray:[((NSSet *)comments) allObjects]];
} else {
_comments = comments;
}
}
I've done it myself quite a few times and it works like a charm!
From the Mantle documentation:
Mantle makes it easy to write a simple model layer for your Cocoa or Cocoa Touch application.
This is simply an unproven statement. Looking at the framework, I do not see where the evidence is. You should get your objects, and insert them into Core Data using Apple's APIs.
Post *cdPost = [NSEntityDescription insertNewObjectForEntityForName:#"Post"
inManagedObjectContext:self.managedObjectContext];
// configure the cdPost object with the data from the web service
for (id commentObject in commentArrayFromPostObject) {
Comment *cdComment =
[NSEntityDescription insertNewObjectForEntityForName:#"Comment"
inManagedObjectContext:self.managedObjectContext];
// configure the cdComment object with the data from the web service
cdComment.post = cdPost;
}
That's all there is to it.
What is the best way to structure a class that has a to many relationship in it?
I come from a C# background. So let me show you how I would do it in C#.
class User {
public List<string> Items { get; set; }
}
Code to access Items:
User u = new User();
u.Items = new List<string>();
u.Items.Add( "foo" );
u.Items.Add( "bar" );
foreach( string s in u.Items ) {
Console.WriteLine( s );
}
How do I do this in Objective-C?
You need to learn ObjC in detail:
Class in ObjC: right click -> create new file and select ObjC class
Each class have property in .h file for you to access outside. Ex list here is NSMultableArray
//USer.h
#interface User: NSObject
{}
#property(nonatomic, strong) NSMutableArray *items;
#end
//User.m
#implementation User
-(id)init{
if (self = [super init]){
self.items = [NSMutableArray array];
}
return self;
}
#end
//In other class
#import "User.h"
User *u = [[User alloc] init];
[u.items addObject:#"foo"];
[u.items addObject:#"bar"];
for (NSString *aStr in u.items) {
NSLog (#"%#",aStr)
}
Objective-C is just a language, it does not contain any framework per se. As C# doesn't have List which comes from .NET framework.
First you need to learn the language by reading one of many guides around, from example the official one from Apple here. Then you can read some reference to Foundation, which is the framework with most basic library support (for lists or other generic elements). You can start from here. Take a look at NSString, NSMutableArray and so on.