热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

iOS创建对象的不同姿势详解

这篇文章主要介绍了iOS创建对象的不同姿势,文中介绍的很详细,对大家具有一定的参考价值,有需要的朋友们下面来一起学习学习吧。

前言

在写 iOS 代码的时候,怎么样去 new 一个新对象出来,都有一些讲究在里面。使用不同的姿势去创建对象,对后期维护所造成的影响会存在细微的差别。

init 创建

在之前一篇分析 iOS 代码耦合的文章中,提到过当我们给一个对象的 property 赋值的时候,通过 init 方法传入参数来初始化 property 会让我们的代码更可靠。

有些人在定义带 property 的 class 的时候,会这样定义:

@interface User : NSObject
@property (nonatomic, strong) NSNumber*     userID;
@end

使用的时候如下:

User* user = [[User alloc] init];
user.userID = @1000;

尤其是在定义 model 的时候,很容易写出这种,先 init,而后挨个给 property 赋值的代码。这种代码的问题在于 property 对于外部是可写的,property 处于随时可能变化的状态。之前不少篇文章中都强调过 immutable 的重要性,同样对于一个 class,我们也应该优先考虑设计成 immutable 的。

initWith 创建

如果将 property 都设置成 readonly 的,或者不暴露 property,property 的赋值都通过 initWith 的方式来初始化,就可以得到一个具备 immutable 的 class 定义了,具体到上面的例子代码如下:

//User.h
@interface User : NSObject
@property (nonatomic, strong, readonly) NSNumber* userID;
- (instancetype)initWithUserID:(NSNumber*)uid;
@end
 
//User.m
@implementation User
- (instancetype)initWithUserID:(NSNumber*)uid {
 self = [super init];
 if (!self) {
  return nil;
 }
 _userID = uid;
 return self;
}
@end

userID 在 .h 文件当中是 readonly 的,userID 只有一次被赋值的机会,即在 User 的 initWith 方法中。这种方式的好处是一旦 User 对象创建完毕之后,就处于 immutable 的状态,property 都是不可修改的,安全可靠。

Designated initializer

Apple 为了方便开发者使用 init 方法,引入了一种名为 designated initializer 的 pattern。主要用来管理当一个 class 拥有多个 property 需要赋值的场景。比如上面我们的 User 类:

@interface User : NSObject
@property (nonatomic, strong, readonly) NSNumber*     userID;
@property (nonatomic, strong, readonly) NSString*     userName;
@property (nonatomic, strong, readonly) NSString*     signature;
@end

有些场景需要初始化 userID 和 userName,而有些场景只需要初始化 userID 和 signature,所以我们需要提供多个 initWith 方法给不同的场景使用。为了管理 initWith 方法,Apple 将 init 方法分为两种类型:designated initializer 和 convenience initializer (又叫 secondary initializer) 。

designated initializer 只有一个,它会为 class 当中每个 property 都提供一个初始值,是最完整的 initWith 方法。convenience initializer 则可以有很多个,它可以选择只初始化部分的 property。convenience initializer 最后到会调用到 designated initializer,所以 designated initializer 也可以叫做 final initializer。

无论我们定义何种类型的 class,给 class 中的每个 property 都赋予一个初始值是个很好的习惯,可以避免掉一些意外的 bug 产生,这也是 designated initializer 的重要职责。

在实际的项目当中,一个 class 的 property 数目可能会随着业务的增长而增加,最后的结果就是会生成越来越多的 convenience initializer。上述的 User 类,如果是 3 个 property,极端的情况下最多可以有 7 个 init 方法。Peak君在阅读代码的时候,也确实看到过有些 class 定义了一连串整整齐齐摆放的 init 方法,代码虽然看着规范,但显得啰嗦,而且每次需要肉眼搜索适合的 init 方法。

其实我们还可以用另一种姿势来 init 我们的对象。

Builder pattern

最初是在学习 Android 的时候,发现这个 builder pattern 也可以用来构建对象,而且可以很好的解决 init 方法过多难以管理的问题。先来看下如何实现,顾名思义,builder pattern 使用另一个名为 builder 的类来创建我们的目标对象,还是上面的例子,代码如下:

//UserBuilder.h
@interface UserBuilder : NSObject
@property (nonatomic, strong, readonly) NSNumber*     userID;
@property (nonatomic, strong, readonly) NSString*     userName;
@property (nonatomic, strong, readonly) NSString*     signature;

- (UserBuilder*)userID:(NSNumber*)userID;
- (UserBuilder*)userName:(NSString*)userName;
- (UserBuilder*)signature:(NSString*)signature;
@end

//UserBuilder.m
@implementation UserBuilder
- (UserBuilder*)userID:(NSNumber*)userID {
 _userID = userID;
 return self;
}
- (UserBuilder*)userName:(NSString*)userName {
 _userName = userName;
 return self;
}
- (UserBuilder*)signature:(NSString*)signature {
 _signature = signature;
 return self;
}
@end

接下来 User 的 init 方法从 Builder 中获取 property 的初始值:

//User.h
@interface User : NSObject
@property (nonatomic, strong, readonly) NSNumber*     userID;
@property (nonatomic, strong, readonly) NSString*     userName;
@property (nonatomic, strong, readonly) NSString*     signature;

- (instancetype)initWithUserBuilder:(UserBuilder*)builder;
@end
 
//User.m
@implementation User
- (instancetype)initWithUserBuilder:(UserBuilder*)builder {
 self = [super init];
 if (!self) {
  return nil;
 }
 
 _userID = builder.userID;
 _userName = builder.userName;
 _signature = builder.signature;
 
 return self;
}
@end

如果要创建 User 对象,则按照这种方式:

UserBuilder* builder = [[[[UserBuilder new] userName:@"peak"] userID:@1000] signature:@"roll"];
User* user = [[User alloc] initWithUserBuilder:builder];

这样我们避免了书写多个 init 方法,同样 User 对象也是 immutable 的,也做到了只在 init 方法中做一次赋值操作,每个场景都可以按照自己的需求初始化部分 property,当然最后我们需要在 initWithUserBuilder 中为每一个 property 赋值, initWithUserBuilder 扮演的角色类似于 designated initializer。

追求代码美感的同学可能发现了, UserBuilder 的创建语法很丑陋,多个 [ ] 套嵌使用。为了让代码更好看一些,我们也可以使用 block 来创建:

User* user = [User userWithBlock:^(UserBuilder* builder) {
 builder.userName = @"peak";
 builder.userID = @1000;
 builder.signature = YES;
}];

builder pattern 在 Android 平台使用的比较多,我在 iOS 平台上鲜少有看到使用的场景。builder pattern 的不足之处也比较明显,需要另外定义一个 builder 类,多写一些代码(property 基本都重复写了一遍)。个人觉得,在 property 数量较多,初始化的场景也比较多的时候,在 iOS 上使用 builder pattern 也会是个不错的方案。

designated initializer vs builder pattern,这二者之间的不同其实很好的体现了语言本身的差异性。学习过 java 的同学就能明白,在 java 的世界中,一切都是可以被封装成对象的,使用 java 的时候,经常要定义各式各样的辅助类来完成某个任务,好处是封装度高,类职责划分粒度小,缺点是类太多,有时候会为了封装而封装,某些场景代码反而不够直观。

经读者反馈,原来这篇文章的主题已经被写过了。看过之后发现比我写的更全面,推荐大家阅读,传送门。

总结

以上就是这篇文章的全部内容了,本文简单梳理了下创建对象的不同姿势,希望对大家有些帮助。如果有疑问大家可以留言交流。


推荐阅读
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • CSS 布局:液态三栏混合宽度布局
    本文介绍了如何使用 CSS 实现液态的三栏布局,其中各栏具有不同的宽度设置。通过调整容器和内容区域的属性,可以实现灵活且响应式的网页设计。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 本文介绍了如何使用jQuery根据元素的类型(如复选框)和标签名(如段落)来获取DOM对象。这有助于更高效地操作网页中的特定元素。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 当iOS设备越狱后,某些插件可能会导致系统崩溃(白苹果)。此时,可以通过进入安全模式来排查并删除有问题的插件。本文将详细介绍如何通过特定按键组合进入不加载MobileSubstrate的安全模式,并提供相关背景知识。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • 本文详细介绍了如何通过命令行启动MySQL服务,包括打开命令提示符窗口、进入MySQL的bin目录、输入正确的连接命令以及注意事项。文中还提供了更多相关命令的资源链接。 ... [详细]
author-avatar
ESC咻咻_973
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有