美文网首页Swifty Coding程序员swift
跟着Alamofire(4.0.0)学Swift3(一)

跟着Alamofire(4.0.0)学Swift3(一)

作者: 纸简书生 | 来源:发表于2016-09-30 17:28 被阅读7447次

    最近一段时间搞得东西太多了。服务端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 <NSCopying, NSCoding>
    
    @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 APINSError 都已经被 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<Value> {
        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字面意思是无法逃脱. closure 被@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)

    相关文章

      网友评论

      本文标题:跟着Alamofire(4.0.0)学Swift3(一)

      本文链接:https://www.haomeiwen.com/subject/zbfryttx.html