iOS中消息传递方式

在iOS中有很多种消息传递方式,这里先简单介绍一下各种消息传递方式。

1、通知:在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式。

  NSNotificationCenter消息通知机制,向NSNotificationCenteraddObserver后,并没有对这个对象进行引用计数加1操作,所以它只是保存了地址。为了验证这个操作,我们来做下代码的测试。

 [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
} - (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"test" object:nil];
}

多线程通知:

NSNotificationCenter消息的接受线程是基于发送消息的线程的。也就是同步的,因此,有时候,你发送的消息可能不在主线程,而大家都知道操作UI必须在主线程,不然会出现不响应的情况。所以,在你收到消息通知的时候,注意选择你要执行的线程。

代理和block的选择

  • 多个消息传递,应该使用delegate
    在有多个消息传递时,用delegate实现更合适,看起来也更清晰。block就不太好了,这个时候block反而不便于维护,而且看起来非常臃肿,很别扭。
    例如UIKitUITableView中有很多代理如果都换成block实现,我们脑海里想一下这个场景,这里就不用代码写例子了.....那简直看起来不能忍受。

  • 一个委托对象的代理属性只能有一个代理对象,如果想要委托对象回调多个代理对象应该用block。(这里主要是针对于对象内部属性不会对block进行引用的情况下,否则再调用同一个方法也会造成重新赋值问题)

  上面图中代理1可以被设置,代理2和代理3设置的时候被划了叉,是因为这个步骤是错误的操作。我们上面说过,delegate只是一个保存某个代理对象的地址,如果设置多个代理相当于重新赋值,只有最后一个设置的代理才会被真正赋值。

  block不应用于声明在.h文件中属性回调,主要是应用于方法回调。例如现在有如下情况需要回调,用block可以,但是用设置delegate方式就不行了:“假设有block回调对象downloadImage类,同一个downloadImage对象带有block回调的方法,在多个类或多个地方进行回调,这种情况就更适合用block的方式了。“每调用一次方法就可以在block回调代码块中,进行自己的操作,比代理的方式更佳强大。

  单例对象最好不要用delegate,单例对象由于始终都只是同一个对象,如果使用delegate,就会造成我们上面说的delegate属性被重新赋值的问题,最终只能有一个对象可以正常响应代理方法。这种情况我们可以使用block的方式,在主线程的多个对象中使用block都是没问题的,下面我们将用一个循环暴力测试一下block到底有没有问题。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 10;
for (int i = 0; i < 100; i++) {
[queue addOperationWithBlock:^{
[[LoginViewController shareInstance] userLoginWithSuccess:^(NSString *username) {
NSLog(@"TestTableViewController : %d", i);
}];
}];
}

  上面用NSOperationQueue创建了一个新的队列,并且将最大并发数设置为10,然后创建一个100次的循环。我们在多线程情况下测试单例在block的情况下能否正常使用,答案是可以的。
  但是我们还是需要注意一点,在多线程情况下因为是单例对象,我们对block中必要的地方加锁,防止资源抢夺的问题发生。

  代理是可选的,而block在方法调用的时候只能通过将某个参数传递一个nil进去,只不过这并不是什么大问题,没有代码洁癖的可以忽略。

[self downloadTaskWithResumeData:resumeData sessionManager:manager savePath:savePath progressBlock:nil successBlock:successBlock failureBlock:failureBlock];

  从设计模式的角度来说,代理更佳面向过程,而block更佳面向结果。例如我们使用NSXMLParserDelegate代理进行XML解析,NSXMLParserDelegate中有很多代理方法,NSXMLParser会不间断调用这些方法将一些转换的参数传递出来,这就是NSXMLParser解析流程,这些通过代理来展现比较合适。而例如一个网络请求回来,就通过successfailure代码块来展示就比较好。

  从性能上来说,block的性能消耗要略大于delegate,因为block会涉及到栈区向堆区拷贝等操作,时间和空间上的消耗都大于代理。而代理只是定义了一个方法列表,在遵守协议对象的objc_protocol_list中添加一个节点,在运行时向遵守协议的对象发送消息即可。

多线程通知:

  NSNotificationCenter消息的接受线程是基于发送消息的线程,也就是同步的。因此,有时候,你发送的消息可能不在主线程,而大家都知道操作UI必须在主线程,不然会出现不响应的情况。所以,在你收到消息通知的时候,注意选择你要执行的线程。

//接受消息通知的回调
- (void)test
{
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"main");
} else {
NSLog(@"not main");
}
dispatch_async(dispatch_get_main_queue(), ^{
//do your UI
});
} //发送消息的线程
- (void)sendNotification
{
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(defaultQueue, ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
});
}

2、代理:是一种通用的设计模式,iOS中对代理支持的很好,由代理对象、委托者、协议三部分组成。

  代理是一种通用的设计模式,在iOS中对代理设计模式支持的很好,有特定的语法来实现代理模式,OC语言可以通过@Protocol实现协议。

代理主要由三部分组成:

  • 协议:用来指定代理双方可以做什么,必须做什么。

  • 代理:根据指定的协议,完成委托方需要实现的功能。

  • 委托:根据指定的协议,指定代理去完成什么功能。

(1)

@protocol LoginProtocol
@optional
- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;
@end
// 通过属性来设置代理对象
@property (nonatomic, weak) id delegate;
// 判断代理对象是否实现这个方法,没有实现会导致崩溃
if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) {
// 调用代理对象的登录方法,代理对象去实现登录方法
[self.delegate userLoginWithUsername:self.username.text password:self.password.text];
}

(2)

// 遵守登录协议
@interface ViewController ()
@end @implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad]; LoginViewController *loginVC = [[LoginViewController alloc] init];
loginVC.delegate = self;
[self.navigationController pushViewController:loginVC animated:YES];
} /**
* 代理方实现具体登录细节
*/
- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password {
NSLog(@"username : %@, password : %@", username, password);
}

3、block:iOS4.0中引入的一种回调方法,可以将回调处理代码直接写在block代码块中,看起来逻辑清晰代码整齐。

①在后面控制器的 .h文件 中声明block

// 一会要传的值为NSString类型
typedef void (^newBlock)(NSString *); @interface NewViewController : UIViewController
// 声明block属性
@property (nonatomic, copy) newBlock block;
// 声明block方法
- (void)text:(newBlock)block;
@end

②在后面控制器的 .m文件 中设置block

// 设置block,设置要传的值
- (void)text:(newBlock)block
{
self.block = block;
} - (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
if (self.block != nil) {
self.block(@"呵呵");
}
}

③在前面控制器的 .m文件 中接收传来的值

#import "ViewController.h"
#import "NewViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:(UIButtonTypeRoundedRect)];
button.frame = CGRectMake(, , , );
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:@selector(push) forControlEvents:(UIControlEventTouchUpInside)];
[self.view addSubview:button];
} - (void)push
{
NewViewController *newVC = [[NewViewController alloc] init];
// 接收block传来的值
newVC.block = ^(NSString *str){
NSLog(@"%@", str);
};
[self.navigationController pushViewController:newVC animated:YES];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

4、KVO:NSObjectCategoryNSKeyValueObserving,通过属性监听的方式来监测某个值的变化,当值发生变化时调用KVO的回调方法。

  KVO(key-value-observing)是一种十分有趣的回调机制,在某个对象注册监听者后,在被监听的对象发生改变时,对象会发送一个通知给监听者,以便监听者执行回调操作。最常见的KVO运用是监听scrollViewcontentOffset属性,来完成用户滚动时动态改变某些控件的属性实现效果,包括渐变导航栏、下拉刷新控件等效果。

  使用KVO的要求是对象必须能支持kvc机制——所有NSObject的子类都支持这个机制。拿上面的渐变导航栏做,我们为tableView添加了一个监听者controller,在我们滑动列表的时候,会计算当前列表的滚动偏移量,然后改变导航栏的背景色透明度。

//添加监听者
[self.tableView addObserver: self forKeyPath: @"contentOffset" options: NSKeyValueObservingOptionNew context: nil];
/**
* 监听属性值发生改变时回调
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
CGFloat offset = self.tableView.contentOffset.y;
CGFloat delta = offset / .f + .f;
delta = MAX(, delta);
[self alphaNavController].barAlpha = MIN(, delta);
}

每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。

@implementationViewController

- (void)viewDidLoad {
[superviewDidLoad];
self.person = [[Personalloc] init];
NSLog(@"%@",_person.name); #pragma mark ------使用KVC检测person对象的name属性值有没有发生变化,当它变化时,观察者会做出相应的操作*(执行指定方法) // 1.注册观察者
[_person   addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"我观察的是name属性"];
} // 2.实现指定的方法(回调方法)
//当person的name值发生变化时,观察者会自动执行这个方法,这个方法名是固定的,不可改变
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
  NSLog(@"被监测的那个对象的属性所在的路径:%@",keyPath);
   NSLog(@"被观察者:%@", object);
NSLog(@"属性所有状态下的值:%@", change);
NSLog(@"在注册观察者的时候,传过来的context :%@", context);
if(![[changeobjectForKey:@"new"]isEqualToString:[changeobjectForKey:@"old"]]) {
self.view.backgroundColor= [UIColorredColor];
} //4.移除观察者
[_person removeObserver:selfforKeyPath:keyPathcontext:context];
} - (IBAction)changePersonValue:(UIButton*)sender { // 3.当属性值发生变化时,将会触发回调方法
   NSLog(@"name值发生了变化!");
  _person.name=_textField.text;
} @end
//注册屏幕旋转通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientChange:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarOrientationChange:)name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
// 监听网络状态改变的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkStateChange) name:kReachabilityChangedNotification object:nil];
//监听系统中断音频播放
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yinpinInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
//监听APP状态信息
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(someMethod1:)
name:UIApplicationDidBecomeActiveNotification object:nil];
// 键盘通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillHide:) name:UIKeyboardWillHideNotification object:nil]; #pragma mark - 键盘显示/隐藏
/**
* 键盘显示
*
* @param note
*/
- (void)keyBoardWillShow:(NSNotification *)note{
CGRect rect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
[UIView animateWithDuration:[note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue] animations:^{
_tableView.frame = CGRectMake(, , self.view.frame.size.width, self.view.frame.size.height-rect.size.height);
}completion:^(BOOL finished) {
}];
} /**
* 键盘隐藏
*
* @param note
*/
- (void)keyBoardWillHide:(NSNotification *)note{
[UIView animateWithDuration:[note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue] animations:^{
_tableView.frame = CGRectMake(, , CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-);
}];
}
 
 

  

 
 
 

iOS中消息传递方式的更多相关文章

  1. iOS中消息的传递机制

    本文中,会经常提及接收者[recipient]和发送者[sender].在消息传递机制中具体是什么意思,我们可以通过一个示例来解释:一个table view是发送者,而它的delegate就是接收者. ...

  2. 分分钟搞定IOS远程消息推送

    一.引言 IOS中消息的推送有两种方式,分别是本地推送和远程推送,本地推送在http://my.oschina.net/u/2340880/blog/405491这篇博客中有详细的介绍,这里主要讨论远 ...

  3. iOS 中的 HotFix 方案总结详解

    相信HotFix大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结.iOS中的HotFix方案大致可以分为四种: WaxPatch(Alibaba) Dynamic Framework(Ap ...

  4. iOS开发——高级篇——iOS中常见的设计模式(MVC/单例/委托/观察者)

    关于设计模式这个问题,在网上也找过一些资料,下面是我自己总结的,分享给大家 如果你刚接触设计模式,我们有好消息告诉你!首先,多亏了Cocoa的构建方式,你已经使用了许多的设计模式以及被鼓励的最佳实践. ...

  5. iOS中响应者链条-触摸事件

    总体来说,分2个步骤: 一,从上到下寻找合适的控件来处理这个触摸事件.如下图,如果点击了黄色4,则UIApplication -> UIWindow -> 1白色 -> 2橙色 -& ...

  6. iOS开发小技巧--iOS中设置applicationIconBadgeNumber遇到的问题

    iOS中设置applicationIconBadgeNumber 在iOS7中直接设置applicationIconBadgeNumber没有问题,但是在iOS8之后设置applicationIcon ...

  7. iOS中多线程原理与runloop介绍

    一.线程概述 有些程序是一条直线,起点到终点:有些程序是一个圆,不断循环,直到将它切断.直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样:圆如操作系统,一直运行直 ...

  8. ios中的RunLoop 和 android 中的Looper

    今天写android程序,用到了Handler,晚上回来查阅资料,发现了Looper这个概念. 看了一下网上关于Looper的资料,发现这个Looper跟ios中的Runloop基本的理念完全一致! ...

  9. iOS中NSUserDefaults详解

    NSUserDefault 作为iOS中一种轻量级数据本地化方式,简单易用,经常用于存储一些应用相关属性记录,例如图书app的背景色,进度,上次阅读的书籍及相关配置信息.NSUserDefault实质 ...

随机推荐

  1. 对象序列化与反序列化(二进制 byte[])

    .序列化 public static byte[] SerializeObject(object obj) { if (obj == null) return null; MemoryStream m ...

  2. javascript计算字符串长度

    javascript计算字符串长度 学习了:https://blog.csdn.net/u012934325/article/details/75214847 function getByteLen( ...

  3. jQuery 图片裁剪插件 Jcrop

    Jcrop是一个jQuery图片裁剪插件,它能为你的WEB应用程序快速简单地提供图片裁剪的功能.特点如下: 对所有图片均unobtrusively(无侵入的,保持DOM简洁) 支持宽高比例锁定 支持 ...

  4. [Spring Boot] Use Component Scan to scan for Bean

    Component Scan is important concept when we want to create Bean. Currently we know what, for the cla ...

  5. [Spring Boot] Singleton and Prototype

    When we use Bean to do autowired, it actually use singleton, so even we create multi instanses, they ...

  6. SQL从一个表查询数据插入/更新到另一个表

    示例一: 从数据库表A中查询出数据插入到数据库表B 从数据库DataBaseA的表TDA中查询出数据插入到数据库DataBaseB的表TDB insert into [DataBaseA].[dbo] ...

  7. js 开源k线图开发库

    https://github.com/andredumas/techan.js/wiki http://techanjs.org/ A visual, stock charting (Candlest ...

  8. logback debug 日志没有信息

    可能是项目绑定的日志不是logback的jar包,而是其他包,具体可查看tomcat启动日志 log4j:WARN No appenders could be found for logger (or ...

  9. Java实战_手把手编写记事本

    Java运用SWT插件编写桌面记事本应用程序 可实现windows系统桌面记事本基本功能.傻瓜式教学,一步一步手把手操作.小白也可自己编写出完整的应用程序. 须要工具:Eclipse(带SWT插件) ...

  10. Webwork【07】文件上传下载

    Web上传和下载应该是很普遍的一个需求,无论是小型网站还是大并发访问的交易网站.WebWork 当然也提供了很友好的拦截器来实现对文件的上传,让我们可以专注与业务逻辑的设计和实现,在实现上传和下载时顺 ...