美文网首页首页投稿(暂停使用,暂停投稿)编程学习iOS Developer
《52个有效方法》笔记5——协议与分类的一些细节

《52个有效方法》笔记5——协议与分类的一些细节

作者: Wang66 | 来源:发表于2016-03-13 00:21 被阅读528次

    代理模式的两种细分

    代理模式也叫委托模式,我们在这统一一下称呼。A类要委托B类来做某事,则A叫“委托者”,因为A委托、托付B帮它做事;把B叫“代理者”,因为B帮助、代理A完成该事。
    在代理模式里,代理者需要实现定义有关该“委托之事”规范的协议,说起协议。可以细分为“普通代理协议”和“数据源协议”。这个在UITableVIew中体现得很明显。
    ** UITableViewDelegate协议就是普通的代理协议。**
    总得来说,在下面这个方法中,** 数据是从“委托者”(UITableView)通过参数流向“代理者”(一般为ViewController)的。**

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
    

    ** 而UITableViewDataSource协议就是所谓的“数据源协议”。**
    顾名思义,它为“委托者”(UITableView)提供数据。总得来说,** 它的数据是从“代理者”(当前ViewController)以返回值的形式流向“委托者”(UITableView)的。**

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    

    代理模式里“普通代理协议”和“数据源协议”有以上数据流向不同的区别,望细辨之。


    委托者的代理属性

    // YWAlertView.h
    
    #import <UIKit/UIKit.h>
    @class YWAlertView;
    @protocol YWAlertViewDelegate <NSObject>
    
    - (void)ywalertView:(YWAlertView *)alertView clickBtnIndex:(NSInteger)index;
    
    @end
    
    
    @interface YWAlertView : UIAlertView
    
    @property (nonatomic, weak)id<YWAlertViewDelegate>      delegate;
    
    @end
    

    我们在自定义的YWAlertView中定义了它的代理者的属性delegate来保存、持有代理者。看上面的代码,定义委托者的代理者属性要注意两点:

    • 内存管理语义要使用weak。因为此时委托者YWAlertView持有了代理者delegate,而在代理者类ViewController中,一般也要持有YWAlertView对象,这样的话它们两者互相持有,互相保留,则会形成保留坏,互不相让,都不释放。这样就造成了内存泄露。
    • 委托者的代理属性的类型是id<YWAlertViewDelegate>这在OC中叫 ** 匿名对象 ** ,没有指定它的具体得是什么类,一定得是什么类,它的语义是,遵从YWAlertViewDelegate协议的任何对象。
      ** 注意:** OC中的匿名对象和概念和其他编程语言中的有所不同,其他编程的匿名对象一般指以内联方式所创建出来的无名类。要注意区分,以免混淆。

    细节优化

    若协议中的代理是可选实现的,则我们在委托类中调用代理方法时,则需要判断代理者是否已经实现该协议里方法,判断代理者能否响应此选择子。
    我们知道有些方法在执行过程中多次调用的,比如NSURLConnection网络下载返回数据的代理方法是多次调用,网络数据是一段一段下载而来的。又比如下面UITextFieldDelegate里的方法,也是多次调用的。

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;   
    

    像下面我们自定义的YWTextfield,它是基于UITextfield而建的,在YWTextField中UITextfieldDelegate的值发生改变时就调用我们协议定义的方法,所以该协议方法会被调用多次。

    // YWTextfield.h
    
    #import <UIKit/UIKit.h>
    @class YWTextfield;
    @protocol YWTextfieldDelegate <NSObject>
    
    - (void)ywtextfield:(YWTextfield *)textfield textValueIsChanging:(NSString *)text;
    
    @end
    
    @interface YWTextfield : UITextField
    
    @property (nonatomic, weak)id<YWTextfieldDelegate>      delegate;
    
    @end
    
    // YWTextfield.m
    
    #import "YWTextfield.h"
    
    @interface YWTextfield ()<UITextFieldDelegate>
    
    @end
    
    @implementation YWTextfield
    
    
    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        if([self.delegate respondsToSelector:@selector(ywtextfield:textValueIsChanging:)])
        {
            [self.delegate ywtextfield:self textValueIsChanging:string];
            return YES;
        }
        
        return NO;
    }
    
    @end
    

    其实每次调用前通过respondsToSelector来检测代理者能否响应,是完全没必要的,因为若代理者本身没变,它不太可能突然不能响应原本可以响应的选择子。所以,这儿有可优化的可能性。比较好的方案是 ** 把代理者能否响应该协议方法这一信息缓存起来,以后每次只通过该缓存判断该协议方法是否被实现。**
    像下面的例子,我们重写代理者属性ywDelegate的setter方法,在setter方法中通过``respondsToSelector```判断一次代理者是否实现该协议方法,然后把结果缓存在“段位”变量_ywdelegateFlags中。这样当我们设置某类为委托类的代理时,就已经开始判断它是否实现协议方法,且把结果缓存下来了。

    // YWTextfield.m
    
    #import "YWTextfield.h"
    
    // C中的“段位”
    struct
    {
        unsigned int ywtextfieldStartEdit   :1;
        unsigned int valueIsChanging        :1; 
    // 表示占用一个字节,因此可以代表1 or 0 两个值
    }_ywdelegateFlags;
    
    @interface YWTextfield ()<YWTextfieldDelegate>
    
    @end
    
    @implementation YWTextfield
    
    
    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        if(_ywdelegateFlags.valueIsChanging)
        {
            [self.ywDelegate ywtextfield:self textValueIsChanging:string];
            return YES;
        }
        
        return NO;
    }
    
    
    #pragma mark ---- setter
    // 重写ywDelegate属性的setter方法,在内把代理者是否实现该协议方法这一信息缓存下来
    - (void)setYwDelegate:(id<YWTextfieldDelegate>)ywDelegate
    {
        _ywDelegate = ywDelegate;
        _ywdelegateFlags.valueIsChanging = [ywDelegate respondsToSelector:@selector(ywtextfield:textValueIsChanging:)];
    }
    
    @end
    

    ** 注意:** 此处用来缓存代理者能否响应该选择子这一信息的数据结构叫“段位”。


    关于分类的一些细节

    OC有分类,当我们想给某类添加新方法时,我们可以为该类定义分类,将新方法定义在里面,而不需要定义一个继承于该类的子类。
    ** 这么说来,OC中为某类添加新方法有两种方案,其一为继承该类定义子类,在子类添加;其二则为添加分类,在分类中定义新方法。**
    当一个类很庞大时,我们可以根据功能模块的不同,而将方法分散到几个分类中,这样便于管理。比如,我们把可以像下面这样拆分Person类

    // Person主类
    
    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    @property (nonatomic, copy)NSString     *personId;
    @property (nonatomic, copy)NSString     *name;
    @property (nonatomic, strong)NSArray    *friends;
    
    @end
    
    // "工作"某块
    @interface Person (Work)
    
    - (void)goToCode;
    - (void)writeBlog;
    
    @end
    
    // "好友"模块
    @interface Person (FriendShip)
    
    - (void)addFriend:(Person *)person;
    - (void)removeFriend:(Person *)person;
    
    @end
    

    ** 注意:我们把“好友”这一模块拆分出来建立了FriendShip分类,指的是把有关好友的“方法们”拆分出来了,你可别把friends这个属性也拆分进FriendShip分类中。若把属性声明放在了分类中,编译时会给出警告。
    要记住: 把所有的成员变量好属性声明都应写在主类中。**

    相关文章

      网友评论

        本文标题:《52个有效方法》笔记5——协议与分类的一些细节

        本文链接:https://www.haomeiwen.com/subject/nxixlttx.html