我有一个iOS应用程序,其核心数据模型模仿我的Rails后端数据模型.在我的Rails后端模型中,我使用了多个实体的多态关联.我的Rails模型看起来像这样:
Airport < ActiveRecord::Base
has_many :reviews, :as => :reviewable
Restaurant < ActiveRecord::Base
has_many :reviews, :as => :reviewable
Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => :true
在我的核心数据模型中,我有三个独立的实体,MyAirport,MyRestaurant和MyReview,具有如下所示的相关属性:
MyAirport
@property (nonatomic, retain) NSSet* reviews; //inverse is airport
MyRestaurant
@property (nonatomic, retain) NSSet* reviews; //inverse is restaurant
MyReview
@property (nonatomic, retain) NSNumber* reviewablId;
@property (nonatomic, retain) NSString* reviewableType;
@property (nonatomic, retain) MyAirport* airport; //inverse is reviews
@property (nonatomic, retain) MyRestaurant* restaurant; //inverse is reviews
我的问题与Core Data中的MyReview类有关.根据reviewableId和reviewableType中的值设置正确的核心数据关系(例如机场或餐厅)的最佳方法是什么?
我尝试过以下内容,但出于某种原因,所有人都显得有点不洁净.一般来说,在设置正确的关联之前,我需要确保我有可用的reviewableId和reviewableType,这似乎是与感知清洁度相关的主要问题.在从JSON中保护这些模型对象时,我并没有真正控制在新的MyReview对象上填充reviewableId和reviewableType的顺序.
1)在reviewableId和reviewableType中的一个或两个上的自定义setter – 我最终在每个setter方法中使用逻辑来检查其他属性的值,以确保在我可以在MyReview模型对象上设置机场或餐馆关系之前填充它.一般来说,这是有效的,但它感觉不对,同时在每个setter中也有一些丑陋的重复代码.
- (void)setReviewableId:(NSNumber*)reviewableId {
[self willChangeValueForKey:@"reviewableId"];
[self setPrimitiveReviewableId:reviewableId];
[self didChangeValueForKey:@"reviewableId"];
if (reviewableId && [reviewableId intValue] != 0 && self.reviewableType) {
if (self.airport == nil && [self.reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [self.reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
- (void)setReviewableType:(NSString*)reviewableType {
[self willChangeValueForKey:@"reviewableType"];
[self setPrimitiveReviewableType:reviewableType];
[self didChangeValueForKey:@"reviewableType"];
if (self.reviewableId && [self.reviewableId intValue] != 0 && reviewableType) {
if (self.airport == nil && [reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = self.reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = self.reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
2)关于reviewableId和reviewableType的KVO – 在awakeFromInsert和awakeFromFetch中,我通过KVO将模型对象注册到观察者自己的reviewableId和reviewableType属性.这个对象观察自己感觉真的很难看,但至少我有一个处理填充关联的方法,这似乎比#1有点改进.请注意,我最终在这里使用removeObserver:forKeyPath:在dealloc方法中遇到了一些崩溃(例如崩溃表明我已经删除了一个不存在的观察者?),所以这远远不是经验证的代码.
- (void)awakeFromFetch {
[super awakeFromFetch];
[self addObserver:self forKeyPath:@"reviewableId" options:0 context:nil];
[self addObserver:self forKeyPath:@"reviewableType" options:0 context:nil];
}
- (void)awakeFromInsert {
[super awakeFromInsert];
[self addObserver:self forKeyPath:@"reviewableId" options:0 context:nil];
[self addObserver:self forKeyPath:@"reviewableType" options:0 context:nil];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"reviewableId"];
[self removeObserver:self forKeyPath:@"reviewableType"];
[super dealloc];
}
- (void)updatePolymorphicAssociations {
if (self.reviewableId && [self.reviewableId intValue] != 0 && self.reviewableType) {
if (self.airport == nil && [self.reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = self.reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [self.reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = self.reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
[self updatePolymorphicAssociations];
}
3)使用上面的updatePolymorphicAssociations方法覆盖willSave.这似乎有效,但它推迟了对关联的所有更改,直到我们去保存对象,这通常不是我们对reviewableId和reviewableType进行初始更改时.这里似乎也存在一些限制,涉及通过后台线程插入一堆新的MyReview对象,可能与willSave相关,实际上导致我们刚刚保存的对象的更改,从而再次触发willSave.即使在updatePolymorphicAssociations中进行nil检查,我仍然可以使用这种方法使应用程序崩溃.
- (void)updatePolymorphicAssociations {
if (self.reviewableId && [self.reviewableId intValue] != 0 && self.reviewableType) {
if (self.airport == nil && [self.reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = self.reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [self.reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = self.reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
- (void)willSave {
[self updatePolymorphicAssociations];
}
4)覆盖didSave,而不是willSave.这解决了我们在#3中使用后台进行大量导入的保存问题,但随后我们最终得到了未保存的更改,这很难看.
所以,我似乎在这里有很多选项,但是没有人能够解决在使用由Rails云数据库支持的Core Data的iOS应用程序中看起来常见的问题.我是否过度分析了这个?或者,还有更好的方法?
更新:请注意,目标是根据reviewableType是“Airport”还是“Restaurant”,仅在MyReview上设置其中一个关系.这种关系设置本身就很难看,但这就是我问这个问题的原因. 🙂
最佳答案 您似乎在将Core Data视为关系数据库周围的对象包装器时犯了一个重大错误.事实并非如此. ActiveRecord是关系数据库的对象包装器,这就是它必须使用多态关联等技术的原因.您不能将Core Data填充到关系包装器范例中,并期望获得优雅的设计.
从我可以从你的数据模型中解脱出来,你实际上并不需要审阅和
reviewableType属性实际创建图形本身.相反,您将使用实体继承和关系替换它们.从一个看起来像这样的数据模型开始.
Review{
//... some review attributes
place<<-(required)->ReviewablePlace.reviews //... logically a review must always have a place that is reviewed
}
ReviewablePlace(abstract){
reviewableID:Number //... in case you have to fetch on this ID number later
reviews<-->>Review.place
}
Airport:Reviewable{
//... some airport specific attributes
}
Restaurant:Reviewable{
//... some restaurant specific attributes
}
这取代了与对象关系的多态关联,它通过创建两个单独的子实体来区分两种类型的可审查位置.由于Review实体具有与抽象Reviewable实体定义的关系,因此它还可以与任何Reviewable子实体形成关系.您可以通过Reviewable实体上的reviewableID获取它,它将返回具有该ID的Airport或Restaurant对象,您可以将其弹出到新Review对象的位置关系中.
要处理你的JSON对象(在psuedocode中):
if Airport
fetch on reviewableID
if fetch returns existing Airport managed object
break //don't want a duplicate so don't create an object
else
create new Airport managed object with reviewableID
break
if Restaurant
fetch on reviewableID
if fetch returns existing Restaurant managed object
break //don't want a duplicate so don't create an object
else
create new Restaurant managed object with reviewableID
break
if Review
fetch on reviewableID
if fetch returns Reviewable object of either subclass
create new Review object and assign fetched Reviewable object to the Review.place relationship
break
else
create new Airport or Resturant object using the JSON provided reviewableID and reviewableType info
create new Review object and assign the newly created Reviewable subclass object to the Review.place relationship
break
我认为这将为您提供一个完整的对象图,而无需跳过篮球.在任何情况下,您在父帖中考虑的大部分检查都可以用子实体和适当的关系替换.