iOS中消息传递方式
iOS中消息传递方式
在iOS中有很多种消息传递方式,这里先简单介绍一下各种消息传递方式。
1、通知:在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式。
NSNotificationCenter消息通知机制,向NSNotificationCenter
中addObserver
后,并没有对这个对象进行引用计数加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
反而不便于维护,而且看起来非常臃肿,很别扭。
例如UIKit
的UITableView
中有很多代理如果都换成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
解析流程,这些通过代理来展现比较合适。而例如一个网络请求回来,就通过success
、failure
代码块来展示就比较好。
从性能上来说,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:NSObject
的Category
-NSKeyValueObserving
,通过属性监听的方式来监测某个值的变化,当值发生变化时调用KVO
的回调方法。
KVO(key-value-observing)
是一种十分有趣的回调机制,在某个对象注册监听者后,在被监听的对象发生改变时,对象会发送一个通知给监听者,以便监听者执行回调操作。最常见的KVO运用是监听scrollView
的contentOffset
属性,来完成用户滚动时动态改变某些控件的属性实现效果,包括渐变导航栏、下拉刷新控件等效果。
使用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中消息传递方式的更多相关文章
- iOS中消息的传递机制
本文中,会经常提及接收者[recipient]和发送者[sender].在消息传递机制中具体是什么意思,我们可以通过一个示例来解释:一个table view是发送者,而它的delegate就是接收者. ...
- 分分钟搞定IOS远程消息推送
一.引言 IOS中消息的推送有两种方式,分别是本地推送和远程推送,本地推送在http://my.oschina.net/u/2340880/blog/405491这篇博客中有详细的介绍,这里主要讨论远 ...
- iOS 中的 HotFix 方案总结详解
相信HotFix大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结.iOS中的HotFix方案大致可以分为四种: WaxPatch(Alibaba) Dynamic Framework(Ap ...
- iOS开发——高级篇——iOS中常见的设计模式(MVC/单例/委托/观察者)
关于设计模式这个问题,在网上也找过一些资料,下面是我自己总结的,分享给大家 如果你刚接触设计模式,我们有好消息告诉你!首先,多亏了Cocoa的构建方式,你已经使用了许多的设计模式以及被鼓励的最佳实践. ...
- iOS中响应者链条-触摸事件
总体来说,分2个步骤: 一,从上到下寻找合适的控件来处理这个触摸事件.如下图,如果点击了黄色4,则UIApplication -> UIWindow -> 1白色 -> 2橙色 -& ...
- iOS开发小技巧--iOS中设置applicationIconBadgeNumber遇到的问题
iOS中设置applicationIconBadgeNumber 在iOS7中直接设置applicationIconBadgeNumber没有问题,但是在iOS8之后设置applicationIcon ...
- iOS中多线程原理与runloop介绍
一.线程概述 有些程序是一条直线,起点到终点:有些程序是一个圆,不断循环,直到将它切断.直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样:圆如操作系统,一直运行直 ...
- ios中的RunLoop 和 android 中的Looper
今天写android程序,用到了Handler,晚上回来查阅资料,发现了Looper这个概念. 看了一下网上关于Looper的资料,发现这个Looper跟ios中的Runloop基本的理念完全一致! ...
- iOS中NSUserDefaults详解
NSUserDefault 作为iOS中一种轻量级数据本地化方式,简单易用,经常用于存储一些应用相关属性记录,例如图书app的背景色,进度,上次阅读的书籍及相关配置信息.NSUserDefault实质 ...
随机推荐
- 对象序列化与反序列化(二进制 byte[])
.序列化 public static byte[] SerializeObject(object obj) { if (obj == null) return null; MemoryStream m ...
- javascript计算字符串长度
javascript计算字符串长度 学习了:https://blog.csdn.net/u012934325/article/details/75214847 function getByteLen( ...
- jQuery 图片裁剪插件 Jcrop
Jcrop是一个jQuery图片裁剪插件,它能为你的WEB应用程序快速简单地提供图片裁剪的功能.特点如下: 对所有图片均unobtrusively(无侵入的,保持DOM简洁) 支持宽高比例锁定 支持 ...
- [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 ...
- [Spring Boot] Singleton and Prototype
When we use Bean to do autowired, it actually use singleton, so even we create multi instanses, they ...
- SQL从一个表查询数据插入/更新到另一个表
示例一: 从数据库表A中查询出数据插入到数据库表B 从数据库DataBaseA的表TDA中查询出数据插入到数据库DataBaseB的表TDB insert into [DataBaseA].[dbo] ...
- js 开源k线图开发库
https://github.com/andredumas/techan.js/wiki http://techanjs.org/ A visual, stock charting (Candlest ...
- logback debug 日志没有信息
可能是项目绑定的日志不是logback的jar包,而是其他包,具体可查看tomcat启动日志 log4j:WARN No appenders could be found for logger (or ...
- Java实战_手把手编写记事本
Java运用SWT插件编写桌面记事本应用程序 可实现windows系统桌面记事本基本功能.傻瓜式教学,一步一步手把手操作.小白也可自己编写出完整的应用程序. 须要工具:Eclipse(带SWT插件) ...
- Webwork【07】文件上传下载
Web上传和下载应该是很普遍的一个需求,无论是小型网站还是大并发访问的交易网站.WebWork 当然也提供了很友好的拦截器来实现对文件的上传,让我们可以专注与业务逻辑的设计和实现,在实现上传和下载时顺 ...