概要
要开始做一个项目,首先要搭建好这个项目的架构,一个项目架构的好坏直接影响到一个项目以后的维护性扩展性和装逼性等一系列的问题。
在最近一个新的项目,我用到Swift来写,因为对Swift不是很熟悉,所以前期做了许多工作,看别人的项目源码啊,看视频什么。目的是尽量了解一个Swift的项目的架构应该如何去做。
在思考这个项目的架构的过程中,考虑修改的比较多的就是网络层(接口调用是否方便,接口添加是否容易,是否符合Swift的风格.......)。
另外,这次项目也用到了RxSwift,基于MVVM模式,但并不严格遵循(主要是懒和觉得有些地方没必要)。项目中也比较多的地方用到OC没有而且实用的Swift的泛型特性。
项目的swift版本是2.3。
1.项目的目录架构
- 1 基础父类
- 2 类库(接下来会介绍)
- 3 弹窗类
- 4 扩展
- 5 网络层(MNNetwork网络请求单例,接口地址,路由-用于请求体的封装-接下来会介绍)
- 6 其他资源
- 7 公共类(主要介绍MNModel,数据模型层)
- 8 三方库
2.接口地址文件-Interface.swift
struct Interface {
enum MNService {
case release, test // 定义服务器地址,方便生产服和测试服们之间的切换
}
static let kService = MNService.test // 默认
static var kHost: String {
get {
if (Interface.kService == HLService.release) {
return "http://release.cn/"
}
else if (Interface.kService == HLService.test) {
return "http://test.cn/"
}
return ""
}
}
static let Test1 = Interface.kHost + "1" // 一个测试接口
}
3.网络请求路由-MNRouter.swift
一个文件里面我会定义不止一个请求路由,例如我会定义一个涉及用户操作的路由和不涉及用户操作的路由,因为他们的请求体可能会不同,前者需要加解密操作,而后者则不需要,这一定程度上是为了应对2017年新的审核规则。
let AF = Alamofire.ParameterEncoding.URL // 相当于宏,方便接下来的调用
// 登录注册Router
enum LoginAndRegisterRouter: URLRequestConvertible {
// 登录,这里之定义了一个接口
case Login(phone: String, pwd:String)
// relation会根据该枚举实例的类型返回相对应的元组,包括请求方法(GET,POST....),请求路径,请求参数
var relation: (method: Alamofire.Method, path: String, params: [String: String]?) {
switch self {
case Login(let phone, let pwd):
return (Alamofire.Method.POST, Interface.Login, ["name": phone, "pwd": pwd])
}
}
// 返回封装好的请求体,接下来的调用接口操作就很方便了
var URLRequest: NSMutableURLRequest {
let url = NSURL.init(string: relation.path)
let mRequest = NSMutableURLRequest.init(URL: url!)
mRequest.HTTPMethod = relation.method.rawValue
mRequest.setValue("xxx", forHTTPHeaderField: "token")
return AF.encode(mRequest, parameters: relation.params).0
}
}
该枚举遵循了Alamofire 的URLRequestConvertible协议,里面要求实现一个URLRequest计算属性。
看了一些项目的源码和网上关于Alamofire的最佳实践的视频,它们的做法是relation那里method独立开来,然后switch一次;然后是path,URLRequest都一样的做法。后来我想了想,搞个元组类型的relation来关联这些更为方便些,因为我发现项目中的请求体基本是一样的,所以没必要做那么多功夫。想想,一个接口添加要多2个步骤,那如果一下子做几十个接口的添加的话,工作量会比较大。
4.数据模型的构建-MNModel.swift
protocol MNModelProtocol {
static func modelWithJSONMN(dict: [String: AnyObject]) -> Self?
}
class MNModel: NSObject, MNModelProtocol {
static func modelWithJSONMN(dict: [String : AnyObject]) -> Self? {
return self.modelWithJSON(dict)
}
func modelArray<T: NSObject>(dic: NSDictionary, key: String, type: T) -> Array<T> {
var temp: [T] = []
if dic[key] is NSArray {
for item in dic[key] as! NSArray {
let tempItem = item as? NSDictionary
// modelWithJSON是YYModel定义的一个类方法,?? 值为空就传一个默认值
temp.append(T.modelWithJSON(tempItem ?? []) ?? T())
}
}
return temp
}
// 打印数据
override var debugDescription: String {
return self.modelDescription()
}
override var description: String {
return self.modelDescription()
}
}
// 数据模型例子
class ModelClass: MNModel {
var identity: IdentityClass = IdentityClass()
var students: Array<Student> = []
class func modelContainerPropertyGenericClass() -> NSDictionary {
return [
"identity": IdentityClass.self,
]
}
func modelCustomTransformFromDictionary(dic: NSDictionary) -> Bool {
students = self.modelArray(dic, key: "students", type: Student())
return true
}
}
项目里面YYModel替代了MJExtension,YYWebImage替代了SDWebimage,YYKit还有其他好用的库,例如YYText,一次pod,多处可用。
其实YYModel兼容Swift并不是很好,像我们经常碰到的数组里面存自定义类型的元素,通过YYModel,MJ转换的数组的遍历在Swift中都会崩溃,因为swift不支持由oc数组转换过来的这样的数组。所以自己做了一些处理。(我之前看见一篇文章,介绍过一个库是实现了Swift版的json转model的,忘记了)。
MNModel要遵循MNModelProtocol这样一个协议是为了让我能够实现请求回调的一个处理数据的泛型函数,下面会介绍。
第二个函数modelArray就是为了解决上面说到的一个问题。
其实可以感受得到在swift中使用泛型的地方会更多,oc是动态类型语言,它弱类型的特质使得我们可以很方便的实现类型之间的转换,而到了swift,他是静态语言,强类型,很多时候类型的转换操作更加的繁琐,甚至OC的转换思路去到Swift就报错,但是它带来的是程序更加的可靠和稳定。
5.网络请求单例的封装-MNNetwork.swift
// 枚举的泛型例子
enum NetWorkResult<T> {
case value(T)
case error(String)
}
typealias Finished = ((objc: [String: AnyObject]?, error: NSError?, badNetwork: BooleanType?) -> ())?
typealias Success = ((objc: AnyObject!) -> ())?
typealias ErrorFunc = ((objc: String!) -> ())?
class MNNetwork: Alamofire.Manager {
// 这是封装了网络请求的单例
static let tool: MNNetwork = {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
config.timeoutIntervalForRequest = 60.0 // 设置超时
config.HTTPMaximumConnectionsPerHost = 1 // 设置最大并发量
return MNNetwork(configuration: config)
}()
class func shareNetwork() -> MNNetwork {
return tool
}
// 这个函数的作用是根据传入的请求体进行网络请求,返回数据后进行一系列的判断数据的有效性(例如status=0就返回错误等)
// 若果数据有效,进行json转Model的操作并返回NetWorkResult.value(tempModel),tempModel是对应数据模型的实例,之所以这样返回因为要适应RxSwift
// 若果数据无效,则返回NetWorkResult. error(objc),枚举值是字符串,用以错误提示
func responseJSONMN<T: MNModelProtocol>(request: URLRequestConvertible,
type: T,
success: ((T!) -> ())?,
fail: ((String!) -> ())?
) -> Observable<NetWorkResult<T>> {
return Observable.create { observer -> Disposable in
let request = MNNetwork.shareNetwork().request(request).responseJSON { (response: Response<AnyObject, NSError>) in
if response.result.isSuccess {
if let dict = response.result.value as? [String: AnyObject] {
let model = T.modelWithJSONMN(dict) // 获取数据模型实例
if let tempModel = model {
if let tempSuccess = success {
tempSuccess(tempModel) // 以上一大串是用于保证数据的安全性,这里相当于OC的block,返回数据模型实例
}
observer.onNext(NetWorkResult.value(tempModel))
}
else {
let objc = "数据解析失败"
if let tempFail = fail {
tempFail(objc)
}
observer.onNext(NetWorkResult.error(objc))
}
}
}
else {
let objc = "网络异常"
if let tempFail = fail {
tempFail(objc)
}
observer.onNext(NetWorkResult.error(objc))
}
}
return AnonymousDisposable {
request.cancel()
}
}
}
}
这里说一下我对 .self Self .Type的理解吧:
- .self : 例如UIViewController.self,相当于获取UIViewController的类实例,然后调用它的类方法。
- Self : 一般用在类方法的返回值类型的声明,代表返回一个当前类型的实例。相当于instancetype
static func modelWithJSONMN(dict: [String : AnyObject]) -> Self? {
return self.modelWithJSON(dict)
}
- .Type : 用来声明该 类实例 是什么类型,它的作用仅用于声明,例如一个方法NSClassFromString(strClass) as! UITableViewCell.Type,NSClassFromString都知道是返回一个类实例,然后我需要在后面声明这个类实例属于什么类型。
6.网络请求的调用
func getLogin() -> Observable<NetWorkResult<MNLoginModel>> {
let create = testRouter.Login(phone: "135111111111", pwd: "123456")
return MNNetwork.shareNetwork().responseJSONMN(create, type: MNLoginModel(), success: { (model) in
print("po:\(model)")
}, fail: { (error) in
print("errorString:\(error)")
})
}
这里举了个登录接口调用的例子,至于返回值也是也是为了适应RxSwift。关于基于Rxswift的网络请求相关更多的资料可以看其他的文章Rxswift网络请求使用
总结
好了,一个基于MVVM模式依赖RxSwift开发的Swift项目架构就差不多是这样了,然后放出呆萌:Demo,呆萌里面手势滑动返回,tabbar显示隐藏动画等的封装我觉得应该对你的项目有帮助。项目中的不足之处希望能够得到大家的指点,谢谢~~!!