更新:解决方案记录在以下答案
我在使用0.20.3中的新@root @parent访问器时遇到RestKit映射问题。我不确定这是一个错误还是对如何正确使用框架的误解。
问题
@root和@parent的新概念似乎对我不起作用。
编辑:删除了一堆关于我认为问题的讨论。我错了所以没有必要消化它。如果上面的问题陈述适用于你......那么这篇SO帖子可能会帮助你顺利进行。
背景
可以下载示例源XML here
XML的基本结构如下:
<locations>
<location lat="38.8561" lon="-94.6654" timezone="UTC" city="Overland Park" region="KS" country="US" zipcode="66223" offset="0" local_offset_hours="-5">
<sfc_ob>
<attribute1></attribute1>
</sfc_ob>
<daily_summaries>
<daily_summary>
<attribute2> </attribute2>
</daily_summary>
</daily_summaries>
<hourly_summaries>
<hourly_summary>
<attribute3></attribute3>
</hourly_summary>
</hourly_summaries>
</location>
</locations>
我的核心数据实体如下:
RESTKIT相关代码
- (GLWeatherManager *)init {
self = [super init];
// setup logging
RKLogConfigureByName("RestKit/Network*", RKLogLevelTrace);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
self.httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://weather.wdtinc.com"]];
[self.httpClient setDefaultHeader:@"Accept" value:RKMIMETypeXML];
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:@"application/xml"];
self.restKitManager = [[RKObjectManager alloc] initWithHTTPClient:self.httpClient];
self.restKitManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
[self.restKitManager.managedObjectStore createManagedObjectContexts];
// Locations
RKEntityMapping *locationMapping = [self buildMapForLocations];
RKEntityMapping *currentConditionsMapping = [self buildMapForCurrentConditions];
RKEntityMapping *dailySummariesMapping = [self buildMapForDailySummaries];
RKEntityMapping *hourlyForecastsMapping = [self buildMapForHourlyForecasts];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"daily_summaries" toKeyPath:@"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"hourly_summaries" toKeyPath:@"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"sfc_ob" toKeyPath:@"currentConditions" withMapping:currentConditionsMapping]];
[dailySummariesMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:@"location" withMapping:locationMapping]];
[hourlyForecastsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:@"location" withMapping:locationMapping]];
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationMapping pathPattern:@"/feeds/demofeeds20131031/mega.php" keyPath:@"locations.location" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
// add mapping description to objectmanager
[self.restKitManager addResponseDescriptor:descriptor];
RKResponseDescriptor *descriptor2 = [RKResponseDescriptor responseDescriptorWithMapping:currentConditionsMapping pathPattern:@"/feeds/demofeeds20131031/mega.php" keyPath:@"locations.location.sfc_ob" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor2];
RKResponseDescriptor *descriptor3 = [RKResponseDescriptor responseDescriptorWithMapping:dailySummariesMapping pathPattern:@"/feeds/demofeeds20131031/mega.php" keyPath:@"locations.location.daily_summaries.daily_summary" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor3];
RKResponseDescriptor *descriptor4 = [RKResponseDescriptor responseDescriptorWithMapping:hourlyForecastsMapping pathPattern:@"/feeds/demofeeds20131031/mega.php" keyPath:@"locations.location.hourly_summaries.hourly_summary"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor4];
// start the location manager to get the current location
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager setDelegate:self];
[self.locationManager startUpdatingLocation];
self.locations = [NSMutableArray arrayWithArray:[Locations findAll]];
[self getMegaFeed];
return self;
}
- (RKEntityMapping *)buildMapForLocations {
RKEntityMapping *locationMapping = [RKEntityMapping mappingForEntityForName:@"Locations" inManagedObjectStore:self.restKitManager.managedObjectStore];
[locationMapping addAttributeMappingsFromDictionary:@{
@"lat" : @"latitude",
@"lon" : @"longitude",
@"city" : @"city",
@"region" : @"region",
@"country" : @"country",
@"zipcode" : @"zipcode",
}];
locationMapping.identificationAttributes = [NSArray arrayWithObject:@"zipcode"];
return locationMapping;
}
- (RKEntityMapping *)buildMapForCurrentConditions {
// Current Conditions
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"CurrentConditions" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
//@"stn" : @"stn",
//@"location" : @"location",
//@"stn_lat" : @"stnLatitude",
//@"stn_lon" : @"stnLongitude",
@"ob_time.text" : @"observationTime",
@"day_of_week.text" : @"dayOfWeek",
@"temp_C.text" : @"temperatureMetric",
@"temp_F.text" : @"temperatureImperial",
@"dewp_C.text" : @"dewPointMetric",
@"dewp_F.text" : @"dewPointImperial",
@"rh_pct.text" : @"relativeHumidity",
@"wnd_dir.text" : @"windDirection",
@"wnd_spd_mph.text" : @"windSpeedImperial",
@"wnd_spd_kph.text" : @"windSpeedMetric",
@"press_in.text" : @"pressureImperial",
@"press_mb.text" : @"pressureMetric",
@"wx.text" : @"conditionSummary",
@"wx_code.text" : @"conditionCode",
@"cld_cover.text" : @"cloudCover",
@"visibility_ft.text" : @"visibilityImperial",
@"visibility_m.text" : @"visibilityMetric",
@"apparent_temp_F.text" : @"feelsLikeTemperatureImperial",
@"apparent_temp_C.text" : @"feelsLikeTemperatureMetric",
@"moon_phase.text" : @"moonPhase",
@"sunrise_utc.text" : @"sunrise",
@"sunset_utc.text" : @"sunset"
}];
[mapping setIdentificationAttributes:[NSArray arrayWithObjects:@"observationTime", nil]];
return mapping;
}
- (RKEntityMapping *)buildMapForDailySummaries {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"DailySummaries" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
@"summary_date.text" : @"date",
@"day_of_week.text" : @"dayOfWeek",
@"max_temp_F.text" : @"tempMaxImperial",
@"max_temp_C.text" : @"tempMaxMetric",
@"min_temp_F.text" : @"tempMinImperial",
@"min_temp_C.text" : @"tempMinMetric",
@"wnd_spd_mph.text" : @"windSpeedImperial",
@"wnd_spd_kph.text" : @"windSpeedMetric",
@"min_wnd_spd_mph.text" : @"windSpeedMinImperial",
@"min_wnd_spd_kph.text" : @"windSpeedMinMetric",
@"max_wnd_spd_mph.text" : @"windSpeedMaxImperial",
@"max_wnd_spd_kph.text" : @"windSpeedMaxMetric",
@"wnd_gust_mph.text" : @"windGustImperial",
@"wnd_gust_kph.text" : @"windGustMetric",
@"wnd_dir.text" : @"windDirection",
@"pop.text" : @"probabilityOfPrecipitation",
@"wx.text" : @"conditionSummary",
@"wx_code.text" : @"conditionCode",
@"text_description.text" : @"textDescription",
@"sunrise_utc.text" : @"sunrise",
@"sunset_utc.text" : @"sunset",
@"@root.locations.location.zipcode" : @"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:@"date", @"locationZipcode", nil];
[mapping addConnectionForRelationship:@"location" connectedBy:@{@"locationZipcode": @"zipcode"}];
return mapping;
}
- (RKEntityMapping *)buildMapForHourlyForecasts {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"HourlyForecasts" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
@"day_of_week_utc.text" : @"dayOfWeek",
@"time_utc.text" : @"forecastTime",
@"temp_C.text" : @"temperatureMetric",
@"temp_F.text" : @"temperatureImperial",
@"dewp_C.text" : @"dewPointMetric",
@"dewp_F.text" : @"dewPointImperial",
@"app_temp_C.text" : @"feelsLikeTemperatureMetric",
@"app_temp_F.text" : @"feelsLikeTemperatureImperial",
@"rh_pct.text" : @"relativeHumidity",
@"wx.text" : @"conditionSummary",
@"wx_code.text" : @"conditionCode",
@"day_night.text" : @"dayNight",
@"pop.text" : @"probabilityOfPrecipitation",
@"sky_cov_pct.text" : @"skyCoverPercent",
@"wnd_dir.text" : @"windDirection",
@"wnd_dir_degs.text" : @"windDirectionDegrees",
@"wnd_spd_mph.text" : @"windSpeedImperial",
@"wnd_spd_kph.text" : @"windSpeedMetric",
@"@root.locations.location.zipcode" : @"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:@"forecastTime", @"locationZipcode", nil];
[mapping addConnectionForRelationship:@"location" connectedBy:@{@"locationZipcode": @"zipcode"}];
return mapping;
}
- (void)getMegaFeed {
for (Locations *location in self.locations) {
NSString *path = [NSString stringWithFormat:@"/feeds/demofeeds20131031/mega.php?ZIP=%@&UNITS=all",location.zipcode];
// fetch data
[self.restKitManager getObjectsAtPath:path
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray *mappedObjects = [mappingResult array];
NSMutableArray *validObjectIDs = [[NSMutableArray alloc] initWithCapacity:[mappedObjects count]];
for (NSManagedObject *object in mappedObjects) {
NSManagedObjectID *moID = [object objectID];
[validObjectIDs addObject:moID];
}
[self notifyObservers:@selector(megaFeedDidFinish:location:) withObject:validObjectIDs withObject:location];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
[REUtility showDefaultAlertWithError:error];
[RELog error:@"%s Hit error:%@", __func__, error];
}];
}
}
答案 0 :(得分:5)
我认为问题是您在尝试使用@root的映射的响应描述符中使用的关键路径。当您在响应描述符上指定键路径时,您实际上正在更改@root对象,因为您正在深入到指定深度的内容,并且该深度成为该映射的根。如果您在映射过程中进行调试并查看提供的元数据,您应该能够看到/验证这一点。
我不清楚为什么你有这么多不同的响应描述符。对于映射定义所有不同部分(及其映射)之间的所有关系的位置,有一个响应描述符似乎更合乎逻辑。以这种方式工作,你会有更简单的代码,你也可以访问@root / @parent。
答案 1 :(得分:1)
同伙堆叠器
这是因为Wain的建议而实现的解决方案。我在这里张贴的不是收集积分,而是将问题和最终解决方案的长内容分开。请将任何赞成奖励给@Wain,我只是想发布一个关于这个问题的简明问题解决方案示例。
我发现RestKit是我见过的最棒的(有几个原因)第三方框架之一。有很多社区参与;但是,文档和示例对于“出界”或不常见的场景和用法来说都很稀疏。我怀疑大多数人使用RestKit和JSON,复杂的现实世界的XML示例很稀疏。我希望这可以帮助那些有同样问题的人。
解
Wain是正确的,因为我正在构建多个响应描述符,目标是我所追求的XML层次结构中的每个点(在上面的问题中可用)。这阻止了RestKit理解曾经存在(root)或(父)访问。
因为我需要将它们映射到Core Data中的多个实体,所以我应该在XML的最高标记级别有一个大的响应描述符。
这意味着如果我映射单个(在我的xml中)高级别位置标记,并且我已经正确配置了RestKit关系,它将为您完成剩下的工作。
实施
我意识到的第一件事是我不关心XML中的高级Locations标记,我不需要将它映射到Core Data Entity,因此我构建了一个RKObjectMapping而不是RKEntityMapping来处理处理XML中的高级标记。
完整的解决方案如下,但我将介绍这些变化。
注意新的RKObjectMapping *locationsMapping = [self buildMapForLocations];
和相应的方法。
然后,我需要告诉新的locationsMapping有关要映射到Core Data的Location实体
[locationsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"location" toKeyPath:@"location" withMapping:locationMapping]];
由于Wain,下一个重大变化是关系的关键路径变化如下:
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"daily_summaries.daily_summary" toKeyPath:@"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"hourly_summaries.hourly_summary" toKeyPath:@"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"sfc_ob" toKeyPath:@"currentConditions" withMapping:currentConditionsMapping]];
这里我们告诉locationMapping(映射到Core Data)我们从源XML映射到我们在Core Data中的位置。由于我们处于Location映射,因此在源XML中,我的dailySummaries(实体)的keyPath是“daily_summaries.daily_summary”。同样,hourlyForecasts的正确keyPath是“hourly_summaries.hourly_summary”,并且从XML“sfc_ob”中的位置映射到currentConditions(实体)。
最后,除了一个简单易用的响应描述符之外,我的所有响应描述符都消失了。
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationsMapping
method:RKRequestMethodGET
pathPattern:@"/feeds/demofeeds20131031/mega.php"
keyPath:@"locations"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor];
在问题陈述(上面)中调用RestKit的成功块中的所有内容都被消除了,因为包括关系在内的所有内容都已正确映射。请注意,映射的所有问题语句代码仍然有效,除非对解决方案的@root @parent方面稍作调整。
在映射每日摘要和每小时预测时,我仍然需要从要填充到核心数据实体的位置获取邮政编码,以便RestKit可以将此作为未来调用的密钥来检查数据库以查看是否有记录使用此密钥已经存在(更新而不是创建)。
我调整了@ mappings来从位置获取邮政编码。请注意层次结构的@ parent。@ parent遍历的所需用法,如下所示:
- (RKEntityMapping *)buildMapForDailySummaries {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"DailySummaries" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
@"summary_date.text" : @"date",
@"day_of_week.text" : @"dayOfWeek",
@"max_temp_F.text" : @"tempMaxImperial",
@"max_temp_C.text" : @"tempMaxMetric",
@"min_temp_F.text" : @"tempMinImperial",
@"min_temp_C.text" : @"tempMinMetric",
@"wnd_spd_mph.text" : @"windSpeedImperial",
@"wnd_spd_kph.text" : @"windSpeedMetric",
@"min_wnd_spd_mph.text" : @"windSpeedMinImperial",
@"min_wnd_spd_kph.text" : @"windSpeedMinMetric",
@"max_wnd_spd_mph.text" : @"windSpeedMaxImperial",
@"max_wnd_spd_kph.text" : @"windSpeedMaxMetric",
@"wnd_gust_mph.text" : @"windGustImperial",
@"wnd_gust_kph.text" : @"windGustMetric",
@"wnd_dir.text" : @"windDirection",
@"pop.text" : @"probabilityOfPrecipitation",
@"wx.text" : @"conditionSummary",
@"wx_code.text" : @"conditionCode",
@"text_description.text" : @"textDescription",
@"sunrise_utc.text" : @"sunrise",
@"sunset_utc.text" : @"sunset",
@"@parent.@parent.zipcode" : @"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:@"date", @"locationZipcode", nil];
[mapping addConnectionForRelationship:@"location" connectedBy:@{@"locationZipcode": @"zipcode"}];
return mapping;
}
- (RKEntityMapping *)buildMapForHourlyForecasts {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"HourlyForecasts" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
@"day_of_week_utc.text" : @"dayOfWeek",
@"time_utc.text" : @"forecastTime",
@"temp_C.text" : @"temperatureMetric",
@"temp_F.text" : @"temperatureImperial",
@"dewp_C.text" : @"dewPointMetric",
@"dewp_F.text" : @"dewPointImperial",
@"app_temp_C.text" : @"feelsLikeTemperatureMetric",
@"app_temp_F.text" : @"feelsLikeTemperatureImperial",
@"rh_pct.text" : @"relativeHumidity",
@"wx.text" : @"conditionSummary",
@"wx_code.text" : @"conditionCode",
@"day_night.text" : @"dayNight",
@"pop.text" : @"probabilityOfPrecipitation",
@"sky_cov_pct.text" : @"skyCoverPercent",
@"wnd_dir.text" : @"windDirection",
@"wnd_dir_degs.text" : @"windDirectionDegrees",
@"wnd_spd_mph.text" : @"windSpeedImperial",
@"wnd_spd_kph.text" : @"windSpeedMetric",
@"@parent.@parent.zipcode" : @"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:@"forecastTime", @"locationZipcode", nil];
[mapping addConnectionForRelationship:@"location" connectedBy:@{@"locationZipcode": @"zipcode"}];
return mapping;
}
- (RKObjectMapping *)buildMapForLocations {
RKObjectMapping *locationsMapping = [[RKObjectMapping alloc] initWithClass:[WDTMegaFeedResponse class]];
[locationsMapping addAttributeMappingsFromDictionary:@{
@"language.text" : @"language",
}];
return locationsMapping;
}
此处对映射的更改以及下面的更改是此用例的完整解决方案。
- (GLWeatherManager *)init {
self = [super init];
self.cloud = [[Kumulos alloc] init];
[self.cloud setDelegate:self];
// Check to see if the states have been loaded
NSArray *states = [States findAllSortedBy:@"code" ascending:YES];
if ([states count] == 0) {
[self.cloud getStates];
}
// setup logging
RKLogConfigureByName("RestKit/Network*", RKLogLevelTrace);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
// initialize the network layer and ensure we are using the same store magical record is using
self.httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://weather.wdtinc.com"]];
[self.httpClient setDefaultHeader:@"Accept" value:RKMIMETypeXML];
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:@"application/xml"];
self.restKitManager = [[RKObjectManager alloc] initWithHTTPClient:self.httpClient];
self.restKitManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
[self.restKitManager.managedObjectStore createManagedObjectContexts];
// Locations
RKObjectMapping *locationsMapping = [self buildMapForLocations];
RKEntityMapping *hourlyForecastsMapping = [self buildMapForHourlyForecasts];
RKEntityMapping *dailySummariesMapping = [self buildMapForDailySummaries];
RKEntityMapping *currentConditionsMapping = [self buildMapForCurrentConditions];
RKEntityMapping *locationMapping = [self buildMapForLocation];
[locationsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"location" toKeyPath:@"location" withMapping:locationMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"daily_summaries.daily_summary" toKeyPath:@"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"hourly_summaries.hourly_summary" toKeyPath:@"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"sfc_ob" toKeyPath:@"currentConditions" withMapping:currentConditionsMapping]];
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationsMapping
method:RKRequestMethodGET
pathPattern:@"/feeds/demofeeds20131031/mega.php"
keyPath:@"locations"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor];
}
有些人可能会发现用于捕获高级XML标记的RKObjectMapping对象的头文件很有用。
#import <Foundation/Foundation.h>
#import "Locations.h"
@interface WDTMegaFeedResponse : NSObject
@property (nonatomic, strong) NSString *language;
@property (nonatomic, strong) Locations *location;
@end