代理
// 制定协议
@protocol LLBabyDelegate <NSObject>
//这里可以编写代理方法(该案例中用不到所以就不写了)
@end
@interface Baby : NSObject /** * baby的代理属性(这里用的是weak修饰,正确的做法) */
@property(nonatomic, weak) id<LLBabyDelegate>delegate;
@end
如果strong的话,在程序运行的时候会造成循环引用(意思就是reatainCount不为0,只要有实线引用,计数器就+1),对象都不会的销毁,所以会调用婴儿类和保姆类不会调用delloc方法,从而造成了内存泄露的问题,weak和assign都不会增加引用计数,区别是修饰的对象在释放时所做的操作不同,weak是会把对象置为nil,assign则不会,assign一般适用与基本数据类型。
通知
1、不传递参数, 最常用的一种
// 发送通知
-(void)btn1Click{
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti1" object:nil];
}
//监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti1) name:@"noti1" object:nil];
//调用方法
-(void)noti1{
NSLog(@"接收 不带参数的消息");
}
//移除
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"noti1" object:nil];
2、使用object 传递消息//发通知
-(void)btn2Click:(UIButton *)btn{
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti2" object:[NSString stringWithFormat:@"%@",btn.titleLabel.text]];
}
//监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti2:) name:@"noti2" object:nil];
//调用方法
-(void)noti2:(NSNotification *)noti{
//使用object处理消息
NSString *info = [noti object];
NSLog(@"接收 object传递的消息:%@",info);
}
//移除
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"noti1" object:nil];
3、使用userInfo 传递消息//发通知
-(void)btn3Click{
NSDictionary *dic = [NSDictionary dictionaryWithObject:@"userInfo消息" forKey:@"param"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti3" object:nil userInfo:dic];
}
//监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti3:) name:@"noti3" object:nil];
//调用方法
-(void)noti3:(NSNotification *)noti{
//使用userInfo处理消息
NSDictionary *dic = [noti userInfo];
NSString *info = [dic objectForKey:@"param"];
NSLog(@"接收 userInfo传递的消息:%@",info);
}
最后,记得在发送通知消息的页面,在dealloc方法里面移除观察者。
-(void)dealloc{
//移除观察者,Observer不能为nil
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
KVO
最常见的KVO运用是监听scrollView的contentOffset属性,来完成用户滚动时动态改变某些控件的属性实现效果,包括渐变导航栏、下拉刷新控件等效果。
//observer观察者 (观察self.view对象的属性的变化)
//KeyPath: 被观察属性的名称
//options: 观察属性的新值,旧值等的一些配置(枚举值)
//context:上下文 可以为kvo的回调方法传值
//这儿的self.view是被观察者 //注册观察者(可以是多个)
/* options: 有4个值,分别是:
NSKeyValueObservingOptionOld 把更改之前的值提供给处理方法
NSKeyValueObservingOptionNew 把更改之后的值提供给处理方法
NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。 NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。 */
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];
pragma mark - kvo的回调方法(系统提供的回调方法)
//keyPath:属性名称 //object:被观察的对象
//change:变化前后的值都存储在change字典中
//context:注册观察者的时候,context传递过来的值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
id oldName = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"oldName----------%@",oldName);
id newName = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"newName-----------%@",newName);
}
//当界面要消失的时候,移除kvo //
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name"];
self.person = nil;
}
block
捕获外界变量
block还可以访问外界的局部变量,在我的从UIView动画说起中有这么一段代码,其中block内部使用到了外部的局部变量:
CGPoint center = cell.center;
CGPoint startCenter = center;
startCenter.y += LXD_SCREEN_HEIGHT;
cell.center = startCenter;
[UIView animateWithDuration: 0.5 delay: 0.35 * indexPath.item usingSpringWithDamping: 0.6 initialSpringVelocity: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
cell.center = center;
} completion: ^(BOOL finished) {
NSLog("animation %@ finished", finished? @"is", @"isn't");
}];
而block会捕获代码外的局部变量,当然这只局限于只读操作。对于希望在block中修改的外界局部对象,我们可以给这些变量加上__block关键字修饰,这样就能在block中修改这些变量。
循环引用
开头说过,block在iOS开发中被视作是对象,因此其生命周期会一直等到持有者的生命周期结束了才会结束。另一方面,由于block捕获变量的机制,使得持有block的对象也可能被block持有,从而形成循环引用,导致两者都不能被释放:
@implementation LXDObject{
void (^_cycleReferenceBlock)(void);
}
- (void)viewDidLoad{
[super viewDidLoad];
_cycleReferenceBlock = ^{
NSLog(@"%@", self); //引发循环引用
};
}
@end
系统提供给我们__weak的关键字用来修饰对象变量,声明这是一个弱引用的对象,从而解决了循环引用的问题:
__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{
NSLog(@"%@", weakSelf); //弱指针引用,不会造成循环引用
};
\在swift中提供了包括map、filter、reduce等十分简洁优秀的高阶函数供我们对数组数据进行操作,同样情况下,遍历一个数组并求和在使用oc(不使用kvc)和swift的环境下的代码是这样的:
pragma mark - OC code
NSArray numbers = @[@10, @15, @99, @66, @25];
NSInteger totalNumber = 0;
for (NSNumber number in numbers) {
totalNumber += number.integerValue;
}
pragma mark - swift code
let numbers = [10, 15, 99, 66, 25];
let totalNumber = numbers.reduce(0, { $0+$1 })
无论是代码量还是简洁性,此时的oc都比不上swift。那么接下来就要通过神奇的block来为oc添加这些高阶函数的实现。为此我们需要新建一个NSArray的分类扩展,命名为NSArray+LXDExtension
import /// 数组元素转换
typedef id(^LXDItemMap)(id item);
typedef NSArray (^LXDArrayMap)(LXDItemMap itemMap);
/// 数组元素筛选
typedef BOOL(^LXDItemFilter)(id item);
typedef NSArray (^LXDArrayFilter)(LXDItemFilter itemFilter);
/
- 扩展数组高级方法仿swift调用
*/
@interface NSArray (LXDExtension)
@property (nonatomic, copy, readonly) LXDArrayMap map;
@property (nonatomic, copy, readonly) LXDArrayFilter filter;
@end
前面说了为了实现链式编程,函数调用的前提是具有返回对象。因此我使用了typedef声明了几个不同类型的block。虽然本质上LXDArrayMap和LXDArrayFilter两个block是一样的,但是为了区分它们的功能,还是建议这么做。其实现文件如下:
typedef void(^LXDEnumerateHandler)(id item);
@implementation NSArray (LXDTopMethod)
- (LXDArrayMap)map
{
LXDArrayMap map = (LXDArrayMap)^(LXDItemMap itemMap) {
NSMutableArray * items = @[].mutableCopy;
for (id item in self) {
[items addObject: itemMap(item)];
}
return items;
};
return map;
} - (LXDArrayFilter)filter
{
LXDArrayFilter filter = (LXDArrayFilter)^(LXDItemFilter itemFilter) {
NSMutableArray * items = @[].mutableCopy;
for (id item in self) {
if (itemFilter(item)) { [items addObject: item]; }
}
return items;
};
return filter;
} - (void)setFilter:(LXDArrayFilter)filter {}
- (void)setMap:(LXDArrayMap)map {}
@end
我们通过重写setter方法保证block不会被外部修改实现,并且在getter中遍历数组的元素并调用传入的执行代码来实现map和filter等功能。对于这两个功能的实现也很简单,下面举出两个调用高阶函数的例子:
pragma mark - 筛选数组中大于20的数值并转换成字符串
NSArray * numbers = @[@10, @15, @99, @66, @25, @28.1, @7.5, @11.2, @66.2];
NSArray * result = numbers.filter((LXDArrayFilter)^(NSNumber * item) {
return item.doubleValue > 20
}).map((LXDArrayMap)^(NSNumber * item) {
return [NSString stringWithFormat: @"string %g", item.doubleValue];
});
pragma mark - 将数组中的字典转换成对应的数据模型
NSArray * jsons = @[@{ ... }, @{ ... }, @{ ... }];
NSArray * models = jsons.map((LXDArrayMap)^(id item) {
return [[LXDModel alloc] initWithJSON: item];
})
由于语法上的限制,虽然这样的调用跟swift原生的调用对比起来还是复杂了,但通过block让oc实现了函数链式调用的代码看起来也清爽了很多。