本地购物车 + CoreData + MagicalRecord

最近公司开发一款新的电商类的app,所以自己整理了一套购物车的业务逻辑,提供以后的复习和整理(这里讨论的只是对购物车信息进行本地的存储,未考虑后台存储的情况)。

数据存储框架(Core Data)

在这里使用的是苹果提供的Core Data,这里不去讨论如何使用和优缺点问题,个人觉得对于移动端来说,并不需要大型网站的高并发,所以这点性能差别几乎是没有影响的。

对数据读写(MagicalRecord)

这里对数据库的读写使用一个非常不错的轮子:MagicalRecord(当然也可以自己写一套读写数据库的工具类出来,思路基本都是差不多的)。github地址:https://github.com/magicalpanda/MagicalRecord

购物车的功能和实现思路

功能:

    1. 登录/未登录状态都可以对购物车中的商品进行增删改查;
    1. 用户登录后,需要把未登录时选购的商品转移到该用户名下,并清空未登录时的购物车信息;
    1. 更新了数据库中的购物车数据,及时的通知到用户。

思路

  • 首先,我们得知道这个购物车是属于谁的,每个登录用户都会有一个唯一的标识符(ID),所以我们就把它作为购物车的标识符(ID),未登录的情况我们可以定义一个固定的标识符。
  • 购物车中的信息是由一家家商铺和商铺中被选中的商品构成的集合,所以我们可以于商铺作为购物车中的一个个单元,商铺的ID就是单元的ID。
  • 我们对购物车的转移就是在用户不同状态之间的处理,对购物车的更新实际就是对商铺信息的更新,这样的我们的业务逻辑就清晰了。

类的设计

涉及到的类

请不要在意项目中的类命名,我们看思路就行了 >v<

  • 模型和类的介绍
// 信息存储模型

ShopModel : 商铺信息模型类
NewGoodsModel : 商品信息模型类 

// 购物车视图模型

JYGoodsCartViewModel : 用户所选商品信息(包括商品信息和购买的数量等)
JYShopCartViewModel : 用户在此商铺中选购的信息(包括商铺信息、用户所选的商品信息),即购物车的组成单元。
 
// CoreData中的存储模型
 
JYGoodsCart : 商品信息在CoreData中的存储模型
JYShopCart : 数据库中存储的单元,即购物车在Core Data对应的模型类

// 工具类

JYShopCartHelper : 对数据库进行增删改查的工具类,并兼具把CoreData模型转换成购物车的视图模型
  • 关系图


    本地购物车 + CoreData + MagicalRecord_第1张图片
    关系图

主要类的实现

ShopModel

  • ShopModel.h
@interface ShopModel : NSObject 

@property (nonatomic, copy) NSString *dmId;           // 店铺ID
@property (nonatomic, copy) NSString *businessName;   // 店铺名称  

@end

  • ShopModel.m

因为需要对模型进行复制和归档解档操作,所以需要实现以下方法:

@interface ShopModel ()  
@end

@implementation ShopModel

#pragma mark - 

- (id)copyWithZone:(nullable NSZone *)zone {
    ShopModel *model   = [[[self class] alloc] init];
    model.dmId         = [self.dmId copy];
    model.businessName = [self.businessName copy];  
    return model;
}

#pragma mark - encode

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.dmId forKey:@"dmId"];                         
    [aCoder encodeObject:self.businessName forKey:@"businessName"];         
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        self.dmId         = [aDecoder decodeObjectForKey:@"dmId"];
        self.businessName = [aDecoder decodeObjectForKey:@"businessName"]; 
    }
    return self;
} 

@end

NewGoodsModel

(这里展示的只是一部分属性)

  • NewGoodsModel.h
@interface NewGoodsModel : NSObject 
         
@property (nonatomic,   copy) NSString *goodsId;        // 商品ID
@property (nonatomic,   copy) NSString *businessId;     // 商铺ID
@property (nonatomic,   copy) NSString *goodsName;      // 商品名称
@property (nonatomic, assign) double price;             // 价格

@end
  • NewGoodsModel.m
#pragma mark - encode

- (void)encodeWithCoder:(NSCoder *)aCoder { 
    [aCoder encodeObject:self.goodsId forKey:@"goodsId"];               // 商品ID
    [aCoder encodeObject:self.businessId forKey:@"businessId"];         // 商铺ID
    [aCoder encodeObject:self.goodsName forKey:@"goodsName"];           // 商品名称 
    [aCoder encodeObject:@(self.price) forKey:@"price"];                // 价格 
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) { 
        self.goodsId    = [aDecoder decodeObjectForKey:@"goodsId"];
        self.businessId = [aDecoder decodeObjectForKey:@"businessId"];
        self.goodsName  = [aDecoder decodeObjectForKey:@"goodsName"]; 
        self.price      = [[aDecoder decodeObjectForKey:@"price"] doubleValue]; 
    }
    return self;
}

@end

JYShopCartViewModel & JYGoodsCartViewModel

  • JYShopCartViewModel.h
@class ShopModel, NewGoodsModel;

@interface JYGoodsCartViewModel : NSObject

@property (nonatomic, strong) NewGoodsModel *goodsModel;    // 用户选购的商品
@property (nonatomic, assign) NSUInteger quantity;          // 用户选中商品的数量

@end

 
/////////////////// 华丽的分割线 /////////////////////////////

@interface JYShopCartViewModel : NSObject

@property (nonatomic, strong) ShopModel *shopModel;                          // 商家信息
@property (nonatomic, strong) NSArray *goodsArray;   // 用户在当前商家所选购的所有商品集合
@property (nonatomic, strong) NSDate *createTime;                            // 添加到购物车的时间
@property (nonatomic, strong) NSDate *updateTime;                            // 更新时间

@end 

JYShopCart

注意点:

用@dynamic修饰属性,是告诉编译器,其getter和setter方法会在程序运行的时候或者用其他方式动态绑定,以便让编译器通过编译,Core Data框架会在程序运行的时候为此类属性生成getter和Setter方法。

  • JYShopCart对应的CoreData结构示意图:


    本地购物车 + CoreData + MagicalRecord_第2张图片
    JYShopCart.png
  • JYShopCart.h

#import 

@interface JYShopCart : NSManagedObject

@property (nonatomic, copy) NSString *cartID;           // 唯一标识,其实商铺的标识
@property (nonatomic, copy) NSString *userID;           // 用户标识
@property (nonatomic, strong) id shopModel;             // 商铺信息
@property (nonatomic, strong) NSData *goodsArrayData;   // 所选商品信息集合
@property (nonatomic, strong) NSDate *createTime;       // 添加到购物车的时间
@property (nonatomic, strong) NSDate *updateTime;       // 更新时间

/**
 *  给当前的购物车更新信息
 *  
 *  shopCart: 新购物车信息
 */
- (JYShopCart *)copyInfoByShopCart:(JYShopCart *)shopCart;

@end
  • JYShopCart.m
@implementation JYShopCart

/**
 用@dynamic修饰属性,是告诉编译器,其getter和setter方法会在程序运行的时候或者用其他方式动态绑定,以便让编译器通过编译,Core Data框架会在程序运行的时候为此类属性生成getter和Setter方法。
 */
@dynamic cartID;
@dynamic userID;
@dynamic shopModel;
@dynamic goodsArrayData;
@dynamic createTime;
@dynamic updateTime;
 
#pragma mark - actions

- (JYShopCart *)copyInfoByShopCart:(JYShopCart *)shopCart {
    self.cartID         = [shopCart.cartID copy];
    self.userID         = [shopCart.userID copy];
    self.shopModel      = [shopCart.shopModel copy];
    self.goodsArrayData = [shopCart.goodsArrayData copy];
    self.createTime     = [shopCart.createTime copy];
    self.updateTime     = [shopCart.updateTime copy];
    return self;
}

@end

JYGoodsCart

  • JYGoodsCart.h
@interface JYGoodsCart : NSObject

@property (nonatomic, copy) NSString *goodsID;      // 商品标识
@property (nonatomic, assign) NSUInteger quantity;  // 用户选购的商品数量
@property (nonatomic, strong) NSData *goodsData;    // 商品信息

@end
  • JYGoodsCart.m
@implementation JYGoodsCart

#pragma mark - encode

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.goodsID forKey:@"goodsID"];
    [aCoder encodeObject:@(self.quantity) forKey:@"quantity"];
    [aCoder encodeObject:self.goodsData forKey:@"goodsData"];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        self.goodsID   = [aDecoder decodeObjectForKey:@"goodsID"];
        self.quantity  = [[aDecoder decodeObjectForKey:@"quantity"] integerValue];
        self.goodsData = [aDecoder decodeObjectForKey:@"goodsData"];
    }
    return self;
}

@end

JYShopCartViewModel工具类

负责对数据库的增删改查;

数据模型之间的转换;

更新完数据库后及时的通知用户。

  • JYGoodsCart.h
@class JYShopCartViewModel;

UIKIT_EXTERN NSString * const kObserverShopCartDidChange;

@interface JYShopCartHelper : NSObject

+ (instancetype)shareShopCartHelper;

// 查询
- (NSMutableArray *)loadShopCartInLocal;
- (JYShopCartViewModel *)checkShopCartInLocalWithShopID:(NSString *)shopID;

// 添加
- (void)addGoodsToLocalWithShopCartViewModel:(JYShopCartViewModel *)shopCartViewModel;
- (void)transformShopCartUnLoginToLoginInLocal;

// 更新
- (void)updateGoodsInLocalWithShopCartViewModel:(JYShopCartViewModel *)shopCartViewModel;

// 删除
- (void)deleteGoodsInLocalWithGoodsArray:(NSArray *)goodsArray shopID:(NSString *)shopID;
- (void)deleteGoodsInLocalWithShopCartViewModel:(JYShopCartViewModel *)shopCartViewModel;
- (void)deleteAllGoodsInLocal;

  • JYGoodsCart.m
NSString * const kObserverShopCartDidChange = @"ObserverShopCartDidChange";

static NSString *kUnLoginUserID = @"UnLoginUserID";         // 未登录的情况下的用户标识,只用于购物车中
static NSString *kShowInfo      = @"商家信息不全,添加失败。";

@implementation JYShopCartHelper

+ (instancetype)shareShopCartHelper {
    static id shareShopCartHelper = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareShopCartHelper = [[[self class] alloc] init];
    });
    return shareShopCartHelper;
}

#pragma mark - 查新购物车中的所有商品

// 根据用户ID查询数据库信息,返回改用户的购物车信息
- (NSMutableArray *)loadShopCartInLocal {
    NSArray *shopCarts = [self checkShopCartsByUserIDInLocalWithUserID:[self userIDByLogin]];
    NSArray *shopCartViewModels = [self transformShopCartsToShopCartViewModels:shopCarts];
    return [self sortShopsByCreateTimeOfShopCartWithShopCarts:shopCartViewModels];
}

// 根据店铺ID和用户ID,查询改店铺在购物车中的选购信息
- (JYShopCartViewModel *)checkShopCartInLocalWithShopID:(NSString *)shopID {
    JYShopCart *shopCart = [self checkShopCartInLocalWithShopID:shopID userID:[self userIDByLogin]];
    return [self transformShopCartToShopCartViewModel:shopCart];
}

// 用户可以选择按时间的排序类型
- (NSMutableArray *)sortShopsByCreateTimeOfShopCartWithShopCarts:(NSArray *)shopCarts {
    NSArray *sorts = [shopCarts sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        JYShopCartViewModel *model1 = obj1;
        JYShopCartViewModel *model2 = obj2;
        // 根据更新时间排序,倒叙
        if (model1.updateTime == [model1.updateTime earlierDate:model2.updateTime]) {
            return NSOrderedDescending; // 降序
        } else if (model1.updateTime == [model1.updateTime laterDate:model2.updateTime]) {
            return NSOrderedAscending;  // 升序
        } else {
            return NSOrderedSame;       // 相等
        }
    }];
    return [sorts mutableCopy];
}

// 根据用户ID,查询数据库信息
- (NSArray *)checkShopCartsByUserIDInLocalWithUserID:(NSString *)userID {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.userID == %@", userID];
    return [JYShopCart MR_findAllWithPredicate:predicate];
}

// 根据商铺ID和用户ID,查询数据库信息
- (JYShopCart *)checkShopCartInLocalWithShopID:(NSString *)shopID userID:(NSString *)userID {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.cartID == %@ && SELF.userID == %@", shopID, userID];
    return [JYShopCart MR_findFirstWithPredicate:predicate];
}


#pragma mark - 添加商品到购物车

- (void)addGoodsToLocalWithShopCartViewModel:(JYShopCartViewModel *)shopCartViewModel {
    if (!shopCartViewModel.shopModel || shopCartViewModel.shopModel.dmId.length <= 0) {
        [SVProgressHUD showInfoWithStatus:kShowInfo];
        return;
    }
    
    JYShopCart *shopCart = [self checkShopCartInLocalWithShopID:shopCartViewModel.shopModel.dmId userID:[self userIDByLogin]];
    if (!shopCart) {
        shopCart = [JYShopCart MR_createEntity];
    } else {}
    
    shopCart.userID         = [self userIDByLogin];
    shopCart.cartID         = shopCartViewModel.shopModel.dmId;
    shopCart.shopModel      = shopCartViewModel.shopModel;
    shopCart.goodsArrayData = [self archivedDataWithGoodsArray:shopCartViewModel.goodsArray];
    shopCart.updateTime     = [NSDate date];
    shopCart.createTime     = [NSDate date];
    
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
    [self postNotificationAfterShopCartIsChange];
}

// 未登录选购的商品 ===> 用户登录后的购物车中
- (void)transformShopCartUnLoginToLoginInLocal {
    NSArray *shopCarts = [self checkShopCartsByUserIDInLocalWithUserID:kUnLoginUserID];
    for (JYShopCart *cart in shopCarts) {
        cart.userID = [self userIDByLogin];
    }
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
    [self deleteAllShopCartInLocalWithUserID:kUnLoginUserID];
    [self postNotificationAfterShopCartIsChange];
}

#pragma mark - 更新购物车中的商品

- (void)updateGoodsInLocalWithShopCartViewModel:(JYShopCartViewModel *)shopCartViewModel {
    if (!shopCartViewModel.shopModel || shopCartViewModel.shopModel.dmId.length <= 0) {
        [SVProgressHUD showInfoWithStatus:kShowInfo];
        return;
    }
    
    [self addGoodsToLocalWithShopCartViewModel:shopCartViewModel];
    [self postNotificationAfterShopCartIsChange];
}

#pragma makrk - 删除购物车中的商品

// 从购物车中,删除店铺中的商品集合
- (void)deleteGoodsInLocalWithGoodsArray:(NSArray *)goodsArray shopID:(NSString *)shopID {
    if (shopID.length <= 0 || goodsArray.count <= 0) {
        return;
    }
    
    JYShopCartViewModel *shopCartViewModel = [self checkShopCartInLocalWithShopID:shopID];
    NSMutableArray *newGoodsArray = [shopCartViewModel.goodsArray mutableCopy];
    for (JYGoodsCartViewModel *deleteGoods in goodsArray) {
        for (JYGoodsCartViewModel *goods in shopCartViewModel.goodsArray) {
            if ([deleteGoods.goodsModel.goodsId isEqualToString:goods.goodsModel.goodsId]) {
                [newGoodsArray removeObject:goods];
            }
        }
    }
    
    if (newGoodsArray.count <= 0) {
        [self deleteShopCartInLocalWithShopID:shopID UserID:[self userIDByLogin]];
    } else {
        shopCartViewModel.goodsArray = [newGoodsArray copy];
        [self updateGoodsInLocalWithShopCartViewModel:shopCartViewModel];
    }
}

// 删除购物车中,此商铺的信息
- (void)deleteGoodsInLocalWithShopCartViewModel:(JYShopCartViewModel *)shopCartViewModel {
    if (!shopCartViewModel.shopModel || shopCartViewModel.shopModel.dmId.length <= 0) {
        [SVProgressHUD showInfoWithStatus:kShowInfo];
        return;
    }
    
    [self deleteShopCartInLocalWithShopID:shopCartViewModel.shopModel.dmId UserID:[self userIDByLogin]];
    [self postNotificationAfterShopCartIsChange];
}

// 清空购物车
- (void)deleteAllGoodsInLocal {
    [self deleteAllShopCartInLocalWithUserID:[self userIDByLogin]];
    [self postNotificationAfterShopCartIsChange];
}

- (void)deleteAllShopCartInLocalWithUserID:(NSString *)userID {
    NSArray *localShopCarts = [self checkShopCartsByUserIDInLocalWithUserID:userID];
    for (JYShopCart *cart in localShopCarts) {
        [cart MR_deleteEntity];
    }
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
}

- (void)deleteShopCartInLocalWithShopID:(NSString *)shopID UserID:(NSString *)userID {
    JYShopCart *shopCart = [self checkShopCartInLocalWithShopID:shopID userID:userID];
    if (shopCart) {
        [shopCart MR_deleteEntity];
        [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
    }
}

#pragma mark - 转换

// JYShopCart ==> JYShopCartViewModel
- (JYShopCartViewModel *)transformShopCartToShopCartViewModel:(JYShopCart *)shopCart {
    if (!shopCart) {
        return nil;
    }
    JYShopCartViewModel *shopCartViewModel = [[JYShopCartViewModel alloc] init];
    shopCartViewModel.shopModel  = shopCart.shopModel;
    shopCartViewModel.goodsArray = [self unArchivedGoodsArrayWithData:shopCart.goodsArrayData];
    shopCartViewModel.createTime = shopCart.createTime;
    shopCartViewModel.updateTime = shopCart.updateTime;
    return shopCartViewModel;
}

// shopCart集合 ==> shopCartViewModel集合
- (NSArray *)transformShopCartsToShopCartViewModels:(NSArray *)shopCarts {
    if (shopCarts.count <= 0) {
        return nil;
    }
    
    NSMutableArray *shopCartViewModels = [NSMutableArray array];
    for (JYShopCart *shopCart in shopCarts) {
        JYShopCartViewModel *model = [self transformShopCartToShopCartViewModel:shopCart];
        [shopCartViewModels addObject:model];
    }
    return shopCartViewModels;
}

#pragma mark - 获取用户标识

- (NSString *)userIDByLogin {
    if ([OPTRuntime sharedInstance].currentNewUser.logined && [OPTRuntime sharedInstance].currentNewUser.uid.length > 0) {
        return [OPTRuntime sharedInstance].currentNewUser.uid;
    }
    return kUnLoginUserID;
}

#pragma mark - 商品的归档和解档

- (NSData *)archivedDataWithGoodsArray:(NSArray *)goodsArray {
    if (goodsArray.count > 0) {
        NSMutableArray *carts = [NSMutableArray array];
        for (JYGoodsCartViewModel *model  in goodsArray) {
            JYGoodsCart *goodsCart = [[JYGoodsCart alloc] init];
            goodsCart.goodsID   = model.goodsModel.goodsId;
            goodsCart.goodsData = [self archivedDataWithGoodsModel:model.goodsModel];
            goodsCart.quantity  = model.quantity;
            [carts addObject:goodsCart];
        }
        return [NSKeyedArchiver archivedDataWithRootObject:carts];
    }
    return nil;
}

- (NSArray *)unArchivedGoodsArrayWithData:(NSData *)goodsArrayData {
    if (goodsArrayData) {
        NSArray *carts = [NSKeyedUnarchiver unarchiveObjectWithData:goodsArrayData];
        NSMutableArray *goodsViewModels = [NSMutableArray array];
        for (JYGoodsCart *cart in carts) {
            JYGoodsCartViewModel *model = [[JYGoodsCartViewModel alloc] init];
            model.goodsModel = [self unArchivedWithData:cart.goodsData];
            model.quantity   = cart.quantity;
            [goodsViewModels addObject:model];
        }
        return goodsViewModels;
    }
    return nil;
}

- (NSData *)archivedDataWithGoodsModel:(NewGoodsModel *)goodsModel {
    if (goodsModel) {
        return [NSKeyedArchiver archivedDataWithRootObject:goodsModel];
    }
    return nil;
}

- (NewGoodsModel *)unArchivedWithData:(NSData *)goodsData {
    if (goodsData) {
        return [NSKeyedUnarchiver unarchiveObjectWithData:goodsData];
    }
    return nil;
}

#pragma mark - 发送更新购物车的通知

- (void)postNotificationAfterShopCartIsChange {
    [[NSNotificationCenter defaultCenter] postNotificationName:kObserverShopCartDidChange object:nil];
}

@end

结尾:

以上是个人对购物车的简单理解,局限于水平的问题,可能还存在一些问题,如有疑问或建议,欢迎积极留言探讨。>v<

  • 个人github: https://github.com/SilongLi

你可能感兴趣的:(本地购物车 + CoreData + MagicalRecord)