原文:https://www.jianshu.com/p/4ef3d3737661?hmsr=toutiao.ioutm_medium=toutiao.ioutm_source=toutiao.io
作者:故胤道长
在 iOS 开辟 中 ,写一个 App 很轻易 ,但是要写好一个 App,确是要下另一番功夫。起首 ,我们来看一个 App 的开辟 要求:
写一个 App,表现 出 Spotify 上 Lady Gaga 相干 的全部 音乐专辑,相干 信息可以通过以下网址查到:
https://api.spotify.com/v1/search?q=lady+gagatype=album
需求分析
起首 拿到开辟 要求 ,最紧张 的是明白 开辟 细节 。这内里 有很多 我们不清楚 的地方必要 与产物 司理 和计划 师交换 :表现 是要用 TableView 还是 CollectionView?每个音乐专辑的哪些信息必要 表现 ?假如 专辑数量 过多,我们优先表现 哪些专辑?这个 App 除了表现 信息以外,还必要 哪些拓展功能?这个产物 的巨细 是否有要求?必要 多少天完成?
颠末 讨论之后 ,各人 的同等 意见是做个如下的 App:
LadyGaga
于是我们就清楚 了,是要做一个 tableView,每个 Cell 对应一个专辑信息,左边是图片 ,右边是专辑名。点击 Cell,可以看到相应的专辑大图。
构建架构
起首 这个 App 比力 简单 ,我们只要用最根本 的 MVC就可做好 。
Model 部分 :
只必要 一个 Model , 为 Album,对应每一个专辑的信息;
View 部分 :
主体的部分 可以在 Storyboard 内里 完成;
最好单独新建一个 UITableViewCell 的子类,用来对应设置专辑的UI;
ViewController 部分 :
此中 一个 ViewController 为 TableViewController ,负责实际 全部 专辑的信息;
另一个 ViewController 负责展示 detail info,比如 专辑的大图;
Network 部分 :
负责从网络上 fetch 专辑信息;以及根据专辑的图片网址,fetch 图片数据;
根本 架构
细节实现
Network 部分 :
fetchAlbums 和 downloadImage 都用Apple 自带的 URLSession 和 JSONserialization 就可以实现 ,大概 也可以用良好 的第三方库 AlamoFire。由于 这个 App 比力 简单 ,AlamoFire 上风 不显着 ,且引入第三方库会增长 App 的体积 ,故而保举 利用 前者。根本 上就是实现下面两个函数:
funcfetchAlbums(with url: String, completion : @escaping (_ albums: [Album]?, _ error : NSError?) - Void) funcdownloadImage(_ url: String) - UIImage?
对于第一个函数 fetchAlbums,由于 网络哀求 是耗时耗力的工作,我们一样平常 会将它们用背景 线程而非主线程(UI线程)来处理 惩罚 ,如许 可以保持UI的流畅 运行 。用闭包则是为了异步多线程完成后可以回调 ,同时 error 是为了监督 网络哀求 是否堕落 。
对于第二个函数 downloadImage,最简单 的方法是通过 url 拿到对应的 data,然后通过相应的 data 拿到 image。返回为 optional 的缘故起因 是有大概 URL 有题目 大概 网络哀求 堕落 ,此时返回 nil 。
从API计划 的角度来说,以上的downloadImage并不是最佳计划 。最佳的计划 是我们能知道那边 堕落 了,比如 下面如许 :
enumDownloadImageError: Error{ caseInvalidURLcaseInvalidData}funcdownloadImage(_ url: String)throws - UIImage { guardlet aUrl = URL(string: url) else { throwDownloadImageError.InvalidURL } do { let data = tryData(contentsOf: aUrl) iflet image = UIImage(data: data) { return image } else { throwDownloadImageError.InvalidData } } catch { throwDownloadImageError.InvalidURL } }
ViewController 部分 :
对于 AlbumsController ,我们用到了署理 模式(Delegate),即将 tableView 署理 到了 AlbumsController 上。我们只要实现相应的 dataSource 和 delegate 方法即可。此中 对于 dataSource 而言,有两个方法是必须实现的 ,它们是:
functableView(_ tableView: UITableView, numberOfRowsInSection section: Int) - IntfunctableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) - UITableViewCell
同时,AlbumsController 内里 ,尚有 两个数组 ,一个用来装专辑([Album]),一个用来装图片([UIImage?]),如许 我们只需下载数据一次,并将其存入相应数组 ,之后就无需再次举行 相干 的网络哀求 了 。也就是说,这两个数组起到了缓存的作用。
具体 的实现是:起首 在 viewDidLoad() 中哀求 服务器取出相应的数据。之后根据专辑数量 设定 TableView 的相应行数 。在具体 的一行当中,我们可以根据 indexPath 确定相应的专辑。根据相应专辑的图片 URL ,我们可以拿到相应的图片,之后缓存进图片数组。由于我们复用了 TableView 的 Cell,以是 假如 不缓存图片而每次去举行 网络哀求 ,会由于 延时很严峻 而会造成图片闪烁 的结果 。
末了 两个 ViewController 之间的跳转可以用 navigationController 来实现。
View 部分 :
自界说 AlbumCell 可以包管 App 的扩展性很好。同时,为了处理 惩罚 有些专辑名字过长 Label 表现 不了的题目 ,可以用autoshrink 来处理 惩罚 。
App 运行流程
优化拓展
上面的计划 和实现比力 抱负 化 ,如今 我们要思量 一个边界 环境 ,假如 网络不稳固 ,怎么办?
一个简单 的办理 方法就是 ,当网络好的时间 把数据下载下来,存入 cache 和 storage 中,之后纵然 网络停止 、App 瓦解 ,我们都能从 storage 中拿到相应数据。
这里引入表面 模式(Facade) ,创建一个新的 class 名为 LibraryAPI,提供两个接口:
funcgetAlbums(completion : @escaping (_ albums: [Album]?, _ error : NSError?) - Void)funcgetImage(_ url: String) throws- UIImage
这里的方法跟之前 Network 的差别 之处在于:getAlbums 方法会先实行 从 storage 中取出相应数据,假如 没有 ,则去访问 Network,之后再把从 Network 中拿到的值存入 storage 中。这内里 的实现有点复杂,扳连 到两大模块和多线程操纵 ,但是我们并不必关心方法内部的实现,而仅仅关心接口,这就是表面 模式的长处 。同时 ,LibraryAPI 这个 class 最好用单例模式(singleton),由于 它应该被当做是全局 API 被各个 ViewController 来访问,同时如许 计划 也节流 资源。
优化后的 App 流程
别的 一个优化点在于 ,假如 我们一开始拿到很多 数据 —— 比方 10000 个专辑,那么我们该怎么操纵 ?
精确 的做法是分页。我们可以先只拿20个,表现 在 TableView 上。当用户快滑到底端的时间 ,我们可以再取下面20个 ,然后我们统共 有40个在内存中可以表现 ,以此类推 。如许 做的长处 是,我们无需下载全部 的数据 ,以最快、最流畅 的方式布局 TableView,同时根据用户的需求增长 相应的专辑数据。
末了 一个优化点在于,假如 用户上下滑动很快 ,我们怎样 可以或许 用最快速率 加载图片?
答案是用 operationQueue 来处理 惩罚 ,当前 cell 是可见的时间 ,我们就 resume 下载图片的进程 ,否则就 suspend。如许 包管 了我们用有限的内存和 CPU 去最高效的下载用户必要 、当前要见的图片 。
值得一提的是,各人 还可以鉴戒 ASDK 的思绪 来进一步优化程序。
总结
本文从一个简单 的 tableView App 提及 ,评论 了开辟 一个 App 的4个步调 :需求分析 、构建架构、细节实现、优化拓展。简单 先容 了多线程和几种计划 模式 ,盼望 对各人 有所资助 。