最近公司开发一款新的电商类的app,所以自己整理了一套购物车的业务逻辑,提供以后的复习和整理(这里讨论的只是对购物车信息进行本地的存储,未考虑后台存储的情况)。
数据存储框架(Core Data)
在这里使用的是苹果提供的Core Data,这里不去讨论如何使用和优缺点问题,个人觉得对于移动端来说,并不需要大型网站的高并发,所以这点性能差别几乎是没有影响的。
对数据读写(MagicalRecord)
这里对数据库的读写使用一个非常不错的轮子:MagicalRecord(当然也可以自己写一套读写数据库的工具类出来,思路基本都是差不多的)。github地址:https://github.com/magicalpanda/MagicalRecord
购物车的功能和实现思路
功能:
-
- 登录/未登录状态都可以对购物车中的商品进行增删改查;
-
- 用户登录后,需要把未登录时选购的商品转移到该用户名下,并清空未登录时的购物车信息;
-
- 更新了数据库中的购物车数据,及时的通知到用户。
思路
- 首先,我们得知道这个购物车是属于谁的,每个登录用户都会有一个唯一的标识符(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 ()
@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结构示意图:
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