购物车01-搭建基本骨架
购物车02-圆角按钮处理
购物车03-显示数据
购物车04-加号减号点击处理
购物车05-通知的应用
购物车06-清空和购买
购物车07-KVO的应用
购物车08-代理的简单实现
购物车09-代理设计模式
NSNotification - 通知
NSNotificationCenter - 通知中心
模拟通知
KVO 浅谈
KVO 原理
- 为什么代理协议的方法 要把代理本身作为参数传进来?
如: 系统的UIScrollViewDelegate
代理协议,
就有很多方法是将代理本身(UIScrollView
),做为方法的参数传入。
如:- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
问 : 系统这样设计,有什么好处呢?
假设, 有2个UIScrollView
,设置代理对象为同一个,且遵循了协议。
@interface ViewController ()<UIScrollViewDelegate>
scrollView.delegate = self; scrollTwo.delegate = self;
想知道,2个UIScrollView
滚动时到底滚动的是哪一个UIScrollView
?
-
UIScrollView
的每次滚动,都会调用代理协议的-scrollViewDidScroll:
方法. - 如果在代理协议
UIScrollViewDelegate
的方法声明里,不传入代理本身,是根本无法知道,到底是scrollView
还是scrollTwo
。
-
- 协议里的方法声明,一般要用
@optional
来修饰-
@optional
修饰的方法,可实现也可不实现. - 协议的方法声明,默认是
@required
(必须实现)
一个协议一般有很多方法声明,在遵守协议的代理方,并不需要实现所有方法. - 既然建议选择
@optional
,那么委托方在调用代理协议的方法A时,要判断代理方是否实现方法A。
respondsToSelector:
否则报错 NSInvalidArgumentException
-
-
清空购物车
假设只需要删除一个物品时,却要遍历整个数据源。这样很浪费性能。-- 清除购物车 -- 注意: self.wineData - 是数据源(所有的模型都在里面) --为了更改一个模型,却要遍历所有数据.(这样做很浪费性能) - (IBAction)cleanBuy:(id)sender { self.totalLabel.text = @"0"; for (Wine *wine in self.wineData) { -- 遍历所有数据 wine.count = 0; -- 数据源,里面更改'购买数量' } [self.tableView reloadData]; //刷新列表 ... }
- 解决思路:
- 在购买物品时,先将物品放入购物车内,不想要物品时,则直接从购物车里面取出物品。
- 创建购物车属性。是可变数组类型
NSMutableArray
-
购物车
只需要加载一次,用到时再加载. 所以用懒加载。 - 点击➕ 按钮,同一个物品只是个数不同,加入到购物车数组里面,模型对象应该只有一个,而不是如下图所示3个模型对象。
- 解决方法:
点击➕ 按钮时,判断购物车数组里面是否存在相同对象。
- containsObject:
返回Y / N
- 解决方法:
- 解决思路:
示例代码
@interface ViewController ()
-- "购物车"可变数组
@property (nonatomic, strong) NSMutableArray<Wine *> *shopping;
@end
-- 懒加载 (只加载1次,用时在加载)
-(NSMutableArray<Wine *> *)shopping{
if(_shopping == nil){
_shopping = [NSMutableArray array];
}
return _shopping;
}
-- 点击➕ 按钮时,将选定的模型对象,放入"购物车-可变数组"里面
-- 当没有相同的对象时,才可将指定对象,加入到"购物车可变数组"里面
-(void)plusTotal:(WineCell *) wineCell{
....
if(![self.shopping containsObject:wineCell.wine]){
[self.shopping addObject: wineCell.wine];
}
....
}
-- 清空购物车
-- 从"购物车,可变数组"(self.shopping)里面将模型更改
-- 这样避免,循环整个数据源。从而影响性能。
- (IBAction)cleanBuy:(id)sender {
for (Wine *wine in self.shopping) {
NSLog(@"wine=%@",wine);
wine.count = 0;
}
....
}
- 点击清空按钮 后,再次点击➕, 会把上次的对象,也添加到"可变数组"里面。
如上图所示:
第一次点击清空按钮,可以看出"可变数组里面"有2个对象。
第一次点击清空按钮,"可变数组里面"有6个对象。(上次的对象,没有删除)
解决方法:removeAllObjects
清空 购物车-可变数组。- (IBAction)cleanBuy:(id)sender { self.totalLabel.text = @"0"; -- 从"购物车"里面将模型更改 for (Wine *wine in self.shopping) { NSLog(@"wine=%@",wine); wine.count = 0; } [self.shopping removeAllObjects]; -- 清空 }
- 点击➖ 按钮时,当数量为0 时,需要把当前模型,从购物车-可变数组里移除, 否则会出现如下图的情况:
- 点击 购买时,购物车-可变数组 里面应该只有2个对象.现在确有3个。
- 解决方案:
-(void)minusTotal:(WineCell *) wineCell { .... -- 当购买数量为0时,从"购物车-可变数组"中移除模型 if(wineCell.wine.count <=0){ [self.shopping removeObject:wineCell.wine]; } ... }
- 购买一个商品,却需要将整个列表都遍历一遍。
- "购物车06-清空和购买"遗留的问题
- 解决: 遍历"购物车-可变数组"即可。
- (IBAction)shopClick:(id)sender { -- 遍历"购物车-可变数组",self.shopping for (Wine * wine in self.shopping) { if(wine.count){ -- 购买数量不为 0 NSLog(@"商品: %@",wine.name); NSLog(@"购买了 %d 件",wine.count); } .... } }
- 命名规范:
代理协议, 类名+Delegate;
代理方法, 以类名开头
- 通知和代理的选择
- 代理: 1个对象只能告诉,另外1个对象发生了什么事。
- 通知: 1个对象能告诉N个对象发生了什么事。
1个对象能知道N个对象发生了什么事。 - 通知比较简单,但是容易出错。
如, "通知名"拼写错误,在编译时不会有任何提示,运行时才会报错。 - 代理比较规范,但有些麻烦。
- 代理和通知都能完成,对象和对象之间的通信!
但是能用"代理"就不用"通知"