概览
一直在写代码,什么是理想中的代码
关于MVC
图片是《斯坦福大学公开课:iOS7应用开发》中第一节课中的图片,讲述了三者之间的关系。按照交通规则来看,黄色是不可逾越的,也就是说model和view之间不可以存在任何联系,controller可以访问和持有model和view,反之则不行。
那么问题来了,UICollectionViewCell,UITableViewCell属于什么呢?当然属于View,继承自它们的当然也属于View,为了方便,我们常常在某个cell中绑定一个model,然后在类里面根据model修改各个元素的值,我个人是不提倡这么做的。从单个页面来看,通常很便捷,但是从上面的图片来看,它已经违反了交通规则。
1、从单元测试来讲,提高了测试的耦合性,想要测试一个view必须先创建一个model
2、一旦绑定了一个类型的model将很难复用到其他界面相似的view,例如中国版的点赞列表、附近的人的列表、消息里面的关注列表,三者界面类似,数据model都是从user继承而来,又稍有不同,换句话说,需求可能是界面都不动,界面中的某个lable的text需要改变。另外如果一个view想要给其他业务或者其他团队或者开源出来给其他人用必须把model从cell中拿掉。这里可以举个开源的例子,com中聊天引用的一个项目JSQMessagesViewController 中的JSQMessagesCollectionViewCell
enum 枚举
enum Rank : Int {
case ace = 1
case two , three , four , five , six , seven , eight , nine , ten
case jack , queen , king
func simpleDescription () -> String {
switch self {
case . ace :
return "ace"
case . jack :
return "jack"
case . queen :
return "queen"
case . king :
return "king"
default :
return String ( self . rawValue )
}
}
}
let ace = Rank . ace
let aceRawValue = ace . rawValue
最初以为swift中的枚举只有以上添加了函数的操作,其实不然啊 看下面
/**
This object gives specific change information about a collection.
*/
public enum CollectionChangeInformation : Equatable {
/// This indicates that an element was updated at a specific index.
case update ( index : Int )
/// This indicates that an element was deleted at a specific index.
case delete ( index : Int )
/// This indicates that an element was inserted at a specific index.
case insert ( index : Int )
}
public func == ( lhs : CollectionChangeInformation , rhs : CollectionChangeInformation ) -> Bool {
switch ( lhs , rhs ) {
case ( . update ( let l ), . update ( let r )):
return l == r
case ( . delete ( let l ), . delete ( let r )):
return l == r
case ( . insert ( let l ), . insert ( let r )):
return l == r
default :
return false
}
}
惊不惊喜,意外不?在枚举的枚举值里update发现了参数这是associated values,你可以理解为这些参数保存在了枚举的变量里面(You can think of the associated values as behaving like stored properties of the enumeration case instance),而且还遵守了Equatable协议,意味着可以自己定义怎么判断两个枚举值是否相等。来看看在苹果官方sdk中说了啥
/// extension StreetAddress: Equatable {
/// static func == (lhs: StreetAddress, rhs: StreetAddress) -> Bool {
/// return
/// lhs.number == rhs.number &&
/// lhs.street == rhs.street &&
/// lhs.unit == rhs.unit
/// }
/// }
public protocol Equatable {
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
public static func == ( lhs : Self , rhs : Self ) -> Bool
}
extension Equatable {
/// Returns a Boolean value indicating whether two values are not equal.
///
/// Inequality is the inverse of equality. For any values `a` and `b`, `a != b`
/// implies that `a == b` is `false`.
///
/// This is the default implementation of the not-equal-to operator (`!=`)
/// for any type that conforms to `Equatable`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
public static func != ( lhs : Self , rhs : Self ) -> Bool
}
Functional Reactive Programming(以下简称FRP)是一种响应变化的编程范式
我们之前一直在使用ReactiveCocoa 【ReactiveCocoa (RAC) is a Cocoa framework inspired by Functional Reactive Programming】,这两天又接触了RxSwift、RxCocoa,发现他们很类似,各有所长。之所以在这里说是因为看到RxCocoa在UI方面更加强大
/**
Binds sequences of elements to collection view items.
- parameter cellIdentifier: Identifier used to dequeue cells.
- parameter source: Observable sequence of items.
- parameter configureCell: Transform between sequence elements and view cells.
- parameter cellType: Type of table view cell.
- returns: Disposable object that can be used to unbind.
Example
let items = Observable.just([
1,
2,
3
])
items
.bind(to: collectionView.rx.items(cellIdentifier: "Cell", cellType: NumberCell.self)) { (row, element, cell) in
cell.value?.text = "\(element) @ \(row)"
}
.disposed(by: disposeBag)
collectionView.rx.itemSelected.subscribe({ [weak self] indexPath in
// do something
}).disposed(by: disposeBag)
*/
有没有很酷,哈哈 看起来不错哦上面例子中只是列举了最简单的一种看下面的复杂的
/**
Binds sequences of elements to collection view items using a custom reactive data used to perform the transformation.
- parameter dataSource: Data source used to transform elements to view cells.
- parameter source: Observable sequence of items.
- returns: Disposable object that can be used to unbind.
Example
let dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel<String, Double>>()
let items = Observable.just([
SectionModel(model: "First section", items: [
1.0,
2.0,
3.0
]),
SectionModel(model: "Second section", items: [
1.0,
2.0,
3.0
]),
SectionModel(model: "Third section", items: [
1.0,
2.0,
3.0
])
])
dataSource.configureCell = { (dataSource, cv, indexPath, element) in
let cell = cv.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell
cell.value?.text = "\(element) @ row \(indexPath.row)"
return cell
}
items
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
*/
MVVM
关于MVVM的介绍ReactiveViewModel ,目前我们项目采用以下设计
model :仅仅是数据模型
view :这里的view包含所有的view和viewcontroller,其中view(包含cell和自定义view)仅仅是view,不包含任何model,另外viewcontroller仅仅负责Layout、Animations、Device rotation、View and window transitions、Presenting loaded UI
viewModel:所有的viewmodel会被viewcontroller持有,这里面负责处理页数调用PXApiMannager获取数据,并且保存在ViewModel的property里面,如果需要为cell准备数据,在这里进行加工
完整数据获取及刷新流程如下:
viewController(订阅ViewModel的某个Signal,如果有输出数据刷新view,如果有错误提示错误)——————>viewModel(调用PXApiManager获取某个signal,进行map加工操作,将数据自身持有)—————>PXApiManager(创建signal,并且调用AFNetworking获取相应数据,如果有有数据则通过Mantle 转化为model并向创建的signal抛出,如果有错误也抛出,这里也会进行接口异常记录,链接请求统一处理header,统一添加token信息等等)
IGListKit
IGListKit 是Facebook的又一神作,这里是raywenderlich上的一篇教程 ,教你如何快速的在list中添加功能,而且滑动起来非常的流畅。IGListKit非常智能,会自动检查你数据中的变化,并流畅的更新UICollectionView 中对应改变数据的部分。
总体分为五步:
1、声明一个IGListCollectionView的变量,并且添加到当前viewcontroller当中,并且设置布局
// 1
let collectionView : IGListCollectionView = {
// 2
let view = IGListCollectionView ( frame : CGRect . zero , collectionViewLayout : UICollectionViewFlowLayout ())
// 3
view . backgroundColor = UIColor . black
return view
}()
2、声明一个IGListAdapter的变量,并且为之设置view和databsource
lazy var adapter : IGListAdapter = {
return IGListAdapter ( updater : IGListAdapterUpdater (), viewController : self , workingRangeSize : 0 )
}()
adapter . collectionView = collectionView
adapter . dataSource = self
3、在datasource中设置数据、IGListSectionController及为空时显示的view,注意⚠️这里的IGListSectionController相当于上面我们提到的ViewModel,你可以在里面控制列表中的单元如何显示
// MARK: - IGListAdapterDataSource
extension FeedViewController : IGListAdapterDataSource {
func objects ( for listAdapter : IGListAdapter ) -> [ IGListDiffable ] {
var items : [ IGListDiffable ] = [ wxScanner . currentWeather ]
items += loader . entries as [ IGListDiffable ]
items += pathfinder . messages as [ IGListDiffable ]
return items . sorted ( by : { ( left : Any , right : Any ) -> Bool in
if let left = left as? DateSortable , let right = right as? DateSortable {
return left . date > right . date
}
return false
})
}
func listAdapter ( _ listAdapter : IGListAdapter , sectionControllerFor object : Any ) -> IGListSectionController {
if object is Message {
return MessageSectionController ()
} else if object is Weather {
return WeatherSectionController ()
} else {
return JournalSectionController ()
}
}
func emptyView ( for listAdapter : IGListAdapter ) -> UIView ? { return nil }
}
4、在IGListSectionController里面设置边距、Section里面有几个单元cell、每个cell又用的哪种类型,并用model填充cell
import IGListKit
class MessageSectionController : IGListSectionController {
var message : Message !
override init () {
super . init ()
inset = UIEdgeInsets ( top : 0 , left : 0 , bottom : 15 , right : 0 )
}
}
// MARK: - IGListSectionType
extension MessageSectionController : IGListSectionType {
func numberOfItems () -> Int {
return 1
}
func sizeForItem ( at index : Int ) -> CGSize {
guard let context = collectionContext else { return . zero }
return MessageCell . cellSize ( width : context . containerSize . width , text : message . text )
}
func cellForItem ( at index : Int ) -> UICollectionViewCell {
let cell = collectionContext ? . dequeueReusableCell ( of : MessageCell . self , for : self , at : index ) as! MessageCell
cell . messageLabel . text = message . text
cell . titleLabel . text = message . user . name . uppercased ()
return cell
}
func didUpdate ( to object : Any ) {
message = object as? Message
}
func didSelectItem ( at index : Int ) {}
}
5、当你的数据有更新时,你需要执行
adapter . performUpdates ( animated : true )
但是看起来并没有Rxswift那么简单有没有,但是性能应该是没问题的。具体可以测试一下。