观“编写高质量iOS与OC 代码的52个有效方法”有感(四)· 协议与分类

神器.png

23、通过委托与数据源协议进行对象间通信

  • 委托模式(Delegate pattern)
    定义一套接口,某对象若想接受另一个对象的委托,则需遵从此接口,以便成为其“委托对象”。而“另一个对象”则可以给其委托对象回传一些信息,也可以在发生相关事件时通知委托对象。
  • 委托模式一般用于反向传值。
    协议定义:
@protocol FirstViewControllerDelegate <NSObject>

//必选
@required
- (void)firstReturnAge:(NSString *)age;

//可选
@optional
- (void)firstReturnName:(NSString *)name;
- (void)firstReturnNPhone:(NSString *)phone;
- (void)firstReturnUSerID:(NSString *)userID;

@end
  • 协议一般分为 required (必选)和 optional (可选)
    例子:UITableView:顾名思义,必选的就是必须实现的方法,不实现程序就会报错。
@protocol UITableViewDataSource<NSObject>

@required

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)

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

@optional

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;              // Default is 1 if not implemented

- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;    // fixed font style. use custom view (UILabel) if you want something different
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
  • 有了协议之后,类就可以用一个属性存放其委托对象了。
    @property (nonatomic, weak) id <FirstViewControllerDelegate> delegate;
    这个属性定义为weak,而非strong,是因为两者之间必须为“非拥有关系”。
  • 委托方法的实现
if ([self.delegate respondsToSelector:@selector(firstReturnName:)]) {
        [self.delegate firstReturnName:@"哈哈"];
        [self.delegate firstReturnAge:@"18"];
        [self.navigationController popViewControllerAnimated:YES];
    }

可以发现:

  • 可选实现的方法,首先需要通过 respondsToSelector 来判断委托对象是否实现了相关方法。如果实现了就调用,没有实现就不执行任何操作。
  • 必选方法,上面我就和可选写在一起了,其实就是默认委托对象一定实现了相关方法。如果没实现就报错。
  • 如果相关方法多次调用,要考虑的优化。
    多次调用,那么每次都会去检测委托对象是否实现了方法。那么我们是不是可以把委托对象是否能够响应相关协议方法这一信息缓存起来?加快执行速度!
    我们可以用 位段
    位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
struct {
        unsigned int didFirstReturnName         :1;
        unsigned int didFirstReturnNPhone       :1;
        unsigned int didFirstReturnUSerID       :1;
    } _delegateFlags;

这个结构体用来缓存委托对象是否能够响应方法。实现缓存功能所用的代码可以写在 delegate 属性所对应的设置方法里:

- (void)setDelegate:(id<FirstViewControllerDelegate>)delegate {
  _delegate = delegate;
  _delegateFlags.didFirstReturnName = [delegate respondsToSelector:@selector(firstReturnName:)];
  _delegateFlags.didFirstReturnNPhone = [delegate respondsToSelector:@selector(firstReturnNPhone:)];
  _delegateFlags.didFirstReturnUSerID = [delegate respondsToSelector:@selector(firstReturnUSerID:)];
}

每次调用 delegate 的相关方法之前,就不用检测委托对象是否实现了相关方法,而是直接查询结构体里的标志:

if (_delegateFlags.didFirstReturnName) {
        [self.delegate firstReturnName:@"哈哈"];
        [self.navigationController popViewControllerAnimated:YES];
    }

24、将类的实现代码分散到便于管理的数个分类之中

  • 类中经常容易填满各种方法,而这些方法的代码则全部堆在一个巨大的实现文件里,可以通过“Objective-C”的“分类”机制,把类代码按逻辑划入几个分区中,对开发与调试都有好处。
Xcode创建分类.png

25、总是为第三方类的分类名称加前缀

  • 因为分类机制通常用于向无源码的既有类中新增功能。这个特性极为强大,使用时很容易忽视其中可能产生的问题:
    如果分类中的方法覆盖原来的那一份实现代码,那么会以最后一个分类为准。
  • 所以我们一般的做法:以命名空间来区别各个分类的名称和其所定义的方法。添加前缀。

26、勿在分类中声明属性

  • 尽管在技术上说,分类也可以声明属性,但是这种做法还是尽量避免。原因:
    除了“class-continuation分类(参看27条)”之外,其他分类都无法向类中新增实例变量,因此,它们无法把实现属性所需的实例变量合成出来。
  • 把封装数据所用的全部属性都定义在主接口里。
  • 在除了“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性。

27、使用“class-contunuation分类”隐藏实现细节

  • 类中经常会包含一些无须对外公布的方法及实例变量。怎么写呢?,这个特殊的“class-continuation分类”就派上用场了。
    “class-continuation分类”和普通分类不同,它必须定义在其所接续的那个类的实现文件里。其重要之处:这是唯一能声明实例变量的分类,而且此分类没有特定的实现文件,其中的方法都应该定义在类的主实现文件里。
@interface ZSCManager () {
    NSString *_name;
}
@end

这样做有什么好处呢?
公共接口里本来就能定义实例变量。不过,把它们定义在“class-continuation分类”或者“实现块”中可以将其隐藏起来,只供本类使用。

28、通过协议提供匿名对象

  • 协议定义了一系列方法,遵从此协议的对象应该实现它们(如果这些方法不是可选的,那么就必须实现)。
    我们可以用协议把自己所写的API之中的实现细节隐藏起来,将返回的对象设计为遵从此协议的纯 id 类型。这样,想要隐藏的类名就不会出现在API之中了。若是接口背后有很多个不同的实现类,而你又不想指明具体使用哪个类,就可以考虑用这个方法。
    因为有时候这些类可能会变,有时候它们无法容纳于标准的类继承体系中,因而不能以某个公共基类来统一表示。
    例子:
#import <Foundation/Foundation.h>

@interface ZSCShare : NSObject

+ (instancetype)shareInstance;
/**通过协议提供匿名对象,返回的具体不知道是什么类型,我们不关注,只要是分享的就行**/
- (id)createShareWithName:(NSString *)name;

@end

//.m的实现
#import "ZSCShare.h"

@implementation ZSCShare

static id instance;
+ (instancetype)shareInstance {
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        instance = [[ZSCShare alloc] init];
    });
    return instance;
}
- (id)createShareWithName:(NSString *)name {

    return [NSString stringWithFormat:@"分享了 %@",name];
}

@end
  • 调用时候就会显示下面这样,达到了我们的目的。
协议匿名返回.png

接下来也将会继续整理。如果觉得有用请点个喜欢!

您的支持将是我继续写作的动力!谢谢。

观“编写高质量iOS与OC X代码的52个有效方法”有感(一)· 熟悉Objective-C
观“编写高质量iOS与OC X代码的52个有效方法”有感(二)· 对象、消息、运行时
观“编写高质量iOS与OC X代码的52个有效方法”有感(三)· 接口与API设计
观“编写高质量iOS与OC X代码的52个有效方法”有感(四)· 协议与分类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容