• 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);
        }];
    }

     

     



     

      

     
     



     
  • 相关阅读:
    pythonic奇淫技巧收集
    广联达软件关闭非必要后台进程、服务
    寒假集训Day6 H(二分答案)
    GJSxCXzcjw
    生活新的篇章~
    Apex: SOQL语句未查询到数据时的返回值
    VS Code关联Salesforce失效
    一个sql和C#代码结合的分组求和的查询
    2022开发上的一些常见技术问题整理
    2021迷惘惆怅的一年
  • 原文地址:https://www.cnblogs.com/fengmin/p/6140869.html
Copyright © 2020-2023  润新知