深入理解iOS设计模式

栏目: 后端 · 发布时间: 6年前

内容简介:深入理解iOS设计模式

做 iOS开发也好几年了,记得自己刚入行时,对iOS 开发模式也是一知半解,后面项目做多了,以及看一些优秀书籍之后对他有了进一步的理解;发现自己写的代码中无形之中就用了很多的设计模式,只不过是不知道相应的 设计模式 术语而已,所以决定把自己做的项目中的一个小模块来写一篇iOS设计模式的文章,一是记录自己的学习;二是让入门者可以作为参考,来理解iOS设计模式。 设计模式是软件设计中常见问题的可重复使用的解决方案。 它们是旨在帮助您编写易于理解和重用的代码的模板。 它们还可以帮助您创建松散耦合的代码,以便您可以在代码中更改或替换组件,而不必太麻烦。 本文将通过一个小项目的形式来讲解iOS的设计模式。通过跟着我一步一步操作你将学到: 什么是设计模式;为什么要使用设计模式;以及怎样在自己的项目中使用合适的设计模式。 设计模式分为三大类:

  1. 构造模式:单例模式(Singleton),抽象工厂模式(Abstract Factory)等等;
  2. 结构模式:MVC,适配器模式(Adapter),外观模式(Facade),装饰模式(Decorator)等等;
  3. 行为模式:观察者(Observer),备忘录(Memento),命令模式(Command)。整个项目完成将会是如下效果图:
    深入理解iOS设计模式

    image.png

开始鲁代码

github上下载starter project,使用Xcode打开项目,这是一个新建的空项目,我将Main.storyboard给删除了,在appdelegate中使用代码设置window的rootViewController为ViewController;以及将books.json放入到了本地,模拟网络请求来的书籍数据(这部分涉及到公司的机密资源,所以只能这样模拟)。

1、使用快捷键Command+N,选择iOS->Swift File,并且命名为 Book ,新建一个 Book 结构体作为模型,并且编写如下代码。(在Objective-C我们一般是新建一个 Book 类作为模型,但是swift中官方推荐使用结构体,更加轻量级),此处我使用了JSONExport开源 工具 生成Book结构体,这是一个很强大的将json转化模型的Mac桌面应用。

struct Book {    var bid: Int!   var bookName: String!   var isRead: Bool!   var orientation: Int!   var pic: String!     /**    * Instantiate the instance using the passed dictionary values to set the properties values    */   init(fromDictionary dictionary: [String: Any]){     bid = dictionary["bid"] as? Int     bookName = dictionary["book_name"] as? String     isRead = dictionary["is_read"] as? Bool     orientation = dictionary["orientation"] as? Int     pic = dictionary["pic"] as? String   }    /**    * Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property    */   func toDictionary() -> [String:Any]   {     var dictionary = [String: Any]()     if bid != nil{       dictionary["bid"] = bid     }     if bookName != nil{       dictionary["book_name"] = bookName     }     if isRead != nil{       dictionary["is_read"] = isRead     }     if orientation != nil{       dictionary["orientation"] = orientation     }     if pic != nil{       dictionary["pic"] = pic     }     return dictionary   }  }

当然在实际项目中swift的json解析我们还可以使用GitHub开源的SwiftyJSON,这个也很好用,使用这个就能够减少model类的创建,这个两种使用方式各有各的好吧。

2、Command + N,选择Cocoa Touch Class,命名为 BookView 作为 UICollectionView 的子类,language选择Swift;同样操作新建一个 BookCell 作为 UICollectionViewCell 的子类,在选择language上面勾选Also create XIB file。新建完毕之后记得在这两个类前面添加 final 修饰符(这里涉及到代码规范的内容,我后续将会写一篇这样的文章,苹果官方推荐那些些不会继承的类可以添加final修饰,这么做的好处是提高编译速度,当项目庞大的时候优势就明显了,我也深有体会)

MVC设计模式

Model View Controller(模型视图控制器)是我们所有设计模式中最常用的,他根据程序中的角色对对象进行分类,使代码干净分离,低耦合; Model: 保存程序的数据并且定义如何操作它的对象。本例子的Model是结构体 BookView: 负责程序UI视觉元素的表示以及与用户的交互响应,基本上所有的UIView及其子类都属于这一类;本例中的View是 BookViewController: 控制器是View和Model之间的协调者,它访问模型中的数据,并且显示于视图,并且监听用户事件并根据需要操作数据;本例中的Controller是 ViewController 下图很好的显示了这三者之间的关系:

深入理解iOS设计模式

MVC

  1. 模型数据的变化会通知通知控制器,同时控制器就会更新视图上的数据;
  2. 当用户有操作交互时,视图也能通知控制器,于是控制器更新模型中的数据信息。有些人就会有疑问了,为什么不把所有的都写在一个类里面,直接在将视图和模型都写在控制器里面,这样操作起来很容易。我们都知道软件设计有个很重要的原则就是: 高内聚,低耦合 ,所以我们得考虑把代码分离和重用。理想情况下,视图应与模型完全分开。 如果View不依赖于Model的具体实现,则可以使用不同的模型重用其他数据。例如本例中的模型Book,因为不依赖任何视图类,所以可以很简单的重复使用。以及视图类BookView和BookCell,在将来要添加某个功能时完全可以复用,例如我做的真实项目中有个功能模块是书籍的本地下载页面,此时就完全可以复用整个视图类。

基于MVC,我将项目程序分为了三个group,使用快捷键Command+option+N新建三个group,并且命名为Model、View、Controller,同时将相应的源文件拖入相应的分组,如下图:

深入理解iOS设计模式

group分组

单例模式(The Singleton Pattern)

单例模式能够确保指定类只有一个实例,并且全局可以访问到该实例。苹果官方有大量使用这个模式,比如: UserDefaults.standard、UIApplication.shared、UIScreen.main 等等,但是, 单例也不能够滥用,因为单例一旦创建他的内存将存在于整个应用程序的一生,直到应用程序被关闭,内存才被释放 。本例中:BookAPI类是单例用来处理书籍数据

final class BookAPI: NSObject {    private var persistencyManager: PersistencyManager   private var httpClient: HTTPClient    /// 单例   static let sharedInstance = BookAPI()    override init() { // 线程安全     persistencyManager = PersistencyManager()     httpClient = HTTPClient()     super.init()   }    /// 返回书籍数量   ///   /// - Returns: book数组   func getBooks() -> [Book] {     return persistencyManager.books   }    /// 保存书籍数据   func saveBooks() {     persistencyManager.saveBooks()   }     /// 删除指定位置的绘本   ///   /// - Parameter index: index 0   func deleteBookAt(index: Int) {     httpClient.postRequest(url: "delete/book", params: ["book_id": ""]) { (res) in       if res { // 服务器删除成功,删除本地的绘本         persistencyManager.deleteBookAt(index: index)       } else {         print("删除绘本失败,请稍后再试")       }     }   } }

外观模式(the Facade Pattern)

深入理解iOS设计模式

外观模式的形象例子

外观设计模式为复杂的子系统提供单一的接口,只公开一个简单统一的API,而不是将一组类及其API暴露给用户。

深入理解iOS设计模式

Facade Pattern

API的用户完全不知道下面的复杂性。 这种模式在使用大量类时非常理想,特别是当它们复杂使用或难以理解时。外观模式将使用系统的代码与您隐藏的类的接口和实现相分离; 它也减少了外部代码对子系统内部工作的依赖性。 如果外观下面的类改变,那么外部类可以保留相同的API。例如,如果您希望替换后台服务器地址,您将不必更改您的API的代码。在本例中如何使用外观设计模式:目前我们有 PersistencyManager 来获取本地书籍数据,保存书籍数据; HTTPClient 来处理网络请求。我们项目中的其他类将不会知道这个底层逻辑。如下图我们将在 BookAPI 暴露以下方法来供外部调用:

深入理解iOS设计模式

公共方法

persistencyManager、httpClientBookAPI 的私有成员变量,不暴露给外部调用;

深入理解iOS设计模式

私有成员变量

装饰设计模式(The Decorator Design Patte)

深入理解iOS设计模式

装饰模式的形象例子

装饰设计模式可以在不修改其代码的情况下,动态地向对象添加行为和责任。 就扩展功能来说,装饰设计模式相比于生成子类更为灵活。在Objective-C中,这种模式有两种非常常见的实现: Category(类别)和Delegation(委派) 。swift中使用 extension 为相应的类添加扩展。以下三种情况考虑使用这一模式:

  1. 想要在不影响其他对象的情况下,移动台、透明的方式给单个对象添加职责;
  2. 想要扩张一个类的行为,却做不到。类定义可能被隐藏,无法进行子类话;或者对类的每个行为的扩展,为支持美中功能组合,将产生大量的子类;
  3. 对类的职责的扩展是可选的。

extension

本例中的book封面,后台返回的字段 "pic": "/image/20160826/d51285bb636281dce6974313eaf6f15d.png" 只是一个路径,前面的域名 https://xxxxxx-aliyun.firstleap.cn 需要我们这边统一拼接,同时我们使用的阿里云存储服务,有对图片进行处理(包括图片压缩,减少图片分辨率),且iPhone和iPad的处理还不一样。

我们对String添加一个扩展(oc中为category),File/New/File...,选择Swift File,命名为 String+Aliyun ,添加以下代码:

// 判断型号 let isPad = ( UI_USER_INTERFACE_IDIOM() == .pad) let fileHost = "https://xxxxxx-aliyun.firstleap.cn"  extension String {   func aliyunThumb() -> String {     if isPad {       return "\(fileHost)\(self)" + "!iPadThumb"     } else {       return "\(fileHost)\(self)" + "!thumb"     }   } }

Delegation

本例中的BookView作为UICollectionView的子类,有两个方法你必须实现,那就是

// 有多少个item public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int // 每个item需要现实的信息内容 public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell

这两个方法都是有BookView的代理ViewController中实现,苹果的UIKit中有很多都是代理委托模式( UITableView, UITextView, UITextField, UIWebView, UIAlert, UIActionSheet, UICollectionView, UIPickerView, UIGestureRecognizer, UIScrollView

ViewController中实现BookView的代理方法如下:

extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {       return books.count   }    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {     let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCell", for: indexPath) as! BookCell     if indexPath.item > books.count - 1  {       cell.coverName.isHidden = true       cell.bookCover.isHidden = true       cell.isReadImageView.isHidden = true     } else {       cell.coverName.isHidden = false       cell.bookCover.isHidden = false       cell.isReadImageView.isHidden = false       cell.book = books[indexPath.item]     }     return cell   }    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {     let alertVc = UIAlertController(title: "要删除这本绘本么", message: "", preferredStyle: .alert)     let sureAction = UIAlertAction(title: "确定删除", style: .default, handler: { (_) in      })     alertVc.addAction(sureAction)     let cancelAction = UIAlertAction(title: "取消", style: .default, handler: nil)     alertVc.addAction(cancelAction)     present(alertVc, animated: true, completion: nil)   }  }

备忘录模式(The Memento Pattern)

顾名思义,备忘录模式用来保存当前程序退出时,当前上下文的文档的数据; 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先的保存状态。 当满足以下两个条件时需要考虑使用这一模式:

  1. 需要保存一个对象在某一时刻的状态,这样以后就可以恢复到先前的状态;
  2. 用于获取状态的接口会暴露细节,需要将其隐藏起来。

在本例中,我在cell的点击事件添加了删除绘本事件,用来删除被点击的书籍,具体代码如下:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {     let alertVc = UIAlertController(title: "要删除这本绘本么", message: "", preferredStyle: .alert)     let sureAction = UIAlertAction(title: "确定删除", style: .default, handler: { (_) in       BookAPI.sharedInstance.deleteBookAt(index: indexPath.item) // 发送删除绘本的请求       self.books = BookAPI.sharedInstance.getBooks() // 更新数据源       self.bookView?.deleteItems(at: [indexPath])        BookAPI.sharedInstance.saveBooks() // 保存最新数据,到本地,下次加载app,删除绘本不再显示     })     alertVc.addAction(sureAction)     let cancelAction = UIAlertAction(title: "取消", style: .default, handler: nil)     alertVc.addAction(cancelAction)     present(alertVc, animated: true, completion: nil)   }

内部的实现,封装在 PersistencyManager 对象中,真个思路就是,第一次启动加载budle中的资源(实际项目中需要加在服务器的数据),然后保存到沙河中,但数据有更新变动时也实时保存数据,整个代码如下:

let LibraryCacheDirectory = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!  final class PersistencyManager: NSObject {    var books = [Book]()    override init() {     super.init()      let fileName = "\(LibraryCacheDirectory)/albums.bin"     let fileurl = URL(fileURLWithPath: fileName)     let bookdata = try? Data(contentsOf: fileurl)      guard let data = bookdata else { // 本地沙河没有数据,所以需要加在bundle中的数据       setupBundleData()       return     }     let json = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Any]     for b in json! {       let book = Book(fromDictionary: b as! [String : Any])       books.append(book)     }   }    private func setupBundleData() {     let path = Bundle.main.path(forResource: "books", ofType: "json")     let url = URL(fileURLWithPath: path!)      do {       let data = try Data(contentsOf: url)       let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Any]       print(data)       //      print(json)       for b in json! {         //        print(b)         let book = Book(fromDictionary: b as! [String : Any])         books.append(book)       }       print(books)       saveBooks()     } catch {       print(error)     }    }    func getBooks() -> [Book]{     return books   }    func saveBooks() {     let fileName = "\(LibraryCacheDirectory)/albums.bin"     let url = URL(fileURLWithPath: fileName)     let bookDicts = books.map { (book) -> [String:Any] in       book.toDictionary()     }     let data = try? JSONSerialization.data(withJSONObject: bookDicts, options: .prettyPrinted)     try? data?.write(to: url)   }    func deleteBookAt(index: Int) {     if index >= books.count {       print("数组越界,没有此绘本书")       return     }     books.remove(at: index)   } }

感兴趣的可以在github上下载完整代码大家相互交流,整个项目只是我从实际项目中摘取出来的一个页面来讲解,而整个iOS设计模式的内容肯定址这么点内容,后续有时间我应该会更新代码讲解其他的设计模式。我的参考内容有:

  1. 图灵程序设计丛书Objective-C编程之道 iOS设计模式解析
  2. raywenderlich官网文章iOS Design Patterns

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Chinese Authoritarianism in the Information Age

Chinese Authoritarianism in the Information Age

Routledge / 2018-2-13 / GBP 115.00

This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换