Code Monkey home page Code Monkey logo

alamofirecoredata's Introduction

AlamofireCoreData

A nice Alamofire serializer that convert JSON into CoreData objects.

With AlamofireCoreData, you will have your JSON mapped and your NSManagedObject instances inserted in your context with just a few lines:

// User is a `NSManagedObject` subclass
Alamofire.request(url)
    .responseInsert(context: context, type: User.self) { response in
        switch response.result {
        case let .success(user):
            // The user object is already inserted in your context!
        case .failure:
            // handle error
        }
}

Internally, AlamofireCoreData uses Groot to serialize JSON into the CoreData objects, so you will need to be familiar with it to use this library. Groot is wonderful and it is very well documented so it shouldn't be a problem to get used to it if you are not.

AlamofireCoreData is built around Alamofire 4.0.x

Installing AlamofireCoreData

Using CocoaPods

Add the following to your Podfile:

pod 'AlamofireCoreData', '~> 2.0.0'

Then run $ pod install.

And finally, in the classes where you need AlamofireCoreData:

import AlamofireCoreData

If you don’t have CocoaPods installed or integrated into your project, you can learn how to do so here.

--

Usage

First steps

The first thing you need to do to user AlamofireCoreData is making your models serializable with Groot.

Check out the Groot project to know how.

Inserting a single object

Let's supose we have a NSManagedObject subclass called User. We also have an API which will return a JSON that we want to convert to a instance of User and insert it in a given NSManagedObjectContext.

Then, we just have to call the method responseInsert of the Alamofire request and pass the context and the type of the object as parameters:

// User is a `NSManagedObject` subclass
Alamofire.request(url)
    .responseInsert(context: context, type: User.self) { response in
        switch response.result {
        case let .success(user):
            // The user object is already inserted in your context!
        case .failure:
            // handle error
        }
}

If the serialization fails, you will have an instance of InsertError.invalisJSON in your .failure(error)

Inserting a list of objects

Serializing a list of object is also easy. If your api returns a list of User, you can insert them all in your context by using Many<User> as the expected type:

// User is a `NSManagedObject` subclass
Alamofire.request(url)
    .responseInsert(context: context, type: Many<User>.self) { response in
        switch response.result {
        case let .success(users):
            // users is a instance of Many<User>
        case .failure:
            // handle error
        }
}

The struct Many is just a wrapper around Array and it's intended to be used in the same way you would use an Array. In any case, you can access to its raw Array by calling its propery array.

Transforming your JSON

In some cases, the data we get from the server is not in the right format. It could even happens that we have a XML where one of its fields is the JSON we have to parse (yes, I've found things like those 😅). In order to solve this issues, responseInsert has an additional optional parameter that you can use to transform the response into the JSON you need. It is called jsonSerializer:

Alamofire.request(url).responseInsert(
    jsonSerializer: jsonTransformer, 
    context: context, 
    type: User.self) 

jsonTransformer is just a Alamofire.DataResponseSerializer<Any>. You can build your serializer as you want; the only condition is that it must return the JSON which you expect and which can be serialized by Groot.

To build this serializer, you could use the Alamofire built-in method:

public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<Value>)

AlamofireCoreData brings two convenience methods to make easier building this serializers:

  • A custom DataRequestSerializer initializer
public init<ParentValue>(
        parent: DataResponseSerializer<ParentValue>,
        transformer: @escaping (ResponseInfo, Result<ParentValue>) -> Result<Value>
        )

where the response is processed by the parent parameter and then the Result is converted by the transformer closure.

  • A DataRequest class method
public static func jsonTransformerSerializer(
        options: JSONSerialization.ReadingOptions = .allowFragments,
        transformer: @escaping ((ResponseInfo, Result<Any>) -> Result<Any>)
        ) -> DataResponseSerializer<Any>

where the response is converted into a JSON and then the Result is converted by the transformer closure.

Let's see an example of this second method. We have this response:

{
  "success": 1,
  "data": { "id": 1, "name": "manue"}
}

We need a serializer which perform two tasks:

  • Check the success key to know if the request finished succesfully and send an error if not
  • Discard the success parameter and just send the contents of data to serialization.

So, we can create this serializer:

let jsonTransformer = DataRequest.jsonTransformerSerializer { (responseInfo, result) -> Result<Any> in
    guard result.isSuccess else {
        return result
    }
    
    let json = result.value as! [String: Any]
    let success = json["success"] as! NSNumber
    switch success.boolValue {
    case true:
        return Result.success(json["data"]!)
    default:
        // here we should create or own error and send it
        return Result.failure(anError)
    }
}

And call the requests this way:

Alamofire.request(url).responseInsert(
    jsonSerializer: jsonTransformer, 
    context: context, 
    type: User.self) 

Using Wrapper

Sometimes, our models are not sent alone in the server responses. Instead, they could be wrapped into a bigger json with some additional useful info. For example, let's suppose that we have a response for our login request where we get the user info, the access token, the validity date for the token and a list of friends:

{
    "info": {
       "token": "THIS_IS_MY_TOKEN",
       "validity": "2020-01-01"
    },
    "user": {
    	"id": "1",
    	"name": "manue",
    },
    "friends": [
        {"id": 2, "name": "Ana"},
        {"id": 3, "name": "Mila"}
    ]
}

We need to not only inserting the User but also the token, validity and friends. To handle this, we have to create a new class or structure and adopt the Wrapper protocol. For example:

struct LoginResponse: Wrapper {
    var token: String!
    var validity: Date?
    var user: User!
    var friends: Many<User>!
    
    // required by protocol
    init () {}
    
    // provides info to convert the json
    mutating func map(_ map: Map) {
        token <- map["info.token"]
        validity <- (map["info.validity"], dateTransformer)
        user <- map["user"]
        friends <- map["friends"]
    }
}

The map function must use the same syntax as the example shows, using the <- operator. Some notes:

  • If the var is a NSManagedObject, a Many<NSManagedObject, another Wrapper or a Many<Wrapper>, the object is serialized and inserted.
  • Note that the collections must be a Many and not an Array. If you would use a Array<User> as friends type, the objects wouldn't be serialized or inserted.
  • You can add transformers to change the type of the JSON value. In the exaple, the validity field of the JSON is a String but we need a Date. We pass dateTrasformer which is just a function that takes an String and turn it into a Date.

Now, we can call the same method as before but with the LoginResponse as the expected type:

Alamofire.request(loginURL)
    .responseInsert(context: context, type: LoginResponse.self) { response in
        switch response.result {
        case let .success(response):
            // The user and friends are already inserted in your context!
            let user = response.user 
            let friends = response.friends 
            let validity = response.validity 
            let token = response.token
            
        case .failure:
            // handle error
        }
}

Root keypath

There is a special case when we want to map to an object which is in the root level of the JSON. For example, if we have a Pagination object that implements Wrapper:

struct Pagination: Wrapper {
	var total: Int = 0
	var current: Int = 0
	var previous: Int?
	var next: Int?	
	
	// MARK: Wrapper protocol methods
    required init() {}
    
    mutating func map(map: Map) {
        total <- map["total"]
        current <- map["current"]
        previous <- map["previous"]
        next <- map["next"]
   }
}

And the response that we have is:

{
	"total": 100,
	"current": 3,
	"previous": 2,
	"next": 4,
	
	"users": [
		{"id": "1", "name": "manue"},
		{"id": "2", "name": "ana"},
		{"id": "3", "name": "lola"}
	]
}

Look that the pagination is not under any key, but it is in the root of the JSON. In this case, we can create the next object:

class UserListResponse: Wrapper {
	var pagination: Pagination!
	var users: Many<User>!
	
	// MARK: Wrapper protocol methods
    required init() {}
    
    func map(map: Map) {
        pagination <- map[.root] // Look that we use `.root` instead of a string
        users <- map["users"]
    }
}

--

Contact

Manuel García-Estañ Martínez
@manueGE

License

AlamofireCoreData is available under the MIT license.

alamofirecoredata's People

Contributors

manuege avatar southfox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

alamofirecoredata's Issues

trying to refactor the request method

I am trying to create one function for different types and requests.

In your call a Many.self when ist's an Array of objects.

How can i realism to get the Object, so the NSManagedObject out of the variables?

So I end up with something like:

Alamofire.request(url)
    .responseInsert(context: context, type: Many<VARIABLE>.self) { response in
        switch response.result {
        case let .success(user):
            // The user object is already inserted in your context!
        case .failure:
            // handle error
        }
}

Xcode 10 + iOS 12

Hi there,

first of all, good repository, we are using AlamofireCoreData for a while since last year, and we are really happy with that. Thanks for sharing this.

I'm having an issue compiling iOS 12 with Xcode 10, the folloging issue appears:

  • Invalid redeclaration of '<-'
//
//  Operator.swift
//  Alamofire+CoreData
...
public func <- <T: Insertable>( left: inout T!, right: MapValue?) {

Do you want me to send a PR on this? or you guys are working right now to fix the issue?

Thanks again,

Javier.

nil; value

Hi,

I'm trying to use this great pod but got an issue. Every value are nil;

Here my class News

extension News {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<News> {
        return NSFetchRequest<News>(entityName: "News")
    }

    @NSManaged public var content: String?
    @NSManaged public var created_at: NSDate?
    @NSManaged public var date_pub_beg: NSDate?
    @NSManaged public var date_pub_end: NSDate?
    @NSManaged public var id: Int16
    @NSManaged public var results: Bool
    @NSManaged public var rotation: Bool
    @NSManaged public var summary: String?
    @NSManaged public var title: String?
    @NSManaged public var updated_at: NSDate?

}

In my swift file I do


    private var appDelegate = UIApplication.shared.delegate as! AppDelegate
    private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

func getNews(url: String, completion: @escaping (Bool) -> Void)  {
        
        Alamofire.request(url).responseInsert(context: context, type: Many<News>.self) { response in
            switch response.result {
            case let .success(news):
                print (news)
                completion(true)
            case .failure:
                // handle error
                completion(false)
            
                print("Erreur")
            }
        }
    }

Calling my func print this in console log

[<News: 0x60800009fef0> (entity: News; id: 0x60800003c880 <x-coredata:///News/tBB28D896-033A-4084-807A-6A31993B8F2F2> ; data: {
    content = nil;
    "created_at" = nil;
    "date_pub_beg" = nil;
    "date_pub_end" = nil;
    id = 0;
    results = nil;
    rotation = nil;
    summary = nil;
    title = nil;
    "updated_at" = nil;
}), <News: 0x608000282170> (entity: News; id: 0x60800003ba00 <x-coredata:///News/tBB28D896-033A-4084-807A-6A31993B8F2F3> ; data: {
    content = nil;
    "created_at" = nil;
    "date_pub_beg" = nil;
    "date_pub_end" = nil;
    id = 0;
    results = nil;
    rotation = nil;
    summary = nil;
    title = nil;
    "updated_at" = nil;
})]

Here what my API sent

[
{
id: 1,
title: "Test d'article",
date_pub_beg: "2017-09-05",
date_pub_end: "2017-09-06",
summary: "Ceci est un test, AH BON ?",
content: "<div><strong>Petit test pour voir !</strong></div><ul><li>test 1</li><li>test 2</li><li>test 3</li></ul><div><br><a href="/attachments/6445bed34e1b29499ed49d7d6b84ab87c145b124/store/529632a6d060f7eb51c12ee5f7e66f8e02bd9b76092d50179f5c8f99537e/stephane.jpg" data-trix-attachment="{&quot;contentType&quot;:&quot;image/jpeg&quot;,&quot;filename&quot;:&quot;stephane.jpg&quot;,&quot;filesize&quot;:38987,&quot;height&quot;:308,&quot;href&quot;:&quot;/attachments/6445bed34e1b29499ed49d7d6b84ab87c145b124/store/529632a6d060f7eb51c12ee5f7e66f8e02bd9b76092d50179f5c8f99537e/stephane.jpg&quot;,&quot;image_id&quot;:&quot;529632a6d060f7eb51c12ee5f7e66f8e02bd9b76092d50179f5c8f99537e&quot;,&quot;url&quot;:&quot;/attachments/6445bed34e1b29499ed49d7d6b84ab87c145b124/store/529632a6d060f7eb51c12ee5f7e66f8e02bd9b76092d50179f5c8f99537e/stephane.jpg&quot;,&quot;width&quot;:308}" data-trix-content-type="image/jpeg"><figure class="attachment attachment--preview attachment--jpg"><img src="/attachments/6445bed34e1b29499ed49d7d6b84ab87c145b124/store/529632a6d060f7eb51c12ee5f7e66f8e02bd9b76092d50179f5c8f99537e/stephane.jpg" width="308" height="308"><figcaption class="attachment__caption"><span class="attachment__name">stephane.jpg</span> <span class="attachment__size">38.07 KB</span></figcaption></figure></a></div>",
created_at: "2017-09-05T07:43:18.011Z",
updated_at: "2017-09-07T02:50:35.414Z",
results: false,
rotation: false
},
{
id: 2,
title: "Victoire de la France",
date_pub_beg: "2017-11-27",
date_pub_end: "2017-12-31",
summary: "La France remporte la Coupe Davis.",
content: "<div>Au terme du dernier match, la France remporte la <strong>Coupe Davis.</strong></div>",
created_at: "2017-11-27T14:18:07.527Z",
updated_at: "2017-11-27T14:18:07.527Z",
results: false,
rotation: false
}
]

Everything seems to be ok, don't understand why those nil values.

Any idea ?

Thanks a lot

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.