iOS中消息传递方式

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(0, 100, 100, 100);
    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 / 64.f + 1.f;
    delta = MAX(0, delta);
    [self alphaNavController].barAlpha = MIN(1, 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 defaultCenteraddObserver: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(0, 20, 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(0, 20, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-20);
    }];
}

 

 



 

  

 
 



 
原文地址:https://www.cnblogs.com/fengmin/p/6140869.html