iOS开源-轻量级 JSON 转 Model 框架,使用链式编程,无侵入
Eileen4640
8年前
<h2>SuperKVC</h2> <p>SuperKVC is a light-weight injection framework to convert JSON to Model. SuperKVC has its own config DSL which provides a chainable way of describing your injection config concise and readable. SuperKVC supports iOS and macOS.</p> <p>Samples are at the <strong>SuperKVCDemo</strong> project in the SuperKVC workspace. There are three sample to show shallow and deep injection configurations.</p> <h2>What's wrong with KVC?</h2> <p>Apple provide KVC for us to inject dictionary to model, but it cannot solve problems below.</p> <ol> <li> <p>KVC simplely mapping the dictionary key to model key, but many times their name are not the same. For example, id is a keyword in Objective-C while not a keyword in backend, so many network interface response id as a key, in Objective-C model, we may use userId , so id cannot mapping to userId , if we use KVC in this time, the app will crash because there are not a id property in the model.</p> </li> <li> <p>KVC cannot handle data format convert, for example, our UserModel has a NSDate property birthday , while the network interface response a date string , we need to use NSDateFormatter to convert, but KVC cannot config a converter, so it inject a NSString to a NSDate property.</p> </li> <li> <p>KVC cannot handle deep injection, for example, our UserModel has a CardModel property card , while the network interface response a NSDictionary , we need to inject the dictionary to card property, but KVC only inject one level.</p> </li> </ol> <h2>Prepare to meet your Injector!</h2> <p>Think about we have the response and the model structure below.</p> <pre> <code class="language-objectivec">{ "id": 100075, "name": "Greedy", "birthday": "1993-03-06", "isVip": true, "partners": [100236, 100244, 100083] }</code></pre> <pre> <code class="language-objectivec">@interface UserModel : NSObject @property (nonatomic, assign) int64_t userId; @property (nonatomic, copy) NSString *name; @property (nonatomic, strong) NSDate *birthday; @property (nonatomic, assign) BOOL isVip; @property (nonatomic, strong) NSArray *partners; @end</code></pre> <p>Now we need to convert json to UserModel instance, please note that the response key id is called userId in the model, and the birthday property type is not a NSString . By using SuperKVC, you even not need to create the model, tell the injector your config, and it returns what you want.</p> <pre> <code class="language-objectivec">// responseObject is a JSONObject(NSDictionary). UserModel *userModel = [responseObject sk_injectWithInjector:^(SuperKVCInjector *injector) { injector.bind([UserModel class]); injector.mapping(@"id").to(@"userId"); injector.format(@"birthday").with.converter(^NSDate* (NSString *birthdayString) { NSDateFormatter *fmt = [NSDateFormatter new]; fmt.dateFormat = @"yyyy-MM-dd"; return [fmt dateFromString:birthdayString]; }); }];</code></pre> <p>The config descriptions are below.</p> <table> <thead> <tr> <th>Name</th> <th>Function</th> <th>Usage</th> </tr> </thead> <tbody> <tr> <td>bind</td> <td>tell the injector what model should be created and injected.</td> <td>injector.bind(#Class#);</td> </tr> <tr> <td>mapping</td> <td>tell the injector to mapping the response key to another property name.</td> <td>injector.mapping(#responseKey#).to(#propertyName#);</td> </tr> <tr> <td>format</td> <td>format a property with a block filter.</td> <td>injector.format(#propertyName#).with.converter(^id (id oldVar) { /* your format code */ return #newVar#; });</td> </tr> <tr> <td>ignore</td> <td>ignore some property which should not be injected. note that ignores can be joined by and in one line.</td> <td>injector.ignore(#propName1#).and(#propName2#) ...</td> </tr> <tr> <td>synthesize</td> <td>to tell the injector mapping a property to a specific ivar. such as when @synthesize userId = userId_, the injector cannot get ivar by default</td> <td>injector.synthesize(#propertyName#).to(#ivarName#);</td> </tr> </tbody> </table> <h2>Array Injection</h2> <p>Think about we have another json below.</p> <pre> <code class="language-objectivec">[{ "id": 100075, "name": "Greedy", "birthday": "1993-03-06", "isVip": true, "partners": [100236, 100244, 100083] }, { "id": 100724, "name": "Charlie", "birthday": "1996-08-12", "isVip": false, "partners": [100710, 100715] },{},]</code></pre> <p>We needn't to modify any codes to get a UserModel array.</p> <pre> <code class="language-objectivec">// responseObject is a JSONObject(NSArray). NSArray<UserModel *> *userModels = [responseObject sk_injectWithInjector:^(SuperKVCInjector *injector) { injector.bind([UserModel class]); injector.mapping(@"id").to(@"userId"); injector.format(@"birthday").with.converter(^NSDate* (NSString *birthdayString) { NSDateFormatter *fmt = [NSDateFormatter new]; fmt.dateFormat = @"yyyy-MM-dd"; return [fmt dateFromString:birthdayString]; }); }];</code></pre> <h2>Deep Injection</h2> <p>Think about we have a two level json.</p> <pre> <code class="language-objectivec">[{ "id": 100075, "name": "Greedy", "birthday": "1993-03-06", "isVip": false, "cards": [ { "id": 400820666, "name": "King Card of Unity", "expire": "2026-03-27" }, { "id": 622800333, "name": "Silver Card of Glory", "expire": "2029-02-21" }, { "id": 623400765, "name": "King Card of Floyt", "expire": "2024-08-15" } ] },{},]</code></pre> <p>And our class structures are below.</p> <pre> <code class="language-objectivec">@interface UserModel : NSObject @property (nonatomic, assign) int64_t userId; @property (nonatomic, copy) NSString *name; @property (nonatomic, strong) NSDate *birthday; @property (nonatomic, assign) BOOL isVip; @property (nonatomic, strong) NSArray<CardModel *> *cards; @end @interface CardModel : NSObject @property (nonatomic, assign) int64_t cardId; @property (nonatomic, copy) NSString *name; @property (nonatomic, strong) NSDate *expireDate; @end</code></pre> <p>To make a deep injection, we need to use the converter to nested call injector on the cards property. To be efficient, we create the NSDateFormatter out of the block and resue it.</p> <pre> <code class="language-objectivec">// responseObject is a JSONObject(NSArray). NSDateFormatter *fmt = [NSDateFormatter new]; fmt.dateFormat = @"yyyy-MM-dd"; NSArray *userArray = [responseObject sk_injectWithInjector:^(SuperKVCInjector *injector) { injector.bind([UserModel class]); injector.mapping(@"id").to(@"userId"); injector.format(@"birthday").with.converter(^NSDate* (NSString *birthdayString) { return [fmt dateFromString:birthdayString]; }); injector.format(@"cards").with.converter(^CardModel* (NSDictionary *cardDictArray) { return [cardDictArray sk_dequeInjectorForClass:[CardModel class] emptyHandler:^(SuperKVCInjector *injector) { injector.bind([CardModel class]); injector.mapping(@"id").to(@"cardId"); injector.mapping(@"expire").to(@"expireDate"); injector.format(@"expireDate").with.converter(^NSDate* (NSString *birthdayString) { return [fmt dateFromString:birthdayString]; }); }]; }); }];</code></pre> <p>Please note that we use sk_dequeInjectorForClass:: method in the nested call, it can handle injector reuse for class to improve execution efficiency.</p> <h2>Installation</h2> <ol> <li>Clone the repo, and drag the SuperKVC folder to your project.</li> <li>#import "SuperKVC.h"</li> </ol> <h2>Cache Configuration</h2> <p>The SuperKVC framework cached reflection result and injector configuration in memory using a 10 limit LRU(NSCache), you can change the limit or clear cache by the property cacheLimit and the method claerCache in SKVManager singleton.</p> <h2>TODO</h2> <ul> <li>Disk cache.</li> <li>More tests and examples.</li> </ul> <p> </p> <p>项目主页:<a href="http://www.open-open.com/lib/view/home/1492567015645">http://www.open-open.com/lib/view/home/1492567015645</a></p> <p> </p>