总结
IOS中关于信息传递过程中经常会使用block,委托代理,NSNotification,KVO进行调用,但是查找资料后有些资料不是很全。
四者总体比较然后进行一一详解进行讲解:
block:一对一进行通信,比起其他三者更加简洁,但是事件比较多时可以使用delegate。
委托代理:和block一样是一对一,我们使用时要先进行协议方法然后实现协议代理,如果需要通信就需要实现代理。
NSNotification:在进行注册监听时,可以进行一对多的情况,一个进行注册可以多种情况下进行监听。
KVO:就是我们所说的键值监听模式,其主要是在KVC基础上完后才能。
以前读过一个砍柴的故事,如果我们想要追求搞得效率就要对所用到的工具了解,下面我们就开始磨我们搜中的刀。
block的来龙去脉:
block是IOS SDK 4.0中引入的,block在IOS中实际就是一个代码块,有点像C++中内联函数inline有点相似,最让人惊奇的是我们还可以向其传递参数。闲来没事想要知道block在C++中的具体实现就是用Clang编辑^{printf"Hello, World!"}(),此时我们在block仅仅是输出一个语句。
//hello.c是我们把想要编辑的内容放置的文件名称
block在没有传入参数情况
$ clang -rewrite-objc hello.c
在编译后再文件后会生成 hello.cpp文件
struct __mian_block_impl_0{
struct __block_impl impl;
struct __mian_block_desc_0* Desc;__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0)
{ impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags; impl.FuncPtr = fp; Desc = desc;
}
};
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself)
{
printf("Hello, World!\n");}
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;}
__mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)
};int mian(){
((void (*)())&__mian_block_impl_0((void *)
__mian_block_func_0, &__mian_block_desc_0_DATA)) ();
return 0;
}
static struct IMAGE_INFO
{ unsigned version; unsigned flag; }_OBJC_IMAGE_INFO = { 0, 2 };
上面的代码既是我们通过编译截取的hello.cpp的具体代码,根据上面代码我们可以看到block实际是struct结构。看出_mian_block_impl_0 其中传入其中也是block_impl&mian_block_desc_0两个block,下面是_mian_block_impl_0的构造函数。
block在有传入参数情况
clang -rewrite-objc hello1.c
同样在我们编译过后会生成hello.cpp
struct __mian_block_impl_0 {struct __block_impl impl;
struct __mian_block_desc_0* Desc;
float perperson;
__mian_block_impl_0(void *fp, struct __mian_block_desc_0*desc, float _perperson, int flags=0) : perperson(_perperson)
{impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc;
}
};static float __mian_block_func_0(struct __mian_block_impl_0 *__cself,
int totalNum) {
float perperson = __cself->perperson; // bound by copy
return perperson * totalNum; }
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;
}__mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};
int mian(){
float perperson = 34.5; float (*sunClassFee)(int) = ((float (*)(int))
&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA,
perperson));
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; }
_OBJC_IMAGE_INFO = { 0, 2 };
可以看出在block中使用外部变量perperson时,block初始化时这样的:
是在__mian_block_impl_0进行初始化perperson,在block构造时作为block的其中
一个成员初始。在访问时通过__cself->perperson指针进行访问。
block在有传入参数情况
clang -rewrite-objc hello2.c
struct __Block_byref_perperson_0 {void *__isa;
__Block_byref_perperson_0 *__forwarding;
int __flags;int __size;
float perperson;};
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;__Block_byref_perperson_0 *perperson; // by ref
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc,__Block_byref_perperson_0 *_perperson, int flags=0) :
perperson(_perperson->__forwarding) {
impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc;
}
};static float __mian_block_func_0(struct __mian_block_impl_0 *__cself,
int totalNum) {
__Block_byref_perperson_0 *perperson = __cself->perperson; // bound by ref (perperson->__forwarding->perperson) = (perperson->__forwarding->
perperson) + 6;
return (perperson->__forwarding->perperson) * totalNum; }
static void __mian_block_copy_0(struct __mian_block_impl_0*dst,
struct __mian_block_impl_0*src)
{_Block_object_assign((void*)&dst->perperson,
(void)src->perperson, 8/BLOCK_FIELD_IS_BYREF*/);}
static void __mian_block_dispose_0(struct __mian_block_impl_0*src) {
_Block_object_dispose((void)src->perperson, 8/BLOCK_FIELD_IS_BYREF*/);
}
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;void (copy)(struct __mian_block_impl_0, struct __mian_block_impl_0*);
void (dispose)(struct __mian_block_impl_0);
} __mian_block_desc_0_DATA = {0, sizeof(struct __mian_block_impl_0), __mian_block_copy_0,
__mian_block_dispose_0};
int mian(){__attribute__((__blocks__(byref))) __Block_byref_perperson_0 perperson = {
(void*)0,(__Block_byref_perperson_0 *)&perperson, 0,
sizeof(__Block_byref_perperson_0), 34.5
};float (*sunClassFee)(int) = ((float (*)(int))&__mian_block_impl_0((void *)
__mian_block_func_0, &__mian_block_desc_0_DATA,
(__Block_byref_perperson_0 *)&perperson, 570425344));
return 0;}
static struct IMAGE_INFO { unsigned version; unsigned flag; }
_OBJC_IMAGE_INFO = { 0, 2 };
我们在对于block外的perperson计算重新复制,和上面在block仅仅在block中使用外面的参数。两者经过对比可以看出:在block中对于外部值进行复制,会出现下面的情况。使用clang对于block进行编码,可以看书对于perperson进行_block的修饰后编码生成Block_byref_perperson_0的struct结构体,其中__Block_byref_perperson_0的指针指向在block中perperson的地址。
struct __Block_byref_perperson_0 {
void *__isa;
__Block_byref_perperson_0 *__forwarding; int __flags;
int __size; float perperson;
};
block的使用方法~传入参数:
int (^isInputEven)(int) = ^(int n){if (n>1) { return n*isInputEven(n-1); }else{ return 1; }
};
上面我们定义一个block代码段,代码的实现功能是利用递归计算n的基乘。代码可以作为整个代码块进行调用,而且在调用过程中我们只需isInputEven()函数名和传入参数即可实现。
block的使用方法~使用外部参数参数:
float perperson = 34.5;float (^sunClassFee)(int) = ^(int totalNum){ return perperson * totalNum;
};
使用外部参数perperson,让我们感觉block可以想一个类一样使用socpe里面的变量目前无法做到。
主要原因是因为:在block的编译过程中我们看到,在block中进行访问其中元素时需要是
block默认构造函数对于block其中的元素访问。
block的使用方法~使用外部参数参数:
__block float perperson = 34.5;float (^sunClassFee)(int) = ^(int totalNum){ perperson = perperson + 6; return perperson * totalNum;
};
block的使用方法~在(UIView)的动画中使用:
[UIView animateWithDuration:3.0f animations:^{view.alpha = 0;
}];
IOS的开发到今天差不多已经有7年之久,block在开发中使用广度越来越广泛:
- 枚举--来过去对象。例如:NSDictionary的枚举过程
- UIView--对于UIView的动画设置,后面有机会会对动画专门讲述...
- 通知--在本文讲述的就是在调用过程中与其他通知不同点所在
- 完成处理--在相应程序结束后,需要对于程序结果的处理。例如:AFNetWork在访问网络后,对于成功和失败进行处理
- GCD--在GCD的过程中都是含有block使用的方法
- 排序--平时我们使用的一些简单算法均可以block进行相关处理
委托代理详解
在发C++过程中经常见到多组继承,但是在IOS开发过程中只能进行单继承,很多方法我们需要实现接口的形式。正如我们在使用UITableView时,经常会用到UITableViewDataSource和UITableViewDelegate。
如果要使用使用委托代理,需要知道协议和委托两者关系。
协议
协议一般分为两种:
@ required: //是我们在继承过程中必须实现的
@optional : //在实现接口中需要进行选择性实现的方式
委托
常见的一种设计模式,身为老板一般负责管理员工、打电话、发薪水,这样由于公司业务发展就请一个秘书负责:打电话和发薪水。
首先进行协议提取(需要员工所得事情):(boss.h文件中声明)
@protocol protocol
-(void)payoff;
-(void)tel;
@end
@interface boss : NSObject
@property(nonatomic,strong) iddelegate;
-(void)manage;
-(void)payoff;
-(void)tel;
@end
对于老板在具体工作的实现:(boss.m文件中实现)
@implementation boss
-(void)manage{
NSLog(@"boss-->manage");
}
-(void)payoff{
[self.delegate payoff];
}
-(void)tel{
[self.delegate tel];
}
@end
委托秘书所要做的事物:(在sec.m的文件中实现完成boss的代理)
@interface Sec ()
@end
@implementation Sec
-(void)payoff{
NSLog(@"sec-->payoff");
}
-(void)tel{
NSLog(@"sec-->tel");
}
注册监听小试
NSNotificationCenter就相当于广播一样,可以对对象进行一次注册然后有多个监听。就像我们知道如果有注册的话,就需要我们对于对象进行释放(remove)。
[[NSNotificationCenter defaultCenter] postNotificationName:@"clickbt" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(click) name:@"clickbt" object:nil];
在上面的代码可以看出我们在使用注册监听时可以进行相关内容传递,需要传递的内容放在object(为id类型)。
在remove监听的方法,经过查找资料:
-(void)viewWillAppear:(BOOL)animated
{[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"clickbt" object:nil];
}-(void)viewWillDisappear:(BOOL)animated
{[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"clickbt" object:nil];
}
KVO简史
当我们说起KVO(key-value-observe)时不免想起他的孪生大哥KVC(key-value-coding)。KVO是观察者模式的继承者,基于键值变化监听者,基于KVC基础完成之一。
下列对于界面的UILabel进行监听:
[self.label addObserver:self forKeyPath:@"narcotics" options:
NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil];
一旦UILabel发生变化就会调用方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context{
//一旦有所变化调用方法
}
附带:
KVC:通过设置key值,进行标记。然后通过key值找到进行重新设置,说的通俗点点就是键值对。类似NSDictionary相似。
以前经常看些大牛写的博客,在看博客过程中也学到很多知识。心向往之,开始动手写关于自己在IOS成长路上见证。
简述block和delegation使用场景的比较
我们知道block
和delegation
是关于通信方面,在开发的过程中我们也经常会使用两者。面对具体情况我们需要怎么样做出选着呢?
1.当在一个方法的参数中需要多个对象时使用delegation
我们在开发过程中经常使用UITableView
,这时我们就要实现接口协议UITableViewDataSource
, UITableViewDelegate
两者是我们在使用UITableView
的相关数据协议和相关协议。从tableView角度出发:实现展示需要数据、对一些展示过程的控制等,这些可以使用block
来进行实现。
void (^ showTableView)(NSData *data, NSInteger number, CGFloat heightForRowAtIndexPath);
上面我们定义告诉过程需要参数在NSIndexPath
,所以第三个参数应该是 block
形式。我们上面知道block在编译过程会被编译为struct结构,在相互嵌套过程中使用delegation
更加方便。
2. 一个对象只能有一个delegation
由于一个对象只能有一个delegate,而且它只能与这个delegate通信。让我们看看CLLocationManager 这个类,当发现地理位置后,location manager 只会通知一个对象(有且只有一个)。当然,如果我们需要更多的对象去知道这个更新,我们最好创建其他的location manager。
这里有的人可能想到,如果CLLocationManager是个单例呢?如果我们不能创建CLLocationManager的其他实例,就必须不断地切换delegate指针到需要地理数据的对象上(或者创建一个只有你理解的精密的广播系统)。因此,这样看起来,delegatetion在单例上没有多大意义。
关于这点,最好的印证例子就是UIAccelerometer。在早期版本的iOS中,单例的 accelerometer 实例有一个delegate,导致我们必须偶尔切换一下。这个愚蠢的问题在之后的IOS版本被修改了,现在,任意一个对象都可以访问CMMotionManager block,而不需要阻止其他的对象来接收更新。
因此,我们可以得出另一个结论:“如果一个对象是单例,不要使用delegation”。
3. 一般delegation都有自己的而返回值
还是拿上面的例子说明:
上面是- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
UITableViewDataSource
协议中必须实现协议,可看出是使用返回值。而我们也可以看其他的一些协议表示情况,一般均是需要返回值的。
4.delegation偏向于使用的过程 VS block偏向于使用结果
如果查看NSURLConnectionDelegate
以及 NSURLConnectionDataDelegate
,我们在可以protocol中看到这样的消息:我将要做什么(如: willSendRequest,将要发送请求)、到目前为止我知道的信息(如:canAuthenticateAgainstProtectionSpace
)、我已经完成这些啦( didReceiveResponse,收到请求的回复,即完成请求)。这些消息组成一个流程,而那些对流程感兴趣的delegate将会在每一步得到相应的通知。
当我们观察handler和完整的方法时,我们发现一个block包含一个响应对象和一个错误对象。显然这里没有任何有关“我在哪里,我正在做什么的”的交互。
因此我们可以这样认为,delegate的回调更多的面向过程,而block则是面向结果的。如果你需要得到一条多步进程的通知,你应该使用delegation。而当你只是希望得到你请求的信息(或者获取信息时的错误提示),你应该使用block。(如果你结合之前的3个结论,你会发现delegate可以在所有事件中维持state,而多个独立的block确不能)。