精华内容
下载资源
问答
  • 以及他们之间关系是怎样(实例与类对象之间的关系以及类对象与元类对象之间的关系)? OC语言中的消息传递机制是怎样? 方法缓存 (我们进行方法查找过程当中,如何进行缓存方法查找?会使用到系统或者...

    4.动态运行时(RunTime)

    • 基础数据结构
    • 对象、类对象、元类对象是怎么理解的?以及他们之间的关系是怎样的(实例与类对象之间的关系以及类对象与元类对象之间的关系)?
    • OC语言中的消息传递机制是怎样的?
    • 方法缓存  (我们进行方法查找的过程当中,如何进行缓存的方法查找?会使用到系统或者说Runtime中一个方法缓存的机制,这个机制又是怎样运行的呢?)
    • 消息转发流程是怎样的?
    • Method-Swizzling(方法混写)是Runtime的一种运用(我们可以再运行时,去替换一些方法的实现,也得益于动态运行时这一个特性),可能会结合实际的场景,比如说通过时长统计框架这样的考察来了解对于Runtime的Methood-Swizzling技术的一个实际的运用
    • 动态添加方法(Runtime提供的功能实现)
    • 动态方法解析(Runtime提供的功能实现)
       

    对象、类对象、元类对象类对象、元类对象分别是什么,他们的区别是什么?)

    • 类对象是存储实例方法列表等信息的结构,实例对象可以通过isa指针找到自己的类对象,来访问实例方法
    • 元类对象存储类方法列表等信息的内容, 类对象可以通过isa找到自己的元类对象,来访问类方法

    红色箭头代表superClass指向,黑色虚线表示isa指针的指向

    • Root class代表根类,父类指向nil,可以理解为NSObject 
      对应这个类对象,有子类Superclass(class),以及子类的子类Subclass(class)
    • 当我们给定一个实例的时候,左边的实例(instance of Subclass)是id类型的,也就是objc_object数据结构,内含isa成员变量指针,指向这个实例所对应的类对象 (例如有个类叫ClassA,创建出一个instanceA,它的isa指针就指向classA)
      所以:实例和类对象的关系:实例(instance)通过isa指针可以找到自己的类对象(class
    • 中间那行类对象是objc_class结构, 最右侧(Superclass)分别为子类,父类和根类的元类对象(元类对象之间也是继承关系)
    • 类对象class也有isa指针,指向元类对象(Superclass meta)
    • 元类对象因为也是objc_class类型,所以也有isa指针。对于任何一个元类对象,包括根元类对象自身,它的isa指针都指向根元类对象
    • 根元类对象的superClass指针指向它的根类对象

    类对象和元类对象的区别和联系:

    1. 实例(instance)通过isa指针可以找到自己的类对象(class),类对象当中存储方法列表等信息
    2. 以及类对象可以通过isa指针找到它的元类对象(Superclass meta),从而可以访问一些关于类方法列表的相关信息。
    3. 类对象和元类对象都是objc_class数据结构的,因为objc_class继承了objc_object,所以它们才有isa指针,进而才能实现我们刚才所描述的操作。

     

    如果我们调用了一个类方法,会从元类的方法列表中查找,查找的过程是沿着红色箭头指向逐级查找
    当我们调用的类方法,在元类当中找不到的时候,就会找根类对象当中同名的实例方法实现

    所以,如果我们调用一个类方法,没有对应的实现,但是有同名的实例方法实现,会不会发生崩溃?会不会产生实际的调用?

    • 会产生实际调用,原因是根元类对象的superClass指针指向了根类对象
    • 所以在元类中查找不到类方法的时候,就会顺着superClass指针去实例方法列表中查找
    • 若有同名方法,就会实现调用
       

    简单的描述下消息传递的过程:

    • 假如我们调用了实例方法A(instance),系统会根据当前实例的isa指针,找到它的类对象。
      在它的类对象中遍历方法列表,若没找到
      会顺着SubClass指针指向去父类(SuperClass)类对象方法列表当中查找
      一直到根类(RootClass)对象的方法列表查找,如果没找到就会进入消息转发流程
    • 如果调用的是类方法(SubClass),系统会通过类对象的isa指针去原来对象中查找,顺次遍历方法列表
      直到根元类(Rootclass meta)对象
      然后再到根类对象(RootClass),都没有的话,进入消息转发流程
    • 遍历类方法和实例方法的区别是:在根元类的指向根元类的super会指向根类对象

    OC语言中的消息传递机制是怎样的?

    •  objc_msgSend函数固定要接受两个参数,id类型的self和SEL类型的方法选择器名称
       再后面才是消息传递的真正的方法参数。
    • 对于任何一个消息传递[self Class],通过编译器都会转换成关于objc_msgSend的函数调用
      第一个参数是消息传递的接收者self ,第二个参数是我们传递的消息名称,也叫做选择器
    • 对于消息传递,在编译器层面,其实转换成了函数调用
      [self Class] ---- objc_msgSend(self,@selector(class))

    另一个函数:

    • 也有两个固定参数, super结构体类型的指针和方法选择器(SEL op),后续是实际参数(...)
    • super结构体中包含了receiver成员变量,这个接收者就是当前对象
      因为super是编译器关键字,经过编译器编译之后,会解析成objc_super结构体指针
      而结构体中的成员变量receiver,就是当前对象
    • 不论是调用self class还是super class ,这条消息的接收者都是当前对象

    那么self class和super class有什么区别呢? 

     

    • 在调用一个方法的时候,先会查找缓存,看缓存中是否有对应选择器名称的方法。
    • 如果有,就通过函数指针调用函数,就完成了消息传递。
    • 如果缓存没有,就会根据当前实例的isa指针,去查找当前类对象的方法列表
              如果找到同样名称的方法,就通过函数指针调用这个函数实现,结束这次消息传递
              如果当前类方法中没有,通过当前类对象的superClass指针去就逐级父类方法列表查找。
              若父类也没有,就根据父类的superClass指针往上查找,直到nil,若都没有,就进入消息转发流程

    总结:首先查缓存,没命中查当前类的方法列表,没命中顺次查找它的各个父类方法列表,都没命中转到消息转发流程。下个章节方法缓存再做补充

    具体缓存查找流程

    根据给定的方法选择器(SEL),来查找(bucket_t)中对应的方法实现(IMP)

    1. 缓存是否命中,缓存查找是哈希查找
    2. 当前类方法列表是否命中,已排序好的是二分查找,未排序好的是一般查找
    3. 逐级父类方法列表是否命中,根据superClass指针逐级查找父类,在父类中也是先查找缓存,再查找父类

    看下这个笔试题:


     

    id objc_msgSend(id self, SEL op, ...);
    /*
    返回值是一个id,objc_msgSend发送消息的本质就是,
    第一个参数self就是当前响应消息的对象指针,记录这个对象的地址
    SEL类型 类似C语言中的函数指针,记录方法列表。在OC里面就是方法名。
    */
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_0,
    NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
    
    • 对于[self class]来说,首先会被转化成objc_msgSend函数调用
      [super class]会被转化成objc_msgSendSuper函数调用,objc_msgSendSuper的参数虽然是super,
      但super结构体里面包的receiver是当前对象,所以这两个方法的接收者都是当前对象
    • 假如现在Phone,实例在8,初始化的时候,我们通过[self class]打印类信息
      会通过8的isa指针找到Phone的类对象14在这里寻找class方法,本身是没有的
      然后会通过superClass指针向上找,找到6号Mobile父类,他这里也没有class实现
      一直找到根类对象5号,也就是NSObject,有class实现,就会调用class具体实现
      返回给调用方,打印出来的就是Phone
    id objc_msgSendSuper(struct objc_super *super, SEL op, ...);
    /*
     struct objc_super *super  结构体指针类型。-->struct objc_super{id receiver;Class super_class;}    receiver:记录消息的接收者  super_class:表示super表示的父类是什么。super是调用父类的方法的。
      
    */
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_1,
    NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super)
    {(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"))));
    • 对于[super Class],实际的接收者仍然是Phone这个实例,调用了objc_msgSendSuper
      也就是从父类的方法列表6开始查找,跨越了当前Phone的类方法列表14号,
      但6号也没有Class方法,一直找到根类NSObject有Class,
      接收者仍然是当前对象(id)self,返回的仍然是Phone
    • 所以此面试题打印结果两个都是Phone。


     

    展开全文
  • iOS中消息的传递机制

    千次阅读 2016-03-19 19:21:13
    每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果Framework中如何使用,同时,还...

    iOS中消息的传递机制

    每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好的完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果的Framework中如何使用,同时,还介绍了一些最佳实践建议,告诉你什么时机该选择使用什么机制。

    虽然这一期的主题是关于Foundation Framework的,不过本文中还介绍了一些超出Foundation Framework(KVO和Notification)范围的一些消息传递机制,另外还介绍了delegation,block和target-action。

    大多数情况下,消息传递该使用什么机制,是很明确的了,当然了,在某些情况下该使用什么机制并没有明确的答案,需要你亲自去尝试一下。

    本文中,会经常提及接收者[recipient]和发送者[sender]。在消息传递机制中具体是什么意思,我们可以通过一个示例来解释:一个table view是发送者,而它的delegate就是接收者。Core Data managed object context是notification的发送者,而获取这些notification的主体则是接收者。一个滑块(slider)是action消息的发送者,而在代码里面对应着实现这个action的responder就是接收者。对象中的某个属性支持KVO,那么谁修改这个值,谁就是发送者,对应的观察者(observer)则是接收者。

    可用的机制

    首先我们来看看每种机制的具体特点。在下一节中,我会结合一个流程图来介绍如何在具体情况下,选择正确的消息传递机制。最后,将介绍一些来自苹果Framework中的示例,并会解释在某种确定情况下为什么要选择固定的机制。

    KVO

    KVO提供了这样一种机制:当对象中的某个属性值发生了改变,可以对这些值的观察者做出通知。KVO的实现包含在Foundation里面,基于Foundation构建的许多Framework对KVO都有所依赖。要想了解更多关于如何使用KVO,可以阅读本期由Daniel写的的KVO和KVC文章。

    如果对某个对象中值的改变情况感兴趣,那么可以使用KVO消息传递机制。这里有两个要求,首先,接收者(会接收到值发生改变的消息)必须知道发送者(值将发生改变的那个对象)。另外,接收者同样还需要知道发送者的生命周期,因为在销毁发送者对象之前,需要取消观察者的注册。如果这两个要求都满足了,消息传递过程中可以是1对多(多个观察者可以注册某个对象中的值)。

    如果计划在Core Data对象上使用KVO,需要知道这跟一般的KVO使用方法有点不同。那就是必须结合Core Data的故障机制(faulting mechanism),一旦core data出现了故障,它将会触发其属性对应的观察者(即使这些属性值没有发生改变)。

    Notifications

    在不相关的两部分代码中要想进行消息传递,通知(notifacation)是非常好的一种机制,它可以对消息进行广播。特别是想要传递丰富的信息,并且不一定指望有谁对此消息关心。

    通知可以用来发送任意的消息,甚至包含一个userInfo字典,或者是NSNotifacation的一个子类。通知的独特之处就在于发送者和接收者双方并不需要相互知道。这样就可以在非常松耦合的模块间进行消息的传递。记住,这种消息传递机制是单向的,作为接收者是不可以回复消息的。

    delegate

    在苹果的Framework中,delegation模式被广泛的只用着。delegation允许我们定制某个对象的行为,并且可以收到某些确定的事件。为了使用delegation模式,消息的发送者需要知道消息的接收者(delegate),反过来就不用了。这里的发送者和接收者是比较松耦合的,因为发送者只知道它的delegate是遵循某个特定的协议。

    delegate协议可以定义任意的方法,因此你可以准确的定义出你所需要的类型。你可以用函数参数的形式来处理消息内容,delegate还可以通过返回值的形式给发送者做出回应。如果只需要在相对接近的两个模块之间进行消息传递,那么Delegation是一种非常灵活和直接方式。

    不过,过渡使用delegation也有一定的风险,如果两个对象的耦合程度比较紧密,相互之间不能独立存在,那么此时就没有必要使用delegate协议了,针对这种情况,对象之间可以知道相互间的类型,进而直接进行消息传递。例如UICollectionViewLayout和NSURLSessionConfiguration。

    Block

    Block相对来说,是一种比较新的技术,它首次出现是在OS X 10.6和iOS 4中。一般情况下,block可以满足用delegation实现的消息传递机制。不过这两种机制都有各自的需求和优势。
    当不考虑使用block时,一般主要是考虑到block极易引起retain环。如果发送者需要reatain block,而又不能确保这个引用什么时候被nil,这样就会发生潜在的retain环。
    假设我们想要实现一个table view,使用block替代delegate,来当做selection的回调,如下:

    self.myTableView.selectionHandler = ^void(NSIndexPath *selectedIndexPath) { 
        // handle selection ... 
    }; 

    上面代码的问题在于self retain了table view,而table view为了之后能够使用block,进而 retain了block。而table view又不能把这个引用nil掉,因为它不知道什么时候不在需要这个block了。如果我们保证不了可以打破这个retain环,而我们又需要retain发送者,此时block不是好的选择。

    NSOperation就可以很好的使用block,因为它能再某个时机打破retain环:

    self.queue = [[NSOperationQueue alloc] init]; 
    MyOperation *operation = [[MyOperation alloc] init]; 
    operation.completionBlock = ^{ 
        [self finishedOperation]; 
    }; 
    [self.queue addOperation:operation]; 

    乍一看这似乎是一个retain环:self retain了queue,queue retain了operation,而operation retain了completion block,而completion blockretain了self。不过,在这里,将operation添加到queue时,会使operation在某个时机被执行,然后从queue中remove掉(如果没有被执行,就会有大问题了)。一单queue移除了operation之后,retain环就被打破了。

    再来一个示例:这里实现了一个视频编码器的类,里面有一个名为encodeWithCompletionHandler:的方法。为了避免出现retain环,我们需要确保编码器这个对象能够在某个时机nil掉其对block的引用。其内部代码如下所示:

    @interface Encoder () 
    @property (nonatomic, copy) void (^completionHandler)(); 
    @end 
    
    @implementation Encoder 
    
    - (void)encodeWithCompletionHandler:(void (^)())handler 
    { 
        self.completionHandler = handler; 
        // do the asynchronous processing... 
    } 
    
    // This one will be called once the job is done 
    - (void)finishedEncoding 
    { 
        self.completionHandler(); 
        self.completionHandler = nil; // <- Don't forget this! 
    } 
    
    @end 

    在上面的代码中,一旦编码任务完成,就会调用complietion block,进而把引用nil掉。

    如果我们发送的消息属于一次性的(具体到某个方法的调用),由于这样可以打破潜在的retain环,那么使用block是非常不错的选择。另外,如果为了让代码可读性更强,更有连贯性,那最好是使用block了。根据这个思路,block经常可以用于completion handler、error handler等。

    Target-Action

    Target-Action主要被用于响应用户界面事件时所需要传递的消息中。iOS中的UIControl和Mac中的NSControl/NSCell都支持这种机制。Target-Action在消息的发送者和接收者之间建立了一个非常松散耦合。消息的接收者不知道发送者,甚至消息的发送者不需要预先知道消息的接收者。如果target是nil,action会在响应链(responder chain)中被传递,知道找到某个能够响应该aciton的对象。在iOS中,每个控件都能关联多个target-action。

    基于target-action消息传递的机制有一个局限就是发送的消息不能携带自定义的payload。在Mac的action方法中,接收者总是被放在第一个参数中。而在iOS中,可以选择性的将发送者和和触发action的事件作为参数。除此之外,没有别的办法可以对发送action消息内容做控制。

    做出正确的选择

    根据上面讨论的结果,这里我画了一个流程图,来帮助我们何时使用什么消息传递机制做出更好的决定。忠告:流程图中的建议并非最终的答案;可能还有别的选项依然能实现目的。只不过大多数情况下此图可以引导你做出正确的决定。

    这里写图片描述

    上图中,还有一些细节需要做更近一步的解释:

    上图中的有个盒子这样说到:sender is KVO compliant(发送者支持compliant)。这不仅以意味着当值发生改变时,发送者会发送KVO通知,并且观察者还需要知道发送者的生命周期。如果发送者被存储在一个weak属性中,那么发送者有可能被nil掉,进而引起观察者发生leak。

    另外底部的一个盒子说到:message is direct response to method call(消息直接在方法的调用代码中响应)。也就是说处理消息的代码跟方法的调用代码处于相同的地方。

    最后,在左下角,处于一个决策问题的判断状态:sender can guarantee to nil out reference to block?(发送者能够确保nil掉到block的引用吗?),这实际上涉及到之前我们讨论到基于block 的APIs已经潜在的retain环。使用block时,如果发送者不能保证在某个实际能够把对block的引用nil掉,那么将会遇到retain环的问题。

    Framework示例

    本节我们通过一些来自苹果Framework的示例,来看看在实际使用某种机制之前,苹果是处于何种原因做出选择的。

    KVO

    NSOperationQueue就是lion给了KVO来观察队列中operation状态属性的改变情况(isFinished, isExecuting, isCancelled)。当状态发生了改变,队列会受到一个KVO通知。为什么operationqueue要是用KVO呢?

    消息的接收者(operation queue)明确的知道发送者(opertation),以及通过retain来控制operation的生命周期。另外,在这种情况下,只需要单向的消息传递机制。当然,如果这样考虑:如果operation queue只关心operation值的改变情况,可能还不足以说服大家使用KVO。但是我们至少可以这样理解:什么机制可以对值的改变进行消息传递呢。

    这里写图片描述

    当然KVO也不是唯一的选择。我们可以这样设计:operation queue作为operation的delegate,operation会调用类似operationDidFinish: 或 operationDidBeginExecuting: 这样的方法,来将它的state传递给queue。这样一来,就不太方便了,因为operation需要将其state属性保存下来,一遍调用这些delegate方法。另外,由于queue不能主动获取state信息,所以queue也必须保存着所有operation的state。

    Notifications

    Core Data使用notification来传递事件(例如一个managed object context内部的改变——NSManagedObjectContextDidChangeNotification)。

    change notification是由managed object context发出的,所以我们不能确定消息的接收者一定知道发送者。如果消息并不是一个UI事件,而有可能多个接收者对该消息感兴趣,并且消息的传递属于单向(one-way communication channel),那么notification是最佳选择。

    这里写图片描述

    delegate

    Table view的delegate有多种功能,从accessory view的管理,到屏幕中cell显示的跟踪,都与delegate的功劳。例如,我们来看看 tableView:didSelectRowAtIndexPath: 方法。为什么要以delegate调用的方式来实现?而又为啥不用target-action方式?

    正如我们在流程图中看到的一样,使用target-action时,不能传递自定义的数据。而在选中table view的某个cell时,collection view不仅仅需要告诉我们有一个cell被选中了,还需要告诉我们是哪个cell被选中了(index path)。按照这样的一种思路,那么从流程图中可以看到应该使用delegation机制。

    这里写图片描述

    如果消息传递中,不包含选中cell的index path,而是每当选中项改变时,我们主动去table view中获取到选中cell的相关信息,会怎样呢?其实这会非常的麻烦,因为这样一来,我们就必须记住当前选中项相关数据,以便获知被选中的cell。

    同理,虽然我们也可以通过观察table view中选中项的index paths属性值,当该值发生改变时,获得一个选中项改变的通知。不过,我们会遇到与上面同样的问题:不做任何记录的话,我们如何获知被选中项的相关信息。

    Block

    关于block的介绍,我们来看看[NSURLSession dataTaskWithURL:completionHandler:]吧。从URL loading system返回到调用者,这个过程具体是如何传递消息的呢?首先,作为这个API的调用者,我们知道消息的发送者,但是我们并没有retain这个发送者。另外,这属于单向消息传递——直接调用dataTaskWithURL:方法。如果按照这样的思路对照着流程图,我们会发现应该使用基于block消息传递的机制。

    这里写图片描述

    还有其它可选的机制吗?当然有了,苹果自己的NSURLConnection就是最好的例子。NSURLConnection在block问世之前就已经存在了,所以它并没有利用block进行消息传递,而是使用delegation机制。当block出现之后,苹果在NSURLConnection中添加了sendAsynchronousRequest:queue:completionHandler:方法(OSX 10.7 iOS 5),因此如果是简单的task,就不必在使用delegate了。

    在OS X 10.9 和 iOS 7中,苹果引入了一个非常modern的API:NSURLSession,其中使用block当做消息传递机制(NSURLSession仍然有一个delegate,不过是用于别的目的)。

    Target-Action

    Target-Action用的最明显的一个地方就是button(按钮)。button除了需要发送一个click事件以外,并不需要再发送别的信息了。所以Target-Action在用户界面事件传递过程中,是最佳的选择。

    这里写图片描述

    如果taget已经明确指定了,那么action消息回直接发送给指定的对象。如果taget是nil,action消息会以冒泡的方式在响应链中查找一个能够处理该消息的对象。此时,我们拥有一种完全解耦的消息传递机制——发送者不需要知道接收者,以及其它一些信息。

    Target-Action非常适用于用户界面中的事件。目前也没有其它合适的消息传递机制能够提供同样的功能。虽然notification最接近这种在发送者和接收者解耦关系,但是target-action可以用于响应链(responder chain)——只有一个对象获得action并作出响应,并且action可以在响应链中传递,直到遇到能够响应该action的对象。

    小结

    首次接触这些机制,感觉它们都能用于两个对象间的消息传递。但是仔细琢磨一番,会发现它们各自有其需求和功能。

    文中给出的决策流程图可以为我们选择使用何种机制提供参考,不过图中给出的方案并不是最终答案,好多地方还需要亲自去实践。

    展开全文
  • IOS消息传递机制

    2015-06-08 19:46:08
    每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果Framework中如何使用,同时,还...


    注1:本文由破船译自Communication Patterns

    本文目录如下所示:

    1. 可用的机制
    2. 做出正确的选择
    3. Framework示例
    4. 小结

    每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好的完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果的Framework中如何使用,同时,还介绍了一些最佳实践建议,告诉你什么时机该选择使用什么机制。

    虽然这一期的主题是关于Foundation Framework的,不过本文中还介绍了一些超出Foundation Framework(KVO和Notification)范围的一些消息传递机制,另外还介绍了delegation,block和target-action。

    大多数情况下,消息传递该使用什么机制,是很明确的了,当然了,在某些情况下该使用什么机制并没有明确的答案,需要你亲自去尝试一下。

    本文中,会经常提及接收者[recipient]发送者[sender]。在消息传递机制中具体是什么意思,我们可以通过一个示例来解释:一个table view是发送者,而它的delegate就是接收者。Core Data managed object context是notification的发送者,而获取这些notification的主体则是接收者。一个滑块(slider)是action消息的发送者,而在代码里面对应着实现这个action的responder就是接收者。对象中的某个属性支持KVO,那么谁修改这个值,谁就是发送者,对应的观察者(observer)则是接收者。

    可用的机制

    首先我们来看看每种机制的具体特点。在下一节中,我会结合一个流程图来介绍如何在具体情况下,选择正确的消息传递机制。最后,将介绍一些来自苹果Framework中的示例,并会解释在某种确定情况下为什么要选择固定的机制。

    KVO

    KVO提供了这样一种机制:当对象中的某个属性值发生了改变,可以对这些值的观察者做出通知。KVO的实现包含在Foundation里面,基于Foundation构建的许多Framework对KVO都有所依赖。要想了解更多关于如何使用KVO,可以阅读本期由Daniel写的的KVO和KVC文章。

    如果对某个对象中值的改变情况感兴趣,那么可以使用KVO消息传递机制。这里有两个要求,首先,接收者(会接收到值发生改变的消息)必须知道发送者(值将发生改变的那个对象)。另外,接收者同样还需要知道发送者的生命周期,因为在销毁发送者对象之前,需要取消观察者的注册。如果这两个要求都满足了,消息传递过程中可以是1对多(多个观察者可以注册某个对象中的值)。

    如果计划在Core Data对象上使用KVO,需要知道这跟一般的KVO使用方法有点不同。那就是必须结合Core Data的故障机制(faulting mechanism),一旦core data出现了故障,它将会触发其属性对应的观察者(即使这些属性值没有发生改变)。

    Notification

    在不相关的两部分代码中要想进行消息传递,通知(notifacation)是非常好的一种机制,它可以对消息进行广播。特别是想要传递丰富的信息,并且不一定指望有谁对此消息关心。

    通知可以用来发送任意的消息,甚至包含一个userInfo字典,或者是NSNotifacation的一个子类。通知的独特之处就在于发送者和接收者双方并不需要相互知道。这样就可以在非常松耦合的模块间进行消息的传递。记住,这种消息传递机制是单向的,作为接收者是不可以回复消息的。

    delegation

    在苹果的Framework中,delegation模式被广泛的只用着。delegation允许我们定制某个对象的行为,并且可以收到某些确定的事件。为了使用delegation模式,消息的发送者需要知道消息的接收者(delegate),反过来就不用了。这里的发送者和接收者是比较松耦合的,因为发送者只知道它的delegate是遵循某个特定的协议。

    delegate协议可以定义任意的方法,因此你可以准确的定义出你所需要的类型。你可以用函数参数的形式来处理消息内容,delegate还可以通过返回值的形式给发送者做出回应。如果只需要在相对接近的两个模块之间进行消息传递,那么Delegation是一种非常灵活和直接方式。

    不过,过渡使用delegation也有一定的风险,如果两个对象的耦合程度比较紧密,相互之间不能独立存在,那么此时就没有必要使用delegate协议了,针对这种情况,对象之间可以知道相互间的类型,进而直接进行消息传递。例如UICollectionViewLayout和NSURLSessionConfiguration。

    block

    Block相对来说,是一种比较新的技术,它首次出现是在OS X 10.6和iOS 4中。一般情况下,block可以满足用delegation实现的消息传递机制。不过这两种机制都有各自的需求和优势。

    当不考虑使用block时,一般主要是考虑到block极易引起retain环。如果发送者需要reatain block,而又不能确保这个引用什么时候被nil,这样就会发生潜在的retain环。

    假设我们想要实现一个table view,使用block替代delegate,来当做selection的回调,如下:

    <span class="line-number" style="margin: 0px; padding: 0px;">1</span>
    <span class="line-number" style="margin: 0px; padding: 0px;">2</span>
    <span class="line-number" style="margin: 0px; padding: 0px;">3</span>
    
    self.myTableView.selectionHandler = ^void(NSIndexPath *selectedIndexPath) {
        // handle selection ...
    };
    

    上面代码的问题在于self retain了table view,而table view为了之后能够使用block,进而 retain了block。而table view又不能把这个引用nil掉,因为它不知道什么时候不在需要这个block了。如果我们保证不了可以打破这个retain环,而我们又需要retain发送者,此时block不是好的选择。

    NSOperation就可以很好的使用block,因为它能再某个时机打破retain环:

    <span class="line-number" style="margin: 0px; padding: 0px;">1</span>
    <span class="line-number" style="margin: 0px; padding: 0px;">2</span>
    <span class="line-number" style="margin: 0px; padding: 0px;">3</span>
    <span class="line-number" style="margin: 0px; padding: 0px;">4</span>
    <span class="line-number" style="margin: 0px; padding: 0px;">5</span>
    <span class="line-number" style="margin: 0px; padding: 0px;">6</span>
    
    self.queue = [[NSOperationQueue alloc] init];
    MyOperation *operation = [[MyOperation alloc] init];
    operation.completionBlock = ^{
        [self finishedOperation];
    };
    [self.queue addOperation:operation];
    

    乍一看这似乎是一个retain环:self retain了queue,queue retain了operation,而operation retain了completion block,而completion blockretain了self。不过,在这里,将operation添加到queue时,会使operation在某个时机被执行,然后从queue中remove掉(如果没有被执行,就会有大问题了)。一单queue移除了operation之后,retain环就被打破了。

    再来一个示例:这里实现了一个视频编码器的类,里面有一个名为encodeWithCompletionHandler:的方法。为了避免出现retain环,我们需要确保编码器这个对象能够在某个时机nil掉其对block的引用。其内部代码如下所示:

    @interface Encoder ()
    @property (nonatomic, copy) void (^completionHandler)();
    @end
    
    @implementation Encoder
    
    - (void)encodeWithCompletionHandler:(void (^)())handler
    {
        self.completionHandler = handler;
        // do the asynchronous processing...
    }
    
    // This one will be called once the job is done
    - (void)finishedEncoding
    {
        self.completionHandler();
        self.completionHandler = nil; // <- Don't forget this!
    }
    
    @end

    </pre></td><td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; width: 905px;"><pre style="white-space: pre-wrap; word-wrap: break-word; margin-top: 0px; margin-bottom: 0px; padding: 0px; border: none; font-family: Menlo, Monaco, 'Andale Mono', 'lucida console', 'Courier New', monospace; line-height: 1.5; overflow-x: auto;"><code class="objc" style="margin: 0px; padding: 0px; border: none; font-family: Menlo, Monaco, 'Andale Mono', 'lucida console', 'Courier New', monospace; font-size: 0.9em;"><span class="line" style="margin: 0px; padding: 0px;">
    </span></code>

    在上面的代码中,一旦编码任务完成,就会调用complietion block,进而把引用nil掉。

    如果我们发送的消息属于一次性的(具体到某个方法的调用),由于这样可以打破潜在的retain环,那么使用block是非常不错的选择。另外,如果为了让代码可读性更强,更有连贯性,那最好是使用block了。根据这个思路,block经常可以用于completion handler、error handler等。

    Target-Action

    Target-Action主要被用于响应用户界面事件时所需要传递的消息中。iOS中的UIControl和Mac中的NSControl/NSCell都支持这种机制。Target-Action在消息的发送者和接收者之间建立了一个非常松散耦合。消息的接收者不知道发送者,甚至消息的发送者不需要预先知道消息的接收者。如果target是nil,action会在响应链(responder chain)中被传递,知道找到某个能够响应该aciton的对象。在iOS中,每个控件都能关联多个target-action。

    基于target-action消息传递的机制有一个局限就是发送的消息不能携带自定义的payload。在Mac的action方法中,接收者总是被放在第一个参数中。而在iOS中,可以选择性的将发送者和和触发action的事件作为参数。除此之外,没有别的办法可以对发送action消息内容做控制。

    做出正确的选择

    根据上面讨论的结果,这里我画了一个流程图,来帮助我们何时使用什么消息传递机制做出更好的决定。忠告:流程图中的建议并非最终的答案;可能还有别的选项依然能实现目的。只不过大多数情况下此图可以引导你做出正确的决定。

    上图中,还有一些细节需要做更近一步的解释:

    上图中的有个盒子这样说到:sender is KVO compliant(发送者支持compliant)。这不仅以意味着当值发生改变时,发送者会发送KVO通知,并且观察者还需要知道发送者的生命周期。如果发送者被存储在一个weak属性中,那么发送者有可能被nil掉,进而引起观察者发生leak。

    另外底部的一个盒子说到:message is direct response to method call(消息直接在方法的调用代码中响应)。也就是说处理消息的代码跟方法的调用代码处于相同的地方。

    最后,在左下角,处于一个决策问题的判断状态:sender can guarantee to nil out reference to block?(发送者能够确保nil掉到block的引用吗?),这实际上涉及到之前我们讨论到基于block 的APIs已经潜在的retain环。使用block时,如果发送者不能保证在某个实际能够把对block的引用nil掉,那么将会遇到retain环的问题。

    Framework示例

    本节我们通过一些来自苹果Framework的示例,来看看在实际使用某种机制之前,苹果是处于何种原因做出选择的。

    KVO

    NSOperationQueue就是lion给了KVO来观察队列中operation状态属性的改变情况(isFinished, isExecuting, isCancelled)。当状态发生了改变,队列会受到一个KVO通知。为什么operationqueue要是用KVO呢?

    消息的接收者(operation queue)明确的知道发送者(opertation),以及通过retain来控制operation的生命周期。另外,在这种情况下,只需要单向的消息传递机制。当然,如果这样考虑:如果operation queue只关心operation值的改变情况,可能还不足以说服大家使用KVO。但是我们至少可以这样理解:什么机制可以对值的改变进行消息传递呢。

    当然KVO也不是唯一的选择。我们可以这样设计:operation queue作为operation的delegate,operation会调用类似operationDidFinish: 或 operationDidBeginExecuting: 这样的方法,来将它的state传递给queue。这样一来,就不太方便了,因为operation需要将其state属性保存下来,一遍调用这些delegate方法。另外,由于queue不能主动获取state信息,所以queue也必须保存着所有operation的state。

    Notifications

    Core Data使用notification来传递事件(例如一个managed object context内部的改变——NSManagedObjectContextDidChangeNotification)。

    change notification是由managed object context发出的,所以我们不能确定消息的接收者一定知道发送者。如果消息并不是一个UI事件,而有可能多个接收者对该消息感兴趣,并且消息的传递属于单向(one-way communication channel),那么notification是最佳选择。

    Delegation

    Table view的delegate有多种功能,从accessory view的管理,到屏幕中cell显示的跟踪,都与delegate的功劳。例如,我们来看看 tableView:didSelectRowAtIndexPath: 方法。为什么要以delegate调用的方式来实现?而又为啥不用target-action方式?

    正如我们在流程图中看到的一样,使用target-action时,不能传递自定义的数据。而在选中table view的某个cell时,collection view不仅仅需要告诉我们有一个cell被选中了,还需要告诉我们是哪个cell被选中了(index path)。按照这样的一种思路,那么从流程图中可以看到应该使用delegation机制。

    如果消息传递中,不包含选中cell的index path,而是每当选中项改变时,我们主动去table view中获取到选中cell的相关信息,会怎样呢?其实这会非常的麻烦,因为这样一来,我们就必须记住当前选中项相关数据,以便获知被选中的cell。

    同理,虽然我们也可以通过观察table view中选中项的index paths属性值,当该值发生改变时,获得一个选中项改变的通知。不过,我们会遇到与上面同样的问题:不做任何记录的话,我们如何获知被选中项的相关信息。

    Blocks

    关于block的介绍,我们来看看[NSURLSession dataTaskWithURL:completionHandler:]吧。从URL loading system返回到调用者,这个过程具体是如何传递消息的呢?首先,作为这个API的调用者,我们知道消息的发送者,但是我们并没有retain这个发送者。另外,这属于单向消息传递——直接调用dataTaskWithURL:方法。如果按照这样的思路对照着流程图,我们会发现应该使用基于block消息传递的机制。

    还有其它可选的机制吗?当然有了,苹果自己的NSURLConnection就是最好的例子。NSURLConnection在block问世之前就已经存在了,所以它并没有利用block进行消息传递,而是使用delegation机制。当block出现之后,苹果在NSURLConnection中添加了sendAsynchronousRequest:queue:completionHandler:方法(OSX 10.7 iOS 5),因此如果是简单的task,就不必在使用delegate了。

    在OS X 10.9 和 iOS 7中,苹果引入了一个非常modern的API:NSURLSession,其中使用block当做消息传递机制(NSURLSession仍然有一个delegate,不过是用于别的目的)。

    Target-Action

    Target-Action用的最明显的一个地方就是button(按钮)。button除了需要发送一个click事件以外,并不需要再发送别的信息了。所以Target-Action在用户界面事件传递过程中,是最佳的选择。

    如果taget已经明确指定了,那么action消息回直接发送给指定的对象。如果taget是nil,action消息会以冒泡的方式在响应链中查找一个能够处理该消息的对象。此时,我们拥有一种完全解耦的消息传递机制——发送者不需要知道接收者,以及其它一些信息。

    Target-Action非常适用于用户界面中的事件。目前也没有其它合适的消息传递机制能够提供同样的功能。虽然notification最接近这种在发送者和接收者解耦关系,但是target-action可以用于响应链(responder chain)——只有一个对象获得action并作出响应,并且action可以在响应链中传递,直到遇到能够响应该action的对象。

    小结

    首次接触这些机制,感觉它们都能用于两个对象间的消息传递。但是仔细琢磨一番,会发现它们各自有其需求和功能。

    文中给出的决策流程图可以为我们选择使用何种机制提供参考,不过图中给出的方案并不是最终答案,好多地方还需要亲自去实践。


    展开全文
  • 消息传递机制总结

    2013-12-30 09:32:10
    每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果Framework中如何使用,同时,还...
    本文目录如下所示:
    可用的机制
    做出正确的选择
    Framework示例
     
    小结
    每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好的完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果的Framework中如何使用,同时,还介绍了一些最佳实践建议,告诉你什么时机该选择使用什么机制。
     
    虽然这一期的主题是关于Foundation Framework的,不过本文中还介绍了一些超出Foundation Framework(KVO和Notification)范围的一些消息传递机制,另外还介绍了delegation,block和target-action。
     
    大多数情况下,消息传递该使用什么机制,是很明确的了,当然了,在某些情况下该使用什么机制并没有明确的答案,需要你亲自去尝试一下。
     
    本文中,会经常提及接收者[recipient]和发送者[sender]。在消息传递机制中具体是什么意思,我们可以通过一个示例来解释:一个table view是发送者,而它的delegate就是接收者。Core Data managed object context是notification的发送者,而获取这些notification的主体则是接收者。一个滑块(slider)是action消息的发送者,而在代码里面对应着实现这个action的responder就是接收者。对象中的某个属性支持KVO,那么谁修改这个值,谁就是发送者,对应的观察者(observer)则是接收者。
     
    可用的机制
    首先我们来看看每种机制的具体特点。在下一节中,我会结合一个流程图来介绍如何在具体情况下,选择正确的消息传递机制。最后,将介绍一些来自苹果Framework中的示例,并会解释在某种确定情况下为什么要选择固定的机制。
     
    KVO
    KVO提供了这样一种机制:当对象中的某个属性值发生了改变,可以对这些值的观察者做出通知。KVO的实现包含在Foundation里面,基于Foundation构建的许多Framework对KVO都有所依赖。要想了解更多关于如何使用KVO,可以阅读本期由Daniel写的的KVO和KVC文章。
     
    如果对某个对象中值的改变情况感兴趣,那么可以使用KVO消息传递机制。这里有两个要求,首先,接收者(会接收到值发生改变的消息)必须知道发送者(值将发生改变的那个对象)。另外,接收者同样还需要知道发送者的生命周期,因为在销毁发送者对象之前,需要取消观察者的注册。如果这两个要求都满足了,消息传递过程中可以是1对多(多个观察者可以注册某个对象中的值)。
     
    如果计划在Core Data对象上使用KVO,需要知道这跟一般的KVO使用方法有点不同。那就是必须结合Core Data的故障机制(faulting mechanism),一旦core data出现了故障,它将会触发其属性对应的观察者(即使这些属性值没有发生改变)。
     
    Notification
    在不相关的两部分代码中要想进行消息传递,通知(notifacation)是非常好的一种机制,它可以对消息进行广播。特别是想要传递丰富的信息,并且不一定指望有谁对此消息关心。
     
    通知可以用来发送任意的消息,甚至包含一个userInfo字典,或者是NSNotifacation的一个子类。通知的独特之处就在于发送者和接收者双方并不需要相互知道。这样就可以在非常松耦合的模块间进行消息的传递。记住,这种消息传递机制是单向的,作为接收者是不可以回复消息的。
     
    delegation
    在苹果的Framework中,delegation模式被广泛的只用着。delegation允许我们定制某个对象的行为,并且可以收到某些确定的事件。为了使用delegation模式,消息的发送者需要知道消息的接收者(delegate),反过来就不用了。这里的发送者和接收者是比较松耦合的,因为发送者只知道它的delegate是遵循某个特定的协议。
     
    delegate协议可以定义任意的方法,因此你可以准确的定义出你所需要的类型。你可以用函数参数的形式来处理消息内容,delegate还可以通过返回值的形式给发送者做出回应。如果只需要在相对接近的两个模块之间进行消息传递,那么Delegation是一种非常灵活和直接方式。
     
    不过,过渡使用delegation也有一定的风险,如果两个对象的耦合程度比较紧密,相互之间不能独立存在,那么此时就没有必要使用delegate协议了,针对这种情况,对象之间可以知道相互间的类型,进而直接进行消息传递。例如UICollectionViewLayout和NSURLSessionConfiguration。
     
    block
    Block相对来说,是一种比较新的技术,它首次出现是在OS X 10.6和iOS 4中。一般情况下,block可以满足用delegation实现的消息传递机制。不过这两种机制都有各自的需求和优势。
    当不考虑使用block时,一般主要是考虑到block极易引起retain环。如果发送者需要reatain block,而又不能确保这个引用什么时候被nil,这样就会发生潜在的retain环。
    假设我们想要实现一个table view,使用block替代delegate,来当做selection的回调,如下:
     
    1. self.myTableView.selectionHandler = ^void(NSIndexPath *selectedIndexPath) { 
    2.     // handle selection ... 
    3. }; 
    上面代码的问题在于self retain了table view,而table view为了之后能够使用block,进而 retain了block。而table view又不能把这个引用nil掉,因为它不知道什么时候不在需要这个block了。如果我们保证不了可以打破这个retain环,而我们又需要retain发送者,此时block不是好的选择。
     
    NSOperation就可以很好的使用block,因为它能再某个时机打破retain环:
     
    1. self.queue = [[NSOperationQueue alloc] init]; 
    2. MyOperation *operation = [[MyOperation alloc] init]; 
    3. operation.completionBlock = ^{ 
    4.     [self finishedOperation]; 
    5. }; 
    6. [self.queue addOperation:operation]; 
     
    乍一看这似乎是一个retain环:self retain了queue,queue retain了operation,而operation retain了completion block,而completion blockretain了self。不过,在这里,将operation添加到queue时,会使operation在某个时机被执行,然后从queue中remove掉(如果没有被执行,就会有大问题了)。一单queue移除了operation之后,retain环就被打破了。
     
    再来一个示例:这里实现了一个视频编码器的类,里面有一个名为encodeWithCompletionHandler:的方法。为了避免出现retain环,我们需要确保编码器这个对象能够在某个时机nil掉其对block的引用。其内部代码如下所示:
     
    1. @interface Encoder () 
    2. @property (nonatomic, copy) void (^completionHandler)(); 
    3. @end 
    4.  
    5. @implementation Encoder 
    6.  
    7. - (void)encodeWithCompletionHandler:(void (^)())handler 
    8.     self.completionHandler = handler; 
    9.     // do the asynchronous processing... 
    10.  
    11. // This one will be called once the job is done 
    12. - (void)finishedEncoding 
    13.     self.completionHandler(); 
    14.     self.completionHandler = nil; // <- Don't forget this! 
    15.  
    16. @end 
     
    在上面的代码中,一旦编码任务完成,就会调用complietion block,进而把引用nil掉。
     
    如果我们发送的消息属于一次性的(具体到某个方法的调用),由于这样可以打破潜在的retain环,那么使用block是非常不错的选择。另外,如果为了让代码可读性更强,更有连贯性,那最好是使用block了。根据这个思路,block经常可以用于completion handler、error handler等。
     
    Target-Action
    Target-Action主要被用于响应用户界面事件时所需要传递的消息中。iOS中的UIControl和Mac中的NSControl/NSCell都支持这种机制。Target-Action在消息的发送者和接收者之间建立了一个非常松散耦合。消息的接收者不知道发送者,甚至消息的发送者不需要预先知道消息的接收者。如果target是nil,action会在响应链(responder chain)中被传递,知道找到某个能够响应该aciton的对象。在iOS中,每个控件都能关联多个target-action。
     
    基于target-action消息传递的机制有一个局限就是发送的消息不能携带自定义的payload。在Mac的action方法中,接收者总是被放在第一个参数中。而在iOS中,可以选择性的将发送者和和触发action的事件作为参数。除此之外,没有别的办法可以对发送action消息内容做控制。
     
    做出正确的选择
    根据上面讨论的结果,这里我画了一个流程图,来帮助我们何时使用什么消息传递机制做出更好的决定。忠告:流程图中的建议并非最终的答案;可能还有别的选项依然能实现目的。只不过大多数情况下此图可以引导你做出正确的决定。
    上图中,还有一些细节需要做更近一步的解释:
     
    上图中的有个盒子这样说到:sender is KVO compliant(发送者支持compliant)。这不仅以意味着当值发生改变时,发送者会发送KVO通知,并且观察者还需要知道发送者的生命周期。如果发送者被存储在一个weak属性中,那么发送者有可能被nil掉,进而引起观察者发生leak。
     
    另外底部的一个盒子说到:message is direct response to method call(消息直接在方法的调用代码中响应)。也就是说处理消息的代码跟方法的调用代码处于相同的地方。
     
    最后,在左下角,处于一个决策问题的判断状态:sender can guarantee to nil out reference to block?(发送者能够确保nil掉到block的引用吗?),这实际上涉及到之前我们讨论到基于block 的APIs已经潜在的retain环。使用block时,如果发送者不能保证在某个实际能够把对block的引用nil掉,那么将会遇到retain环的问题。
     
    Framework示例
    本节我们通过一些来自苹果Framework的示例,来看看在实际使用某种机制之前,苹果是处于何种原因做出选择的。
     
    KVO
    NSOperationQueue就是lion给了KVO来观察队列中operation状态属性的改变情况(isFinished, isExecuting, isCancelled)。当状态发生了改变,队列会受到一个KVO通知。为什么operationqueue要是用KVO呢?
     
    消息的接收者(operation queue)明确的知道发送者(opertation),以及通过retain来控制operation的生命周期。另外,在这种情况下,只需要单向的消息传递机制。当然,如果这样考虑:如果operation queue只关心operation值的改变情况,可能还不足以说服大家使用KVO。但是我们至少可以这样理解:什么机制可以对值的改变进行消息传递呢。
    当然KVO也不是唯一的选择。我们可以这样设计:operation queue作为operation的delegate,operation会调用类似operationDidFinish: 或 operationDidBeginExecuting: 这样的方法,来将它的state传递给queue。这样一来,就不太方便了,因为operation需要将其state属性保存下来,一遍调用这些delegate方法。另外,由于queue不能主动获取state信息,所以queue也必须保存着所有operation的state。
     
    Notifications
    Core Data使用notification来传递事件(例如一个managed object context内部的改变——NSManagedObjectContextDidChangeNotification)。
     
    change notification是由managed object context发出的,所以我们不能确定消息的接收者一定知道发送者。如果消息并不是一个UI事件,而有可能多个接收者对该消息感兴趣,并且消息的传递属于单向(one-way communication channel),那么notification是最佳选择。
     
    Delegation
    Table view的delegate有多种功能,从accessory view的管理,到屏幕中cell显示的跟踪,都与delegate的功劳。例如,我们来看看 tableView:didSelectRowAtIndexPath: 方法。为什么要以delegate调用的方式来实现?而又为啥不用target-action方式?
     
    正如我们在流程图中看到的一样,使用target-action时,不能传递自定义的数据。而在选中table view的某个cell时,collection view不仅仅需要告诉我们有一个cell被选中了,还需要告诉我们是哪个cell被选中了(index path)。按照这样的一种思路,那么从流程图中可以看到应该使用delegation机制。
     
    如果消息传递中,不包含选中cell的index path,而是每当选中项改变时,我们主动去table view中获取到选中cell的相关信息,会怎样呢?其实这会非常的麻烦,因为这样一来,我们就必须记住当前选中项相关数据,以便获知被选中的cell。
     
    同理,虽然我们也可以通过观察table view中选中项的index paths属性值,当该值发生改变时,获得一个选中项改变的通知。不过,我们会遇到与上面同样的问题:不做任何记录的话,我们如何获知被选中项的相关信息。
     
    Blocks
    关于block的介绍,我们来看看[NSURLSession dataTaskWithURL:completionHandler:]吧。从URL loading system返回到调用者,这个过程具体是如何传递消息的呢?首先,作为这个API的调用者,我们知道消息的发送者,但是我们并没有retain这个发送者。另外,这属于单向消息传递——直接调用dataTaskWithURL:方法。如果按照这样的思路对照着流程图,我们会发现应该使用基于block消息传递的机制。
    还有其它可选的机制吗?当然有了,苹果自己的NSURLConnection就是最好的例子。NSURLConnection在block问世之前就已经存在了,所以它并没有利用block进行消息传递,而是使用delegation机制。当block出现之后,苹果在NSURLConnection中添加了sendAsynchronousRequest:queue:completionHandler:方法(OSX 10.7 iOS 5),因此如果是简单的task,就不必在使用delegate了。
     
    在OS X 10.9 和 iOS 7中,苹果引入了一个非常modern的API:NSURLSession,其中使用block当做消息传递机制(NSURLSession仍然有一个delegate,不过是用于别的目的)。
     
    Target-Action
    Target-Action用的最明显的一个地方就是button(按钮)。button除了需要发送一个click事件以外,并不需要再发送别的信息了。所以Target-Action在用户界面事件传递过程中,是最佳的选择。
    如果taget已经明确指定了,那么action消息回直接发送给指定的对象。如果taget是nil,action消息会以冒泡的方式在响应链中查找一个能够处理该消息的对象。此时,我们拥有一种完全解耦的消息传递机制——发送者不需要知道接收者,以及其它一些信息。
     
    Target-Action非常适用于用户界面中的事件。目前也没有其它合适的消息传递机制能够提供同样的功能。虽然notification最接近这种在发送者和接收者解耦关系,但是target-action可以用于响应链(responder chain)——只有一个对象获得action并作出响应,并且action可以在响应链中传递,直到遇到能够响应该action的对象。
     
    小结
    首次接触这些机制,感觉它们都能用于两个对象间的消息传递。但是仔细琢磨一番,会发现它们各自有其需求和功能。
     
    文中给出的决策流程图可以为我们选择使用何种机制提供参考,不过图中给出的方案并不是最终答案,好多地方还需要亲自去实践。
    展开全文
  • iOS 消息的传递机制

    千次阅读 2013-12-18 08:05:30
    转载原地址:... 注1:本文由破船译自Communication Patterns。 ...可用的机制做出正确的选择Framework示例小结 ...每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好的
  • 每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过 示例来介绍这些机制在苹果Framework中如何使用,同时,还...
  • 每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果Framework中如何使用,同时,还...
  • 1. Handler消息传递机制初步认识:什么是Handler?handler通俗一点讲就是用来在各个线程之间发送数据处理对象。在任何线程中,只要获得了另一个线程handler,则可以通过handler.sendMessage(message)方法向那个...
  • Objective-C中调用函数方法是“消息传递”,这个和普通函数调用区别是,你可以随时对一个对象传递任何消息,而不需要在编译时候声明这些方法。所以Objective-C可以在runtime时候传递人和消息。 在C++或...
  • 每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好完成任务,就需要进行消息传递。本文将介绍所有可用的消息传递机制,并通过示例来介绍这些机制在苹果Framework中如何使用,同时,还...
  • 1. Handler消息传递机制初步认识:什么是Handler? handler通俗一点讲就是用来在各个线程之间发送数据处理对象。在任何线程中,只要获得了另一个线程handler,则可以通过 handler.sendMessage(message)方法向...
  • 1. Handler消息传递机制初步认识:什么是Handler?  handler通俗一点讲就是用来在各个线程之间发送数据处理对象。在任何线程中,只要获得了另一个线程handler,则可以通过 handler.sendMessage(message)方法...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 560
精华内容 224
关键字:

对象之间消息传递的机制