Search code examples
objective-cmvvmreactive-cocoa

how to transform json to pageVo using Reactive Cocoa + MVVM


Rest return json:

{  
    "respCode": {  
    "code": 0,  
    "msg": "success"  
  },  
  "timestamp": 1438004437838,  
  "pageVo": {  
    "pageSize": 5,  
    "pageNo": 1,  
    "recordSize": 2,  
    "hasNextPage": false,  
    "hasPrevPage": false,  
    "pageTotal": 1,    
    "data": [  
      {  
        "id": 1,  
        "status": 1,  
        "createDate": 1437747090000,  
        "modifyDate": 1437748290000,  
        "userId": 1,  
        "like": 0,  
        "relay": 0,  
        "category": 0,  
        "lat": "133.333",  
        "lng": "13.999",  
        "user": {  
          "id": 1,  
          "nickname": "Jack",  
          "avatar": "http://www.xx.xxxx/xxxx/xxx.jpg"
        },  
        "itemsPrices": [
          {
            "id": 1,
            "status": 1,
            "itemsId": 1,
            "price": 188.0,
            "title": "world",
            "x": "123.1223",
            "y": "123.4444"
          },
          {
            "id": 2,
            "status": 1,
            "itemsId": 1,  
            "price": 188.0,
            "title": "world2",
            "x": "123.1223",
            "y": "123.4444"
          }
        ],
        "itemsPictures": [
          {
            "id": 1,
            "status": 1,
            "itemsId": 1,
            "url": "http://xxx.xxxx.xxx",
            "isCover": true
          },
          {
            "id": 2,
            "status": 1,
            "itemsId": 1,
            "url": "http://xxx.xxxx.xxx",
            "isCover": false
          }
        ]
      },
      {
        "id": 2,
        "status": 1,
        "createDate": 1437747128000,
        "modifyDate": 1437748341000,
        "userId": 1,
        "like": 0,
        "relay": 0,
        "category": 0,
        "lat": "133.333",
        "lng": "13.999",
        "user": {
          "id": 2,
          "nickname": "world",
          "avatar": "http://www.xx.xxxx/xxxx/xxx.jpg"
        },
        "itemsPrices": [
          {
            "status": 1
          }
        ],
        "itemsPictures": [
          {
            "status": 1
          }
        ]
      }
    ],
    "firstIndex": null,
    "lastIndex": null
  }
}

rest fetch data:

@implementation ZCAPIManager (Items)  
- (RACSignal *)fetchItems {
    NSString *url = @"http://localhost:8080/zc_mobile/items/list?        pageNo=1&pageSize=5";
    RACSignal *resultSignal = [self requestGetWithRelativePath:url parameters:nil resultClass:nil listKey:nil];

return [[resultSignal subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(RACTuple *jsonAndHeaders) {
    NSDictionary *jsonResult = jsonAndHeaders.first;
    NSHTTPURLResponse *httpResponse = jsonAndHeaders.second;

    ZCItemsViewModel *itemsViewModel = [[ZCItemsViewModel alloc] init];
    ZCRespCode *respCode = [MTLJSONAdapter modelOfClass:[ZCRespCode class] fromJSONDictionary:jsonResult[ZCJSONResponseResponseCodeKey] error:nil];

    itemsViewModel.respCode = respCode;

    if(ZCResponseCodeSuccess == respCode.code && httpResponse.statusCode == 200) {
        NSDictionary *pageVoDictionary = jsonResult[ZCJSONResponsePageVoKey];
        ZCPageVo *pageVo = [MTLJSONAdapter modelOfClass:[ZCPageVo class] fromJSONDictionary:pageVoDictionary error:nil];
        itemsViewModel.pageVo = pageVo;

        itemsViewModel.data = [[[pageVoDictionary[ZCJSONResponseDataKey] rac_sequence] map:^id(NSDictionary *itemsDictionary) {
            ZCItems *items = [MTLJSONAdapter modelOfClass:[ZCItems class] fromJSONDictionary:itemsDictionary error:nil];
            return items;
        }] array];
    }

    return itemsViewModel;
}] ;
}

@end

ViewModel:
@interface ZCBaseViewModel : RVMViewModel

@property (nonatomic, strong) ZCRespCode *respCode;
@property (nonatomic, strong) ZCPageVo *pageVo;

@end

@interface ZCItemsViewModel : ZCBaseViewModel

@property (nonatomic, strong) RACSignal *executeFetch;

@property (nonatomic, strong) NSArray *data;

@end

@interface ZCItemsViewModel : ZCBaseViewModel

@property (nonatomic, strong) RACSignal *executeFetch;

@property (nonatomic, strong) NSArray *data;

@end

Unit test / ViewController like this:

- (void)testRequestWithMethod_new3 {

    hxRunInMainLoop(^(BOOL *done) {

        RACSignal *loadItems = [[[ZCAPIManager alloc] init] fetchItems];
        RAC(self, itemsViewModel) = loadItems;
        [loadItems subscribeNext:^(id x) {
            NSLog(@"x = %@\n",x);
            NSLog(@"itemsViewModel = %@", self.itemsViewModel);
            *done = YES;
        }];

    });
}

This way is right? Do you have other way to solve this problem with Complex json to ViewModel. This way is right? Do you have other way to solve this problem with Complex json to ViewModel.

If JSON conversion logic inside ViewModel. The ViewModel init method look like this?

- (instancetype) init {
    if(self = [super init]) {
        ZCAPIManager *apiManager = [[ZCAPIManager alloc] init];

        self.executeFetch = [[[apiManager fetchItems] subscribeOn:    [RACScheduler mainThreadScheduler]] map:^id(RACTuple *jsonAndHeaders) {
            NSDictionary *jsonResult = jsonAndHeaders.first;
            NSHTTPURLResponse *httpResponse = jsonAndHeaders.second;

            ZCRespCode *respCode = [MTLJSONAdapter modelOfClass:[ZCRespCode class] fromJSONDictionary:jsonResult[ZCJSONResponseResponseCodeKey] error:nil];

            self.respCode = respCode;

            if(ZCResponseCodeSuccess == respCode.code && httpResponse.statusCode == 200) {
                NSDictionary *pageVoDictionary = jsonResult[ZCJSONResponsePageVoKey];
                ZCPageVo *pageVo = [MTLJSONAdapter modelOfClass:[ZCPageVo class] fromJSONDictionary:pageVoDictionary error:nil];
                self.pageVo = pageVo;

                self.data = [[[pageVoDictionary[ZCJSONResponseDataKey] rac_sequence] map:^id(NSDictionary *itemsDictionary) {
                    ZCItems *items = [MTLJSONAdapter modelOfClass:[ZCItems class] fromJSONDictionary:itemsDictionary error:nil];
                    return items;
                }] array];
            }
            return self;

        }];
    }
    return self;
}

Do it right? What is the right thing to do?


Solution

  • Let me be a bit catchy:

    1. Your test code subscribes to this signal two times, why?
    2. There's great framework to test reactive cocoa. Expecta + LLReactiveMatchers
    3. Your test code makes real network request which makes your test slow and network dependent. You should have injected some mock API network manager. Maybe DI with Typhoon?
    4. Your code doesn't populate JSON error
    5. I wouldn't merge to responsibilities in one method: making a request and parsing. These are separate principles.