#
参考简书文集:https://www.jianshu.com/nb/13910345
基于 Artsy’s implementation. 使用RxSwift
more >>人比黄花瘦
转:https://www.jianshu.com/p/0418f5051185
在Mac系统中进入“钥匙串访问”,选择“钥匙串访问”-“证书助理”-“从证书颁发机构请求证书…”,如图1所示:
钥匙串请求证书
—>填写前两项,并选择“存储到磁盘”,如图2所示:
MemberCenter
—>Certificates, Indentifiers & Profiles
—>Certificates
,如图3所示:—>在图4页面,点击右上角加号,添加一个证书:
—>选择In-House and Ad Hoc,点继续,如图5所示:
—>如图6:Choose File选择第1步的CSR文件上传,点击generate生成cer证书,下载后双击安转(需要输入Mac的密码)
点击右上角的加号按钮,如图7所示:
—>有两项需要填(如图8所示),name为描述可以随便填写.bundleID必须与APP的bundleID完全一致(如图9所示)。至于App Services根据自己需要选择。最后点击继续->submit->done
选择Provisioning Profile->ALL,点击右上角加号:
—>选择刚创建的AppID,点击继续,如图12所示:
—>选择cer证书点击继续.图13 给最终生成的Profile文件命名方便自己识别。然后点击生成,并下载双击打开。至此证书环节完毕
这一步开始前,需要将手机插入电脑,并调试选项选择真机。(防止无法Archive),如果没有真机,可以选择iOS Device(这个没有测试)。
用Xcode打开对应APP。Product->Scheme->Edit scheme,填写Archive name,即为打包后的ipa名字,如图14所示
完成之后会生成一个ipa包。
要发布还必须有一个plist文件,在Xcode6之前会自动生成一个plist文件,但是Xcode6之后需要我们自己创建plist,文章最后提供一个plist模板,复制并重命名为plist后打开根据提示操作即可.图18为plist的截图,可以看到有三个URL,分别存放ipa,大小图标。下图的1(ipa)、2(大图)、3(小图)填写我们自己生成的URL,即将ipa和大小图标放在我们自己的服务器,当用Safari打开plist时会根据填的plist里面的1、2、3对应的URL来下载安装ipa、大小图标。
那么plist放在哪里呢(即Safari打开plist的URL是多少呢)?苹果对plist存放地址有要求,必须是https的,如果没有https网站,我们可以把plist放在https://git.oschina.net。
具体做法就是在上面创建一个项目(不能是私人的),然后将编辑好的plist传到项目,最后将plist的URL赋值下来,比如https://git.oschina.net/waitwait/companytest/blob/master/MDDTest.plist。然后我们在Safari中输入:itms-services:///?action=download-manifest&url=https://git.oschina.net/waitwait/companytest/blob/master/MDDTest.plist
就可以安装了(Safari会解析itms-services:///?action=download-manifest&url=
)。
注意,有简友反应oschina的https不能使用,其实公用的https链接经常会被封掉,可以试试github,百度云,七牛云存储等等。另外,最好还是用自己的
Safari操作的具体流程是:
下面是plist模板的文本形式,将其复制到文本然后重命名.plist,用Xcode打开按照提示编辑即可。
1 | <?xml version="1.0" encoding="UTF-8"?> |
https://developer.apple.com/documentation/foundation/urlsessionconfiguration#2888288
配置对象,用于定义URL会话的行为和策略。
软件开发工具包
骨架
一个对象定义了使用对象上传和下载数据时要使用的行为和策略。上传或下载数据时,创建配置对象始终是您必须采取的第一步。您可以使用此对象来配置打算用于对象的超时值,缓存策略,连接要求和其他类型的信息。URLSessionConfiguration
URLSession
URLSession
在使用它来初始化会话对象之前,正确配置对象非常重要。会话对象会复制您提供的配置设置,并使用这些设置来配置会话。配置完成后,会话对象将忽略您对该对象所做的任何更改。如果您需要修改传输策略,则必须更新会话配置对象并使用它创建新对象。URLSessionConfiguration``URLSessionConfiguration``URLSession
注意在某些情况下,此配置中定义的策略可能会被NSURLRequest
为任务提供的对象指定的策略覆盖。在请求对象上指定的任何策略都会受到尊重,除非会话的策略更具限制性。例如,如果会话配置指定不应允许蜂窝网络,则该NSURLRequest
对象不能请求蜂窝网络。
有关使用配置对象创建会话的更多信息,请参阅URLSession
。
class var
default: URLSessionConfiguration
返回新创建的默认会话配置对象。
class var ephemeral: URLSessionConfiguration
返回不使用缓存,Cookie或凭证的持久性存储的会话配置。
class func background(withIdentifier: String)
返回一个会话配置对象,允许在后台执行HTTP和HTTPS上传或下载。
var identifier: String?
配置对象的后台会话标识符。
var httpAdditionalHeaders: [AnyHashable : Any]?
与请求一起发送的附加头文件的字典。
var networkServiceType: NSURLRequest.NetworkServiceType
网络服务的类型。
var allowsCellularAccess: Bool
一个布尔值,用于确定是否应通过蜂窝网络进行连接。
var timeoutIntervalForRequest: TimeInterval
等待其他数据时使用的超时间隔。
var timeoutIntervalForResource: TimeInterval
资源请求应该允许的最大时间量。
var sharedContainerIdentifier: String?
应该下载后台URL会话中的文件的共享容器的标识符。
var waitsForConnectivity: Bool
一个布尔值,指示会话是否应等待连接变为可用或者立即失败。
var httpCookieAcceptPolicy: HTTPCookie.AcceptPolicy
决定何时应该接受Cookie的策略常量。
var httpShouldSetCookies: Bool
一个布尔值,用于确定请求是否应包含来自Cookie存储的Cookie。
var httpCookieStorage: HTTPCookieStorage?
用于在此会话中存储Cookie的Cookie存储。
class HTTPCookieStorage
管理cookie存储的单一对象(共享实例)。
class HTTPCookie
表示HTTP cookie的对象。它是一个不可变的对象,从包含cookie属性的字典中初始化。
var tlsMaximumSupportedProtocol: SSLProtocol
在此会话中进行连接时客户端应请求的最大TLS协议版本。
var tlsMinimumSupportedProtocol: SSLProtocol
协议协商期间应该接受的最小TLS协议。
var urlCredentialStorage: URLCredentialStorage?
提供身份验证凭据的凭证存储。
var urlCache: URLCache?
用于向会话中的请求提供缓存响应的URL缓存。
var requestCachePolicy: NSURLRequest.CachePolicy
一个预定义常量,用于确定何时从缓存中返回响应。
var sessionSendsLaunchEvents: Bool
一个布尔值,指示在传输完成时是否应该在后台继续或启动应用程序。
var isDiscretionary: Bool
一个布尔值,用于确定是否可以根据系统的判断来调度后台任务以获得最佳性能。
var shouldUseExtendedBackgroundIdleMode: Bool
var protocolClasses: [AnyClass]?
在会话中处理请求的额外协议子类的数组。
class URLProtocol
一个NSURLProtocol
对象处理加载协议特定的URL数据。在NSURLProtocol
类本身是一个抽象类,可以为与特定URL方案的URL处理基础设施。您可以为您的应用支持的任何自定义协议或URL方案创建子类。
使用多路径TCP提高网络可靠性
使用iOS设备中的可用收音机来提高应用程序的网络可靠性和性能。
var multipathServiceType: URLSessionConfiguration.MultipathServiceType
指定用于通过Wi-Fi和蜂窝接口传输数据的多路径TCP连接策略的服务类型。
enum URLSessionConfiguration.MultipathServiceType
指定多路径TCP使用的服务类型的常量。
var httpMaximumConnectionsPerHost: Int
同时连接到给定主机的最大数量。
var httpShouldUsePipelining: Bool
一个布尔值,用于确定会话是否应使用HTTP流水线。
var connectionProxyDictionary: [AnyHashable : Any]?
包含有关在此会话中使用的代理信息的字典。
var waitsForConnectivity: Bool
一个布尔值,指示会话是否应等待连接变为可用或者立即失败。
class func backgroundSessionConfiguration(String)
返回一个会话配置对象,允许在后台执行HTTP和HTTPS上传或下载。
弃用
将网站数据存入内存
通过从URL会话创建数据任务,将数据直接接收到内存中。
上传数据到网站
将数据从应用程序发布到服务器。
在后台下载文件
创建在您的应用处于非活动状态时下载文件的任务
class URLSession
协调一组相关网络数据传输任务的对象。
class URLSessionTask
一个任务,比如下载一个特定的资源,由一个URL会话进行。
如果您是网络管理员,您可以将 Multipath TCP 与 iOS 搭配使用,以加强与您的目标主机的连接。
https://github.com/below/MultipathTCP
iOS 支持 Multipath TCP (MPTCP),并且允许 iPhone 或 iPad 通过蜂窝移动数据连接建立与目标主机的备份 TCP 连接。
网络管理员可能需要使用 MPTCP。使用标准家庭网络的客户无需启用 MPTCP。
MPTCP 是对传输控制协议 (TCP) 规范的一组扩展。凭借 MPTCP,客户端可以通过不同网络适配器连接到有多个连接的同一目标主机。这可在各主机间建立强大而高效的数据连接,并且与现有的网络基础设施兼容。
iPhone 和 iPad 在具有活跃的蜂窝移动数据连接的情况下使用 MPTCP 来建立两个连接:
如果 Wi-Fi 不可用或无响应,iOS 会使用蜂窝移动数据连接。
MPTCP 使用 TCP 选项域 30,这是互联网编号分配机构 (IANA) 专为此用途而保留的。如果 iOS 设备与服务器之间的任何中间盒(如路由器或交换机)都不支持 MPTCP,则 iOS 会建立标准的 TCP 连接。
例如,当您向 Siri 提问时,Siri 会尝试通过 Wi-Fi 建立 MPTCP 连接。如果连接成功,Siri 会通过蜂窝移动数据建立备用连接。如果 Wi-Fi 不可用或不可靠,则 MPTCP 会立即在后台切换到蜂窝移动数据。
MPTCP 与现有网络兼容。如果某个网络不支持 MPTCP,则客户端会使用标准的 TCP 连接。不过,网络管理员必须查看其防火墙政策,以确保所有介入设备都允许 TCP 选项 30,才能以未修改的方式传递信号。
很多商用路由器会将未知的 TCP 选项替换为 NOOP 数据。询问您的供应商如何开启 TCP 选项。
有关非 Apple 制造的产品或非 Apple 控制或测试的独立网站的信息仅供参考,不代表 Apple 的任何建议或保证。Apple 对于第三方网站或产品的选择、性能或使用不承担任何责任。Apple 对于第三方网站的准确性和可靠性不作任何担保。互联网的使用具有一定风险。请联系供应商以了解其他信息。其他公司和产品名称可能是其各自所有公司的商标。
ios7.0之后,通过wifi建立tcp连接时,还会通过3g/4g建立一个备用的连接。
然后搜索这玩意儿如何用法:
不要填dns就ok了。
为了验证这种说法,
验证一: 连接了盯盯拍的wifi(老板买了一个盯盯拍给大家琢磨),点进细节一看,dhcp的信息里果然没有dns信息。
验证二:连接了一个支持ap模式的wifi,记录下dhcp模式下的ip、掩码、网关,然后设置为static模式,填写 刚才记录的信息。注意不要填写dns信息。果然可以同时连接ap模式的ipc,也可以3g/4g翻看网页。只是这种做法下,手机顶部没有扇形的连接信号。
Moya是一个基于Alamofire开发的,轻量级的Swift网络层。Moya的可扩展性非常强,可以方便的RXSwift,PromiseKit和ObjectMapper结合。
如果你的项目刚刚搭建,并且是纯Swift的,非常推荐以Moya为核心去搭建你的网络层。另外,如果你对Alamofire的源码感兴趣,推荐我之前的一篇博客:
Moya除了依赖Alamofire,还依赖Result。Result用一种枚举的方式提供函数处理结果:
.success(let data)
// 成功,关联值是数据.falure(let error)
// 失败, 关联值是错误原因
本文的讲解顺序:Moya的实现原理 -> Moya的设计理念 -> Moya与RxSwift,ObjectMapper一起工作
–
分析任何代码都是从它的接口开始的。
我们先来看看通过Moya如何去写一个网络API请求。Moya中,通过协议TargetType来表示这是一个API请求。
协议要求提供以下属性,
1 | public protocol TargetType { |
通过枚举来管理一组API,比如
1 | public enum GitHub { |
当然也可以让你的Class/Stuct来实现TargetType协议,使用枚举可以方便的管理一组API,优点是方便复用baseURL,method等,缺点是不得不写大量的Switch语句
然后,在进行API请求的时候,要创建MoyaProvider
,接着调用Request方法进行实际的请求
1 | let provider = MoyaProvider<GitHub>() |
可以看到,Moya通过协议来定义一个网络请求,并且属性都是只读的。协议意味着是依赖于抽象,而不是具体的实现,这样更易控制藕合,并且容易扩展;只读的意味着不可变状态,不可变状态会让你的代码行为可预测。
通过功能划分,Moya大致分为几个模块
Request,包括TargetType,Endpoint,Cancellable集中类型
Provider,网络请求的枢纽,Provider会把TargetType
转换成Endpoint
再转换成URLRequest
交给Alamofire去实际执行
Response,回调给上层的数据结构,支持filter
,mapJSON
等方法
Alamofire封装,通过桥接的方式对上层隐藏alamofire的细节
Plguins,插件。moya提供了插件来给给外部。包括四个方法,这里知道方法就好,后文会具体的讲解插件的方法在何时工作。
1 | public protocol PluginType { |
为了更好的讲解Moya的处理流程,我画了一张图(用Sketch画的):
第一眼看到这张图的时候,你肯定是困惑的,我们来一点点讲解图中的过程。通过上文的讲解我们知道,Provider这个类是网络请求的枢纽,它接受一个TargetType(请求),并且通过闭包的方式给上层回调。
那么,我们来看看Provider的初始化方法:
1 | public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping, |
初始化的时候的几个参数:
endpointClosure
作用是把TargetType转换成EndPoint,EndPoint是Moya网络请求的一个中间态。requestClosure
作用是把Endpoint转换成URLRequeststubClosure
是用来桩测试的,也就是模拟服务端假数据,这里先不管。manager
,实际请求的Alamofire的SessionManagerplugins
, 插件trackInflights
,是否要跟踪重复网络请求在Moya中,请求是按照如图的方式进行转换的。其中,TargetType到Endpoint的转换是通过闭包endpointClosure
来完成的。闭包的输入是TargetType,输出是EndPoint
1 | public typealias EndpointClosure = (Target) -> Endpoint<Target> |
在初始化Provider的时候,endpointClosure
有默认参数,可以看到默认实现只是由Target创建了一个Endpoint
1 | public final class func defaultEndpointMapping(for target: Target) -> Endpoint<Target> { |
接着,通过requestClosure
将Endpoing映射到URLRequest。这是你最后修改Request的机会,同样它也有默认参数。
1 | public final class func defaultRequestMapping(for endpoint: Endpoint<Target>, closure: RequestResultClosure) { |
为什么要用闭包进行TargetType->Endpoint->URLRequest映射呢?
为了在灵活性和易用性之间进行平衡。
对于大部分API请求来说,使用Moya提供的默认闭包映射足以,这样大多数时候根本不需要关心着两个闭包的内容。但是有时候,有一些额外需求,比如对所有API请求增加额外的HTTP Header
,moya通过闭包的方式开发者可以去修改这些内容。
1 | let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in |
为什么要引入requestClosure,把底层的URLRequest暴露给外部?
我想有几点原因
- 有些信息只有
URLRequest
创建之后才能知晓,比如cookie。URLRequest
属性很多,大多不常用,比如allowsCellularAccess
,没必在Moya这一层封装。- Endpoint到URLRequest的映射是通过闭包回调的方式进行的,意味着你可以异步回调。
为什么要引入Endpoint,不直接映射成URLRequest?也就是说,两步闭包映射变成一步
为了保证TargetType维持不可变状态(属性全都是只读),同时给外部友好的API。通过Endpoint你可以方便的:添加新的参数,添加HttpHeader….
这里我们先不管流程图中的Plugins(插件),先顺着流程走,接下来我们到了一个叫做stub的模块。stub是一个测试相关的概念,通过stub你可以返回一些假数据。
Moya的stub原理很简单,如果Provider决定Stub,那么就返回Endpoint中的假数据;否则就进行实际的网络请求。
Moya通过StubClosure
闭包开决定stub的模式:
1 | public typealias StubClosure = (Target) -> Moya.StubBehavior |
模式分为三种
1 | public enum StubBehavior { |
返回数据的时候,就是简单的根据EndPoint中的假数据闭包:
1 | switch endpoint.sampleResponseClosure() { |
默认的Endpoint的sampleResponseClosure
。
1 | sampleResponseClosure: { .networkResponse(200, target.sampleData) }, |
Moya采用了这种简单粗暴,但是效果却很好的stub方式。
这里很多人肯定会问,假如我不用Moya,我还想返回假数据,我该咋么做呢?
答案是URLProtocol。通过URLProtocol可以拦截网络请求,你可以把网络请求重定向到假数据。
对于NSURLConnection发起的请求可以直接拦截。在拦截NSURLSession的时候有一点tricky,因为URLSession支持的拦截是通过URLSessionConfiguration的属性protocolClasses来决定的,一般的做法是hook URLSession的初始化方法
init(configuration: URLSessionConfiguration, delegate: URLSessionDelegate?, delegateQueue: OperationQueue?)
,然后把想要的拦截Protocol注册到URLSessionConfiguration中。
Plugin提供了一种插件的机制让你可以在网络请求的关键节点插入代码,比如显示小菊花扽等。
这里我们再看一下这张图,可以清楚的看到四个plugin方法作用的时机。
Note:Plugin没有用范型编程,所以不要尝试在plugin中进行JSON解析然后传递给上层。
Moya提供了四种Plugin:
Moya并没有对Response进行特殊处理,仅仅是把Alamofire层面返回的数据封装成
Moya.Response
,然后再调用convertResponseToResult
进一步封装成Result<Moya.Response, MoyaError>
类型交给上层
1 | public func convertResponseToResult(_ response: HTTPURLResponse?, request: URLRequest?, data: Data?, error: Swift.Error?) -> |
如果你要对Response进一步转换成JSON,可以用Response的方法,比如:
1 | func mapJSON(failsOnEmptyData: Bool = true) throws -> Any {/* */} |
到这里,Moya做的事情已经很清晰了:提供一种面向协议的接口来进行网络请求的编写;提供灵活的闭包接口来自定义请求;提供插件来让客户端在各个节点去介入网络请求;返回原始的请求数据给层。
Moya最大的优点:
网络API请求应该是可以被取消的。也就是说,在发起一个API请求后,客户端应该能够有一个数据结构能够取消这个请求。Moya返回协议Cancellable
给客户端
1 | public protocol Cancellable { |
这符合《最少知识原则》。客户端不知道请求是什么,它唯一能做的就是
cancel
。
在内部实现中,引入了一个CancellableWrapper
来进行实际的Cancel动作包装,返回的实际实现协议的类型就是它
1 | internal class CancellableWrapper: Cancellable { |
为什么要用一个CancellableWrapper进行包装呢?
原因是:
SimpleCancellable
即可。1 | let cancellableToken = CancellableWrapper() |
而CancellableToken
中,取消网络请求:
1 | public final class CancellableToken: Cancellable{ |
这里用到了信号量,为了防止两个线程同时执行cancel操作。
Moya采用桥接的方式,把Alamofire的API细节进行封装,详细的封装细节可见Moya+Alamofire.swift。总的来说,采用了两种方式:
简单的类型桥接
1 | //用typealias进行桥接 |
协议桥接
Alamofire对外的接口是Request类型。而Moya需要在Plugin中对Reuqest进行暴露,用协议怼Request进行了桥接
1 | public protocol RequestType { |
然后,暴露给外部的接口变成了:
1 | func willSend(_ request: RequestType, target: TargetType) |
采用桥接的方式对外隐藏了细节,这样即使有一天Moya的底层依赖不再是Alamofire,对上层也没有任何影响。
moya的很多设计原则是值得借鉴的,这些原则在软件开发领域是通用的。
Swift是一个面向协议的语言。(这句话我好像在博客里写过好多遍了)
比如:
1 | protocol TargetType {} //表示这是一个API请求 |
面向协议的最大优点是:
同时,Swift协议支持扩展,你可以通过协议扩展为协议中的方法提供默认实现
1 | public extension TargetType { |
不可变状态会让你的代码可预测,可测试。
不可变状态是函数式编程里的一个核心概念。在Moya中,很多状态都是不可变的。典型的是:
1 | public protocol TargetType { |
同样,还体现在Endpoint中:
1 | open class Endpoint<Target> { |
Swift中,函数是一等公民,意味着你可以把它作为函数的参数和返回值。当一个函数作为函数参数或者返回值的时候,称之为高阶函数。
高阶函数让你的代码可以输入/输出逻辑,这样就增加了灵活性。
比如在Provider初始化的时候传入的三个闭包:
1 | endpointClosure: = MoyaProvider.defaultEndpointMapping, |
高阶函数配合函数默认值,是Swift开发中进行接口暴露的常用技巧。
插件是我认为Moya这个框架最吸引我的地方。
通过在各个节点暴露出插件的接口,让Moya的日志,授权,小菊花等功能无需耦合到核心代码里,同时也给外部足够的灵活性,能够插入任何想要的代码。
使用枚举来保证类型安全是Swift中常用技巧。
比如:
1 | //返回假数据 |
Moya的错误处理主要采用了两种方式:
抛异常:
1 | public func filterSuccessfulStatusAndRedirectCodes() throws -> Response { |
Result类型:
1 | func convertResponseToResult(****) -> Result<Moya.Response, MoyaError> { |
在Swift中,通过Result类型来处理异步错误是一个很常见也很有效的做法。
使用Result类型最大的好处是可以不用每一步都处理错误。
比如,类似这个链式调用,每一步都有可能出错,通过Result类型,我们可以在最后统一处理错误。
1 | provider.request(...).filter().mapJSON.filter().{ result in |
RxSwift是一个响应式编程框架,它是语言层面的扩展,改变的是你写代码的方式,与具体业务细节无关。
如果你对RxSwift并不熟悉,推荐我之前的一篇博客:RxSwift使用教程。另外,我还维护了一个awesome-rxswift列表。
Moya核心代码并没有支持RxSwift,那样就与另外一个框架耦合在一起了。Moya采用了扩展的方式,让Moya支持RxSwift,具体代码参见RxMoya。
在扩展中,提供了RxMoyaProvider
类:
1 | class RxMoyaProvider<Target>: MoyaProvider<Target> |
在请求的时候,不再通过闭包进行回调,而是返回Observable<Response>
(一个可监听的信号源)。
1 | open func request(_ token: Target) -> Observable<Response> { |
然后,通过extension扩展ObservableType为Response提供各种响应式处理方法
1 | extension ObservableType where E == Response { |
ObjectMapper 是一个用来做把JSON转换成Struct/Class的Swift框架。
实际开发中,先把JSON转换成对象再进行下一步UI操作是很常见的事情。结合RxSwift,我们可以很容易的把ObjectMapper插入响应式处理的一个节点中:
1 | extension ObservableType where E == Response { |
通过这个方法,可以进行信号中包含的信息转换:
于是,通过RxSwift和ObjectMapper,就可以这么处理:
1 | rxRrovider.request(.targetType) |
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true