美文网首页程序员
本地购物车 + CoreData + MagicalRecord

本地购物车 + CoreData + MagicalRecord

作者: 青苹果园 | 来源:发表于2017-09-05 17:52 被阅读89次

    最近公司开发一款新的电商类的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模型转换成购物车的视图模型
    
    • 关系图


      关系图

    主要类的实现

    ShopModel

    • ShopModel.h
    @interface ShopModel : NSObject 
    
    @property (nonatomic, copy) NSString *dmId;           // 店铺ID
    @property (nonatomic, copy) NSString *businessName;   // 店铺名称  
    
    @end
    
    
    • ShopModel.m

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

    @interface ShopModel () <NSCoding> 
    @end
    
    @implementation ShopModel
    
    #pragma mark - <NSCopying>
    
    - (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<JYGoodsCartViewModel *> *goodsArray;   // 用户在当前商家所选购的所有商品集合
    @property (nonatomic, strong) NSDate *createTime;                            // 添加到购物车的时间
    @property (nonatomic, strong) NSDate *updateTime;                            // 更新时间
    
    @end 
    

    JYShopCart

    注意点:

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

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


      JYShopCart.png
    • JYShopCart.h

    #import <CoreData/CoreData.h>
    
    @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<

    相关文章

      网友评论

        本文标题:本地购物车 + CoreData + MagicalRecord

        本文链接:https://www.haomeiwen.com/subject/mmcpjxtx.html