最近一段时间搞得东西太多了。服务端Spring,Android入门。但是自己的老本行一直没有好好的整理过。加上现在Swift3已经出来了一段时间了。还是不能忘了老本行,为了顺应潮流前前后后看了不少关于Swift的。还是觉得要在成熟的项目中才能学到更多的东西。所以选择了
Alamofire
这个库作为学习材料。
文中难免有错,不喜勿喷!
跟着Alamofire(4.0.0)学Swift3(二)
枚举定义(AFError异常类型)
枚举感觉更像一个类。感觉失去当初熟悉的枚举的影子。四不像,可以定义方法,但是不能定义变量
enum CompassPoint{
case North
case Sourth
case East
case West
//枚举中 可以定义方法
func show(){
print(self)
}
}
// 定义枚举变量
var p = CompassPoint.North
// 类型标注之后 可以使用点来获取枚举值
var p2 : CompassPoint = .Sourth
p.show()
p2.show()
除此之外,在Alamofire
中的枚举更是有点与众不同
。可以在枚举里面定义其他枚举类型,并且枚举的case
可以传递参数。异常类型可以直接通过throw
抛出。
public enum AFError: Error {
public enum ParameterEncodingFailureReason {
case missingURL
// 可以传递参数
case jsonEncodingFailed(error: Error)
case propertyListEncodingFailed(error: Error)
}
...
// 这才是真正的case
case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}
枚举可以扩展。并且可以在扩展里面给枚举添加属性值,通过判断当前枚举,返回相应的内容
extension AFError {
/// Returns whether the AFError is an invalid URL error.
public var isInvalidURLError: Bool {
if case .invalidURL = self { return true }
return false
}
...
/// `underlyingError` properties will contain the associated values.
public var isResponseSerializationError: Bool {
if case .responseSerializationFailed = self { return true }
return false
}
}
这里就是判断当前的错误是属于哪一种类型的错误。除此之外,由于扩展可以定义多个,那么就可以对某一类功能归类到统一扩展中。比如其中就定义了一个便捷属性的扩展。
extension AFError {
/// The `URLConvertible` associated with the error.
public var urlConvertible: URLConvertible? {
switch self {
case .invalidURL(let url):
return url
default:
return nil
}
}
...
/// The `String.Encoding` associated with a failed `.stringResponse()` call.
public var failedStringEncoding: String.Encoding? {
switch self {
case .responseSerializationFailed(let reason):
return reason.failedStringEncoding
default:
return nil
}
}
}
当然除了给AFError
添加扩展之外,还为AFError
内部枚举定了相应扩展。
extension AFError.ParameterEncodingFailureReason {
var underlyingError: Error? {
switch self {
case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
return error
default:
return nil
}
}
}
然后上上层直接调用underlyingError
得到error
Summary
1.枚举添加访问修饰符,并且可以实现协议。比如。
public enum AFError: Error
。这里的Error
其实是一个协议public protocol Error
-
2.枚举内部可以再定义枚举。相当于声明枚举,后面还是通过
case
的方式使用。并且可以传递参数public enum ParameterEncodingFailureReason { case missingURL case jsonEncodingFailed(error: Error) case propertyListEncodingFailed(error: Error) } ... case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
-
3.通过扩展给枚举挺添加便捷属性。
extension AFError { /// Returns whether the AFError is an invalid URL error. public var isInvalidURLError: Bool { if case .invalidURL = self { return true } return false } ...
-
4.按照不同功能给扩展分组。让代码更便于阅读。比如:
MARK: - Convenience Properties// MARK: - Convenience Properties
extension AFError {
/// The URLConvertible
associated with the error.
public var urlConvertible: URLConvertible? {
switch self {
case .invalidURL(let url):
return url
default:
return nil
}
}
```
MARK: - Error Descriptions
```
// MARK: - Error Descriptions
extension AFError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidURL(let url):
return "URL is not valid: \(url)"
case .parameterEncodingFailed(let reason):
return reason.localizedDescription
case .multipartEncodingFailed(let reason):
return reason.localizedDescription
case .responseValidationFailed(let reason):
return reason.localizedDescription
case .responseSerializationFailed(let reason):
return reason.localizedDescription
}
}
```
通知定义(Notifications)
定义的通知是一件比较简单的事情。一般情况下,在OC
中我们会直接定义一个字符串来表示某种通知。通常情况下也没怎么把通知管理起来。比如:NSString *NTESNotificationLogout = @"NTESNotificationLogout";
。
在Alamofire
中定义的通知就感觉很正式了。首先是成了一个扩展的形式,把相关的通知都写在里面。然后使用结构体来包装通知。代码如下:
extension Notification.Name {
public struct Task {
public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")
public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")
public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")
public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
}
}
注意定义的都是静态常量
相当于扩展了系统通知name
。把通知名称都定义在里面。然后通过不同的结构体定义不同用途的通知。其实在OC
中也可以这样做。只是平时很少这样写,这样写之后代码组织就更加优雅了。
OC
中也有这样的属性。
@interface NSNotification : NSObject
@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;
Swift
中是
open class NSNotification : NSObject, NSCopying, NSCoding {
open var name: NSNotification.Name { get }
open var object: Any? { get }
open var userInfo: [AnyHashable : Any]? { get }
使用的地方写法:
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
Summary
- 1.通过扩展
Notification.Name
来定义通知名称。让代码组织更加优雅。 - 2.使用结构体来区分不同功能的通知。在结构体下定义静态常量定义通知名称。
参数编码(ParameterEncoding)
再看枚举定义
枚举继承的类就是case
所对应的类型呢。比如:
public enum HTTPMethod: String {
case options = "OPTIONS"
...
case connect = "CONNECT"
}
HTTPMethod继承
自String,表示case所定义的就是字符串类型。如果改为int
。就会出现:
这里的String。其实就是rawType
typealias
通过typealias
就是指给一个类型取一个别名。比如public typealias Parameters = [String: Any]
。Parameters
就是一个字典,key
为字符串,value
可以是任意类型
throws
在Swift 2
中所有的同步 Cocoa API
的 NSError
都已经被 throw 关键字取代。这就很尴尬了,关于这个的用法可以参考这里Swift 2 throws 全解析 - 从原理到实践当然最好的还是直接看苹果的文档Page
Error Handling。
结构体实现协议
-
方法默认值:
public init(destination: Destination = .methodDependent) { self.destination = destination }
这里默认参数为枚举
methodDependent
-
快速创建当前结构体。这个有点类似于通过静态方法创建类的概念。只不过把创建的过程直接放在了声明属性的后面。比如:
/// Returns a default `URLEncoding` instance. public static var `default`: URLEncoding { return URLEncoding() } ... /// Returns a `URLEncoding` instance with an `.httpBody` destination. public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) }
这样的方式在项目中用得还不较多。在声明变量的时候就赋值好。
-
方法参数
通过
_
来省略外部参数。内部参数和外部参数一样,则只用声明种就行了。如public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?)
异常
看一段函数
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
guard let url = urlRequest.url else {
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
}
return urlRequest
}
try
的使用。如果方法有throws
。那就就可以通过在调用的时候加上try
来捕获异常。有一种场景比如不处理异常
,我非常确定某个方法或者函数虽然声明会抛出异常,但是我自己知道我在使用时候是绝对不会抛出任何异常的。这种情况下 我们可以使用 try!。try! functionThrowErrorNil()
??问号,表示如果前面表达式为空,则就用后面的作为返回值。比如HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET")
if let和 guard else的使用
。抛出异常直接就用throw
抛出异常。千万要记住,if 后面不一定只是let。还有跟var。其本质就是变量和常量而已。
比如上面代码中的if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET")
和if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty
其他
-
Any和AnyObject:详细介绍可以参考ANY 和 ANYOBJECT
- AnyObject 可以代表任何 class 类型的实例
- Any 可以表示任意类型,甚至包括方法 (func) 类型
数组定义及初始化:
var components: [(String, String)] = []
-
do catch的使用
。和其他语言不同,没有用try
.do { try functionWillThrowError() } catch { // deal with error }
catch 和 switch 一样具有 Pattern Matching 的能力。所以,使用 catch 你可以对异常的解析进行更为高级的处理。比如:
do { try functionWillThrowError() } catch MyError.NotExist { // deal with not exist } catch MyError.OutOfRange { // deal with not exist }
-
try。try?会将错误转换为可选值,当调用try?+函数或方法语句时候,如果函数或方法抛出错误,程序不会发崩溃,而返回一个nil,如果没有抛出错误则返回可选值。使用try!可以打破错误传播链条。错误抛出后传播给它的调用者,这样就形成了一个传播链条,但有的时候确实不想让错误传播下去,可以使用try!语句
先来看看代码。
do { let content = try NSString(contentsOfFile: "/file/path/str.txt", encoding: NSUTF8StringEncoding)
} catch {
print("read content fail")
}
```
里的 try 关键字是写在具体调用代码行上面的。也就是说,那个语句会有可能抛出异常,我们才在哪个语句前面加上 try 关键字。这种方式有一个好处。就是我们可以一目了然的看到那些代码会抛出异常。而不是将所有代码都混在 try-catch 语句块中。
结果(Result)
这个类是一个泛型枚举。一开始我还在想结果不就成或者失败没。为什么还要高这么多。通过对结果的封装(Swift的枚举相当强大)可以直接获取到更加详细的信息。来看代码:
public enum Result {
case success(Value)
case failure(Error)
// 对结果信息进一步处理,可以马上返回成功或者失败。
public var isSuccess: Bool {
switch self {
case .success:
return true
case .failure:
return false
}
}
public var isFailure: Bool {
return !isSuccess
}
// 对结果信息进一步处理,还可以直接返回成功的值。
public var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
public var error: Error? {
switch self {
case .success:
return nil
case .failure(let error):
return error
}
}
}
Swift
的枚举比较高级的用法如上。
- CustomStringConvertible,CustomDebugStringConvertible接口:这两个接口都是自定义输出的。之前如果要达到同样的效果就重写
toString
。现在还多了一种。注意这两个知识协议。还有一点就是要善于使用扩展extension
由于组织代码。比如下面代码就用扩展来实现了接口。之后这个类就有这个接口的功能。
extension Result: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
switch self {
case .success:
return "SUCCESS"
case .failure:
return "FAILURE"
}
}
}
Request(请求)
一上来先定义一套协议,和类型别名(类型别名)。如下:
public protocol RequestAdapter {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}
// 类似于block
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
public protocol RequestRetrier {
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
protocol TaskConvertible {
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
}
名字取得好就是好。见名知意
闭包声明:
public typealias ProgressHandler = (Progress) -> Void
defer关键字:表示在执行完方法最后的时候调用。比如文件打开后最后需要关闭。
-
internal(set)
,这种写法还比较少见。具体如下:open internal(set) var delegate: TaskDelegate { get { taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() } return taskDelegate } set { taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() } taskDelegate = newValue } }
表示
set
方法只有在内部模块才能访问。get
方法是都能访问的。 -
@discardableResult
:Swift 3.0 中方法的返回值必须有接收否则会报警告,当然其实主要目的是为了避免开发人员忘记接收返回值的情况,但是有些情况下确实不需要使用返回值可以使用"_"接收来忽略返回值。当然你也可以增加@discardableResult
声明,告诉编译器此方法可以不用接收返回值。比如:
@discardableResult
open func authenticate(
user: String,
password: String,
persistence: URLCredential.Persistence = .forSession)
-> Self
{
let credential = URLCredential(user: user, password: password, persistence: persistence)
return authenticate(usingCredential: credential)
}
```
-
@noescape: 用来标记一个闭包, 用法如下
func hostFunc(@noescape closure: () -> ()) -> Void
@noescape
字面意思是无法逃脱.closur
e 被@noescape
修饰, 则声明 closure 的生命周期不能超过 hostFunc, 并且, closure不能被hostFunc中的其他闭包捕获(也就是强持有)
func hostFunc(@noescape closure: () -> ()) -> Void {
//以下编译出错, closure 被修饰后, 不能被其他异步线程捕获
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
closure()
}
}
### 参考
[Swift 2.0初探:值得注意的新特性](http://www.cocoachina.com/swift/20150623/12231.html)
[关于 Swift 2.0 - 语言新特性与革新](http://www.cnblogs.com/theswiftworld/p/swift2.html?utm_source=tuicool&utm_medium=referral)
[Swift 2.0 异常处理](http://www.jianshu.com/p/96a7db3fde00)
[Swift 3那些不同以往的特性](http://www.jianshu.com/p/5d911fae5b2f)
[iOS开发系列--Swift 3.0](http://www.cnblogs.com/kenshincui/p/5594951.html)