iOS开发:The Woost Way™

在Woost,我们的承诺是在一个月内生产出最低可行的产品。 我们相信(几乎)每个新概念的核心都可以在该时间范围内构建。 我们帮助我们的客户找到核心,定义目标和假设,并缩小范围,直到一个月之内即可完成开发。

我们为网络(响应式)和移动(本地Android,本地iOS)构建。 为了能够加快速度并快速交付,我们拥有用于大多数项目的标准工具集和标准项目设置。 在本文中,我将向您介绍我们用于iOS项目的内容。

当然,随着每天都会出现新的最佳实践和新框架,这可能会发生变化。 警告:技术性越来越强😉

可可豆

其他人已经做了很多工作。 如果您知道如何珍惜他人*的工作,则可以节省大量时间,并避免通过使用轮子来重新发明轮子。

*: 这个很重要! 您应用的性能和稳定性至关重要。

CocoaPods是Swift和Obj-C项目的程序包/依赖性管理器。 您可以将CocoaPods集成到项目中,以轻松添加,删除或更新外部软件包。 我们主要使用CocoaPods,有时使用Carthage,但发现前者更易于使用。

我们或多或少的标准Podfile看起来像这样,已经使您了解了我们使用的工具:

 吊舱“ RxSwift” 
豆荚“ RxCocoa”
pod'SwiftyJSON'
 吊舱“翠鸟” 
pod'PureLayout'
吊舱“ R.swift”
 豆荚“面料” 
pod'Crashlytics'
pod'Firebase / Analytics'#或'GoogleAnalytics'

在设置Pod时,请确保还检查gitignore.io来创建一个漂亮的.gitignore模板! 我们通常从这一点开始。

接收

RxSwift是ReactiveX for Swift的一个版本(RxCocoa通过Reactive magic扩展了Cocoa API)。 ReactiveX简化了异步调用(避免了回调地狱 ),使代码更具功能性,可读性且不易出错。

我们主要将其用于API调用和用户界面绑定。 有关为什么使用Rx的更多信息,请查看RxSwift不错的页面。 Ray Wenderlich有一个很好的Rx入门教程。

SwiftyJSON

尽管Swift对JSON有一些基本的了解,但是使用SwiftyJSON可以使代码更整洁,类型更强,代码更短,更好理解。

翠鸟

有很多用于缓存图像的库,但是我们发现Kingfisher是最简单,最方便的使用方法。

PureLayout

AutoLayout在情节提要板上可以很好地工作,但是如果您想在代码中添加布局约束,则原始API相当冗长且难以阅读。 PureLayout是一个很好的API,可以解决此问题。 凭借其方便的类型推断,尤其是使用Swift的短点语法,PureLayout的工作非常简洁。 SnapKit是一个不错的选择,我们尚未尝试。

斯威夫特

R.swift是从Android借来的一个想法,它将强类型,自动完成和编译时检查的资源引入到您的Swift环境中。 通常,在检索例如图像时,您将输入如下内容:

  UIImage(名称:“ imageName”) 

使用R.swift:

  R.image.imageName() 

使用本地化的字符串,它甚至可以更好地工作。 假设您有以下本地化字符串:

  “ welcome.withName” =“欢迎%@”; 

通常,您将输入:

 让welcomeName =字符串(格式:NSLocalizedString(“ welcome.withName”,注释:“”),区域设置:NSLocale.current,“ Alice”) 

和R.swift:

 让welcomeName = R.string.localizable.welcomeWithName(“ Alice”) 

它也非常适合字体,笔尖,重用标识符,本地化字符串等! 查看其回购以获取更多示例。

面料/速溶剂

Crashlytics是Fabric的一部分,是用于崩溃监控和报告的强大工具。 我们还将其用于Beta分发,并使用Fabric的Answers工具进行一些基本的使用情况分析。 Fabric iOS应用程序是一个随时随地检查使用情况和稳定性的好工具。 当出现问题时,它也会直接通知您!

Firebase Analytics / Google Analytics

当然,Google Analytics(分析)是在线分析的行业标准。 最近,Google购买了Firebase并启动了Firebase Analytics。 尽管Google Analytics(分析)是一种扩展的工具,但它主要是为网络分析而创建的。 Firebase是从头开始构建的,并考虑了移动性。 两者都有其优点和缺点。 对于每个项目,我们都会确定最适合的工具。

一定还要检查Firebase的其他功能。 他们的实时数据库,身份验证和远程配置工具也很棒。

项目结构

当我在一家大型荷兰银行的大型iOS项目中工作时,我的同事Alexander向我介绍了带有流量控制器的MVVM模式。 从那以后,我没有使用其他结构。 在我们的框架Xcode项目中设置的文件夹下面找到。

我们使用流控制器来管理通过应用程序的流,而不是将视图控制器彼此连接。 流控制器负责建立视图模型,将其连接到视图控制器并定义视图之间的流。 这样,视图将变得彼此独立,并且可以更轻松地重用。 我们为每个主要流设置了FlowController协议,MainFlowController(由AppDelegate实例化)和单独的流控制器。

协议示例:

 协议FlowController { 
var flowDelegate:FlowController吗? {设置}
func start()
  } 

MainFlowController可能看起来像这样:

 类MainFlowController:FlowController { 
var flowDelegate:FlowController吗?
var window:UIWindow?
  func start(){ 
窗口= UIWindow(框架:UIScreen.main.bounds)
窗口!.rootViewController = preparePreloaderVc()
窗口!.makeKeyAndVisible()
  setupDependencies { 
checkUserAuthorization()
  } 
  } 
  func checkUserAuthorization(){ 
如果AuthService.isAuthenticated {
showMainScreen(动画:false)
  }其他{ 
showLogin()
  } 
  } 
  } 

模型

我们设置了一个Model协议(带有init?(json:JSON)初始化程序),并为每个实体创建了一个模型结构。 初始值设定项可轻松 JSON响应从API 映射到Swift结构。

协议:

 协议模型{ 
初始化?(json:JSON)
  } 

还有一个示例模型:

  struct Person:模型{ 
let id:整数
让firstName:字符串?
让lastNamePrefix:字符串? //荷兰姓氏可能带有前缀
让lastName:字符串?
 让地址:[地址]? 
  var fullName:String { 
返回[firstName,lastNamePrefix,lastName]
.flatMap {$ 0}
.joined(分隔符:“”)
  } 
 初始化?(json:JSON){ 
// id不是可选的,因此没有它,初始化器将失败
卫队让id = json [“ id”]。int else {return nil}
self.id = id
  self.firstName = json [“ firstName”]。string 
self.lastNamePrefix = json [“ lastNamePrefix”]。string
self.lastName = json [“ lastName”]。string
  //使用flatmap映射带有地址的JSON数组 
//寻址对象
self.addresses = json [“ addresses”]。array?
.flatMap {地址(json:$ 0)}
  } 
  } 

视图模型

视图模型基本上包含视图的所有状态和业务逻辑。 通过将其与视图控制器分离,可以更轻松地重用视图。 紧接着,由于视图模型不依赖于UIKit,因此可以进行更扩展和更干净的单元测试。

请参阅下面的视图模型示例,其中包含带有人员列表的视图。 此视图模型既可以用于包含用户的最爱人物,也可以用于搜索查询的结果:

  struct PersonsViewModel:ViewModel { 
枚举模式{
案件
收藏夹,
searchResults(查询:字符串)
  } 
 让模式:模式 
var项目:Variable = Variable([])
 私人让disposeBag = DisposeBag() 
 初始化(模式:模式){ 
self.mode =模式
刷新()
  } 
  func refresh(){ 
切换模式{
大小写
self.items.value =收藏夹服务
。收藏夹
.map {
人(id:$ 0.id,fullName:$ 0.name)
  } 
 案例.searchResults(让查询): 
API服务
.persons(过滤器:[“” freeQuery“:查询])
.bindTo(项目)
.addDisposableTo(disposeBag)
  } 
  } 

视图

视图控制器和视图负责显示视图模型中包含的信息(以一种不错的方式),并负责将用户界面交互与视图模型中的业务逻辑联系起来。 我们尝试将视图保持为“哑巴”。 它们仅代表用户界面的视觉表示。 不多不少。

我们还尝试使视图尽可能通用。 例如,我们不创建“ 搜索结果”视图,而是创建“ 人员列表”视图。 然后,该列表器可用于搜索结果,但也可用于例如列出某人的联系。 只需注入一个不同的视图模型。

视图与流控制器(和UIKit扩展)一起是唯一依赖UIKit的对象类型,这意味着可以轻松地对所有服务和视图模型进行单元测试。

有时我们完全在代码中设置视图,有时我们使用笔尖,有时使用情节提要。 我们从不使用segues,因为流量始终由流量控制器管理。

经理

在我们的案例中,经理是单身人士,负责(您不会说)管理材料。 由于单例的内存使用情况,我们创建了尽可能少的管理器,并尝试在服务的静态函数中尽可能多地包装。

我们使用它们来保持全局状态(例如,身份验证会话),或者用于数字格式化程序和日期格式化程序(如果这些格式化程序和日期格式化程序通过应用程序散布开来,并且我们不想每次都再次实例化它们)。

服务

服务是API或其他数据提供程序与应用程序其余部分之间的接口。 我们的服务仅包含静态函数,这些函数会输出纯对象或带有对象映射数据流的Rx- Observables

例如,一个简单的服务可从UserDefaults中检索保存的收藏夹:

  struct收藏夹{ 
let id:整数
命名:字符串
  } 
 类“收藏夹服务” { 
private static let kFavorites =“收藏夹”
私人静态var favoriteDict:[String:Any] {
得到{
返回UserDefaults
。标准
.dictionary(forKey:kFavorites)?? [:]
  } 
设置{
用户默认值
。标准
.set(newValue,forKey:kFavorites)
  } 
}
 静态var收藏夹:[Favorite] { 
返回收藏夹
.flatMap {(key,val)in
保护卫队id = Int(key)else {return nil}
返回收藏夹(id:id,名称:字符串(描述:val))
  } 
  } 
 静态函数isFavorite(_ id:Int)-> Bool { 
返回favoriteDict [String(id)]!=无
  } 
 静态功能添加(_收藏夹:收藏夹){ 
favoriteDict [String(favorite.id)] = favorite.name
  } 
 静态功能删除(_收藏夹:收藏夹){ 
收藏夹区
.removeValue(forKey:字符串(favorite.id))
  } 
 静态函数删除(_ id:Int){ 
收藏夹区
.removeValue(forKey:字符串(id))
  } 
  } 

稍后,我将撰写有关Rx的API服务的更广泛的文章,但是例如,它可能包含如下功能:

 静态功能人员(id:Int)->可观察 { 
返回请求(.person(id:id))
.map {
Person(json:$ 0 [“ person”])
  } .errorOnNil()// RxOptional的一部分 
  } 

其中request(_ route:APIRoute)URLSession.shared.rx.json(request:URLRequest)的基本包装,负责设置标头,映射错误以及创建请求本身。

延期

Extension文件夹仅包含对Foundation或UIKit类/结构的Swift扩展。

资源资源

在这里,我们存储字体,图像资源, Info.plist文件, R.genic.swift文件,启动屏幕等。

包起来

我希望这对我们如何开发iOS应用程序有一个初步的了解。 就像我在导言中说的那样:这些事情总是在变化。 但是,我们已经在相当长的一段时间内使用了本文中介绍的框架和结构,因此它们可能会保留一段时间。

如果您有任何疑问或意见,请回复此帖子或给我发送消息!

最后但并非最不重要的一点:非常感谢我的前同事Alexander,他向我介绍了MVVM,流量控制器和Rx!