`EXC_BAD_ACCESS` 在 Class 实现 KVO

`EXC_BAD_ACCESS` in Class implementing KVO

Objective-c 菜鸟在这里。我在测试中遇到 EXC_BAD_ACCESS 错误,无法弄清楚原因。我为代表用户购物车的应用程序编写了 Cart class。一个Cart可以有多个Order,存储在一个数组中。我正在尝试使用给定的代码为 Orders 数组实现 KVO 合规性(对于长代码片段表示歉意,希望确保一切都存在以解决问题)。 .h 文件:

#import "Model.h"

@class Item;
@class Organization;
@class Order;

@interface Cart : NSObject

@property (copy, readonly, nonatomic) NSArray *orders;

- (instancetype) init __attribute__((unavailable("init not available. Use `sharedCart`.")));
+ (instancetype)sharedCart;

- (void)setItem:(Item *)item withQuantity:(NSNumber *)quantity;
- (void)removeItem:(Item *)item;
- (NSNumber *)itemCount;
- (Order *)orderForSeller:(Organization *)seller;

@end

.m:

@interface Cart()
- (void)addOrder:(Order *)order;
- (void)removeOrder:(Order *)order;
@property (copy, readwrite, nonatomic) NSArray *orders;
@end

NSString * const ordersKey = @"orders";

@implementation Cart
{
    NSMutableArray *_orders;
}

@synthesize orders = _orders;


+ (instancetype)sharedCart {
    static dispatch_once_t onceToken;
    static Cart *cart;

    dispatch_once(&onceToken, ^{
        cart = [[Cart alloc] initPrivate];
    }); 

    return cart;
}

- (void)insertObject:(Order *)order inOrdersAtIndex:(NSUInteger)index {
    [_orders insertObject:order atIndex:index];
}

- (void)removeObjectFromOrdersAtIndex:(NSUInteger)index {
    [_orders removeObjectAtIndex:index];
}

- (void)setOrders:(NSArray *)array {
    if (array != _orders) {
        _orders = [array mutableCopy];
    }   
}

- (instancetype)initPrivate {
    self = [super init];
    if (self) {
        self = [super init];
        _orders = [[NSMutableArray alloc] init];
    }   
    return self;
}
+ (instancetype)object {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Can not create new Cart instance. Used [Cart sharedCart]" userInfo:nil];
}

-(Order *)orderForSeller:(Organization *)seller {
    for (Order *order in self.orders) {
        if ([order.seller is:seller]) {
            return order;
        }
    }
    return nil;
}

- (void)setItem:(Item *)item withQuantity:(NSNumber *)quantity {

    Order *order = [self orderForSeller:item.seller];

    if (!order) {
        order = [Order object];
        order.seller = item.seller;
        order.user = [User currentUser];
        [self addOrder:order];
    }

    [order setItem:item withQuantity:quantity];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {


    // If an order is ever empty, remove it from cart.
    if ([object isKindOfClass:[Order class]] && [keyPath isEqualToString:@"orderItems"]) {
        Order *order = (Order *)object;
        if ([[order itemCount] integerValue] == 0) {
            [self removeOrder:order];
        }
    }

    // When orders are saved, remove them from cart.
    if ([object isKindOfClass:[Order class]] && [keyPath isEqualToString:@"isNew"]) {
        Order *order = (Order *)object;
        BOOL orderHasBeenPlaced = ![order isNew];
        if (orderHasBeenPlaced) {
            [self removeOrder:order];
        }
    }
}
- (void)addOrder:(Order *)order {
    [order addObserver:self forKeyPath:@"orderItems" options:NSKeyValueObservingOptionNew context:nil];
    [order addObserver:self forKeyPath:@"isNew" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    [self insertObject:order inOrdersAtIndex:[_orders count]];
}

-(void)removeOrder:(Order *)order {
    [order removeObserver:self forKeyPath:@"orderItems"];
    [order removeObserver:self forKeyPath:@"isNew"];
    NSUInteger index = [_orders indexOfObject:order];
    [self removeObjectFromOrdersAtIndex:index];
}

-(void)removeItem:(Item *)item {
    Order *order = [self orderForSeller:item.seller];
    if (order) {
        [order removeItem:item];
    }
}

-(NSNumber *)itemCount {
    return [self.orders valueForKeyPath:@"@sum.itemCount"];
}

- (void)dealloc {
    for (Order *order in self.orders) {
        [order removeObserver:self forKeyPath:@"isNew"];
        [order removeObserver:self forKeyPath:@"orderItems"];
    }
}

@end

addOrder: 方法在这一行产生 EXC_BAD_ACCESS 异常:

[self insertObject:order inOrdersAtIndex:[_orders count]];

正在调试,_orders数组是有效的NSMutableArray。任何人都知道发生了什么事?非常感谢。

`

我很确定我在您的其他问题之一中提出了这个建议:问题可能与观察此 [= 实例的 orders 属性 的键值对有关11=] class。可能观察者在它被释放之前没有停止观察。因此,当您修改 orders 属性 时,KVO 会尝试向一个不再存在的对象发送更改通知,该对象会崩溃。

Nimrod 对 运行 你的应用在 Zombies 工具下的建议很好。

顺便说一下,对于这个 Cart class:

  • 你应该总是在调用-addObserver:forKeyPath:options:context:时指定一个唯一的context值并在你的-observeValueForKeyPath:ofObject:change:context:中检查它方法。对于您正在使用的上下文以外的上下文,请调用 super 和 return。框架也允许让你的对象观察东西,如果你不这样做,你将干扰它们的功能。

  • 您应该将 -addObserver:...-removeObserver:... 调用从 -addOrder:-removeOrder: 移动到 属性 变异方法,-insertObject:inOrdersAtIndex:-removeObjectFromOrdersAtIndex:-setOrders:。基本上,您应该将代码移动到在实际数组中添加和删除项目的相同位置,这样您就可以确保始终观察数组中的所有对象,并且停止观察从数组中删除的对象。特别是,您忽略了 -setOrders: 案例,尽管我怀疑您从未使用过该方法。

    在任何情况下,将数组操作与观察操作放在不同的地方会让您可以只做一个而不做另一个。例如,您可能会添加未通过 -addOrder: 的对 -insertObject:inOrdersAtIndex: 的未来调用,并且您将无法开始遵守该顺序。