您的位置:首页 > 移动开发 > IOS开发

iOS 开发中的消息机制-代理、通知、block

2016-05-03 22:45 295 查看
关于代理

1.代理时一种设计模式。

使用场景:如果对象B要监听对象A里面发什么了什么,使用代理。如果对象A想让对象B去干活,使用代理。如果对象A中的按钮或者cell被点击,要把相应的数据传递给对象B,使用代理。例如,我们自定义view里面的button被点击,需要让控制器知道,或者传递参数给控制器,使用代理。还有,如果A不想做什么,需要让B来替他执行某项操作,使用代理。补充:代理时一对一的。

如何使用?场景A中发生了什么,B来响应这个事件

1.定义代理协议,申明代理方法,关键字是@protocol ,方法是使用@optional&@required修饰。其中@required是必须实现的方法。比如协议名字为--ClassA[b]Delegate[/b]

2.定义代理属性  @property (nonatomic,weak)id<[b]ClassADelegate>delegate;[/b]

你可能注意到代理为什么使用weak,因为一般的代理使用weak/assign。

@property (nonatomic,
weak, nullable)
id <UITableViewDataSource> dataSource;

@property (nonatomic,
weak, nullable)
id <UITableViewDelegate> delegate;

查看UITableView的api发现常用的代理使用的是weak.为什么?

分析:比如控制器时UITableViewController.UITableViewController(引用)--->tableView,tableView.delegate/dataSource(引用)--->UITableViewController.如果设置成strong,那么就是两个强指针,造成循环引用。那么解决办法呢,一边使用weak,我们能改的只能的代理,改成弱指针。

3.指定代理 

在B类中,实例化A,指定A的实例化对象的代理属性为B

具体做法是

ClassA *obj = [[[b]ClassA alloc]init];[/b]

obj.delegate = self;//self 指的是B的实例

---->[b]做完上面的几部会有一个警告,通常是没有遵循协议和实现相应的@required方法[/b]

4.遵循代理协议 类名后面加上:[b]ClassADelegate[/b]

[b]5.在B的implementation文件中实现代理方法。[/b]



代理实现细节-----代理要传值一般要把自己传出去。也就是传递当前类的实例

关于通知

通知--通知时一对多的,一半注册了这个通知的对象都可以收到这个通知。就像你发出一个广播,别人通过频率,调到当前频道上,就可以收听广播一样。

通知的三步:
1.发出通知---

2.添加监听者---

3.执行回调---

我们可以在回调的函数中取到userInfo内容,如下

-(void)notice:(id)notice{

 NSLog(@"%@",notice);

}

4.使用通知记得移除,不然会造成内存溢出

-(void)dealloc{

 [[NSNotificationCenter defaultCenter]removeObserver:self];

}

--提到通知不得不提到KVO 属性监视器--在java中管它叫做观察者模式

KVO 的实现也依赖于 Objective-C 强大的 Runtime 。Apple 的文档有简单提到过 KVO 的实现

Automatic key-value observing is implemented using a technique called isa-swizzling
102a8
... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than
at the true class ...

Apple 的文档真是一笔带过,唯一有用的信息也就是:被观察对象的 
isa
 指针会指向一个中间类,而不是原来真正的类。看来,Apple
并不希望过多暴露 KVO 的实现细节。不过,要是你用 runtime 提供的方法去深入挖掘,所有被掩盖的细节都会原形毕露。Mike Ash 早在 2009 年就做了这么个探究

简单概述下 KVO 的实现:

当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 
setter
 方法。自然,重写的 
setter
 方法会负责在调用原 
setter
 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 
isa
 指针
isa
 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

原来,这个中间类,继承自原本的那个类。不仅如此,Apple 还重写了 
-class
 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下
Mike Ash 的那篇文章里的代码就能明白,这里就不再重复。

当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 
setter
 方法。自然,重写的 
setter
 方法会负责在调用原 
setter
 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 
isa
 指针
isa
 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

原来,这个中间类,继承自原本的那个类。不仅如此,Apple 还重写了 
-class
 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下
Mike Ash 的那篇文章里的代码就能明白,这里就不再重复。

KVO-缺陷

KVO 很强大,没错。知道它内部实现,或许能帮助更好地使用它,或在它出错时更方便调试。但官方实现的 KVO 提供的 API 实在不怎么样。

比如,你只能通过重写 
-observeValueForKeyPath:ofObject:change:context:
 方法来获得通知。想要提供自定义的 
selector
 ,不行;想要传一个 
block
 ,门都没有。而且你还要处理父类的情况
- 父类同样监听同一个对象的同一个属性。但有时候,你不知道父类是不是对这个消息有兴趣。虽然 
context
 这个参数就是干这个的,也可以解决这个问题
- 在 
-addObserver:forKeyPath:options:context:
 传进去一个父类不知道的 
context
。但总觉得框在这个
API 的设计下,代码写的很别扭。至少至少,也应该支持 
block
 吧。

有不少人都觉得官方 KVO 不好使的。Mike Ash 的 Key-Value Observing Done Right,以及获得不少分享讨论的 KVO
Considered Harmful 都把 KVO 拿出来吊打了一番。所以在实际开发中 KVO 使用的情景并不多,更多时候还是用 Delegate 或 NotificationCenter。

在ObjC中使用KVO操作常用的方法如下:

注册指定Key路径的监听器: addObserver: forKeyPath: options:  context:
删除指定Key路径的监听器: removeObserver: forKeyPathremoveObserver: forKeyPath: context: 
回调监听: observeValueForKeyPath: ofObject: change: context:

关于block 

// 下面这个网址很详细 http://www.cocoachina.com/ios/20150109/10891.html block是一段特殊的代码块。使用起来有点像函数,在swift语言中管他叫做闭包 
一般是在A中宏定义一个block属性,比如在B中实例化A,并设置block的属性也就是block被调用时执行的代码块。咋洗需要调用的地方拿到block名传入对应需要的参数,即可完成回调。

补充block属性的申明要用weak,因为block 是存在于栈区的,如果在MRC环境下不会对其进行拷贝,在ARC环境下就会做拷贝操作。
MRC情况下,不做拷贝,就会成为局部变量,生命周期与栈绑定,不能跨方法访问。
另一个需要注意的问题是关于线程安全,在声明Block属性时需要确认“在调用Block时另一个线程有没有可能去修改Block?”这个问题,如果确定不会有这种情况发生的话,那么Block属性声明可以用nonatomic。如果不肯定的话(通常情况是这样的),那么你首先需要声明Block属性为atomic,也就是先保证变量的原子性(Objective-C并没有强制规定指针读写的原子性,C#有)。
比如这样一个Block类型:
typedef void (^MyBlockType)(int);

属性声明:
@property (copy) MyBlockType myBlock;

这里ARC和非ARC声明都是一样的,当然注意在非ARC下要release Block。

但是,有了atomic来保证基本的原子性还是没有达到线程安全的,接着在调用时需要把Block先赋值给本地变量,以防止Block突然改变。因为如果不这样的话,即便是先判断了Block属性不为空,在调用之前,一旦另一个线程把Block属性设空了,程序就会crash,如下代码:
if (self.myBlock)
{
//此时,走到这里,self.myBlock可能被另一个线程改为空,造成crash
//注意:atomic只会确保myBlock的原子性,这种操作本身还是非线程安全的
self.myBlock(123);
}

所以正确的代码是(ARC):
MyBlockType block = self.myBlock;
//block现在是本地不可变的
if (block)
{
block(123);
}

在非ARC下则需要手动retain一下,否则如果属性被置空,本地变量就成了野指针了,如下代码:
//非ARC
MyBlockType block = [self.myBlock retain];
if (block)
{
block(123);
}
[block release];

  

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息