本篇文章给大家谈谈在Swift中,如何从UrlSession获取错误类型,以及swifturlencode的知识点,同时本文还将给你拓展iOSSwiftURLSessionPOST请求因慢速API调用
本篇文章给大家谈谈在 Swift 中,如何从 UrlSession 获取错误类型,以及swift urlencode的知识点,同时本文还将给你拓展iOS Swift URLSession POST 请求因慢速 API 调用而重复、iOS Swift 应用程序随机 EXC_BAD_ACCESS 崩溃:swift_bridgeObjectRetain swift_retain swift::RefCounts
- 在 Swift 中,如何从 UrlSession 获取错误类型(swift urlencode)
- iOS Swift URLSession POST 请求因慢速 API 调用而重复
- iOS Swift 应用程序随机 EXC_BAD_ACCESS 崩溃:swift_bridgeObjectRetain swift_retain swift::RefCounts
- iOS URLSession 简单的文件下载
- iOS 中的 URLSession
在 Swift 中,如何从 UrlSession 获取错误类型(swift urlencode)
如何解决在 Swift 中,如何从 UrlSession 获取错误类型
我的网络逻辑中有以下代码:
let task = urlSession.dataTask(with: request) { [weak self] (data,response,error) in
if let error = error {
if error.localizedDescription.contains("The request timed out") {
// request timeout stuff ...
} else {
// other errors
}
}
}
字符串匹配 localizedDescription
不是好的代码实践。如何获取错误类型,就像在 catch
子句中一样?
解决方法
您可以将其转换为 URLError
,然后查看 code
:
if let error = error as? URLError {
switch error.code {
case .timedOut: ...
case .cannotFindHost: ...
default: ...
}
}
或者,如果您只关心一种情况,您可以使用 if case
:
if let error = error as? URLError,case .timedOut = error.code {
...
}
或
if let error = error as? URLError,error.code == .timedOut {
...
}
iOS Swift URLSession POST 请求因慢速 API 调用而重复
如何解决iOS Swift URLSession POST 请求因慢速 API 调用而重复
我有一个下载任务,它首先调用一个 REST API,服务器需要为其生成一个相当大的文件,该文件需要几分钟才能生成,因为它是 cpu 和磁盘 IO 密集型的。客户端等待服务器用它生成的文件的 URL 给出一个 JSON 响应。文件下载在得到第一个结果后开始。
对于生成特别大文件的调用,这会导致服务器响应非常慢,我看到我的代码未启动的重复请求。
最初在服务器端工作的人告诉我重复的请求。然后我设置了一种检查网络流量的方法。这是通过设置连接到有线网络的 Mac 并启用网络共享并使用 Proxyman 检查从 iPhone 到 API 服务器的流量来完成的。我在网络层看到同一 API 请求的多个实例,但我的代码从未收到通知。
代码如下
@objc class OfflineMapDownloadManager : NSObject,URLSessionDelegate,URLSessionDownloadDelegate {
@objc func download(){
let config = URLSessionConfiguration.background(withIdentifier: "OfflineMapDownloadSession")
config.timeoutIntervalForRequest = 500
config.shouldUseExtendedBackgroundIdleMode = true
config.sessionSendsLaunchEvents = true
urlSession = URLSession(configuration: config,delegate: self,delegateQueue: nil)
getMapUrlsFromServer(bounds)
}
func getMapUrlsFromServer(){
var urlString = "http://www.fake.com/DomakeMap.PHP"
if let url = URL(string: urlString) {
let request = NSMutableuRLRequest(url: url)
//...Real code sets up a JSON body in to params...
request.httpBody = params.data(using: .utf8 )
request.setValue("application/json",forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.timeoutInterval = 500
urlSession?.configuration.timeoutIntervalForRequest = 500
urlSession?.configuration.timeoutIntervalForResource = 500
request.httpShouldUsePipelining = true
let backgroundTask = urlSession?.downloadTask(with: request as URLRequest)
backgroundTask?.countOfBytesClientExpectsToSend = Int64(params.lengthOfBytes(using: .utf8))
backgroundTask?.countOfBytesClientExpectsToReceive = 1000
backgroundTask?.taskDescription = "Map Url Download"
backgroundTask?.resume()
}
}
func urlSession(_ session: URLSession,downloadTask: URLSessionDownloadTask,didFinishDownloadingTo location: URL) {
if (downloadTask.taskDescription == "CTM1 Url Download") {
do {
let data = try Data(contentsOf: location,options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data,options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String,AnyObject> {
if let ctm1Url = jsonResult["CTM1Url"] as? String {
if let filesize = jsonResult["filesize"] as? Int {
currentDownload?.ctm1Url = URL(string: ctm1Url)
currentDownload?.ctm1FileSize = Int32(filesize)
if (Int32(filesize) == 0) {
postDownloadFailed()
} else {
startCtm1FileDownload(ctm1Url,filesize)
}
}
}
}
} catch {
postDownloadFailed()
}
}
}
此下载类还有更多内容,因为它会在第一个 api 调用完成后下载实际文件。由于问题发生在该代码执行之前,我没有将其包含在示例代码中。
Proxyman 的日志显示 API 调用在 (minutes:seconds) 46:06,47:13,48:21,49:30,50:44,52:06,53:45 结束
看起来请求以刚超过 1 分钟的间隔重复。有一个 API 字段,我可以在其中输入任何值,服务器会回显给我。我在那里放了一个用 CACurrentMediaTime() 生成的时间戳,并登录 Proxyman 显示它确实是相同的 API 调用,所以我的代码不可能被多次调用。似乎 iOS 网络层正在重新发出 http 请求,因为服务器需要很长时间才能响应。这最终会导致服务器出现问题并且 API 失败。
任何帮助将不胜感激。
解决方法
我认为问题在于此 API 调用使用 URLSessionConfiguration.background(withIdentifier:)。
使用此方法初始化一个配置对象,适合在应用程序在后台运行时传输数据文件。 使用此对象配置的会话将传输的控制权移交给系统,该系统在单独的进程中处理传输。在 iOS 中,即使应用程序本身已关闭,此配置也可以继续传输暂停或终止。
所以问题在于,由于这种错误的 API 使用,系统不必要地重试了您的请求。
这是我推荐的 -
- 使用默认会话配置(不是后台)。
- 执行此 API 调用以启动此长作业,不要让客户端等待此作业,一旦此作业启动,服务器端就会将 job_id 返回给客户端。
- 客户端现在可以使用该 job_id 值每 X 秒轮询一次服务器以了解作业的状态,甚至可以根据需要在客户端显示进度。
- 当作业完成,客户端下次轮询时,它会获取这个大文件的下载 URL。
- 下载文件(根据需要使用默认/后台会话配置)。
这听起来很像 TCP 重传。如果客户端发送一个 TCP 段,并且服务器在短时间内没有确认收到,则客户端假定该段没有到达目的地,并再次发送该段。这是一种比 URLSession 低得多的机制。
此 API 使用的 HTTP 服务器应用程序(例如 Apache、IIS、LigHTTPd、nginx 等)可能配置为使用响应数据进行确认,以节省打包和帧开销。如果是这样,并且如果响应数据花费的时间比客户端的 TCP 重传超时时间长,您将获得此行为。
你有连接的数据包捕获吗?如果没有,请尝试使用 tcpdump 收集一个并在 Wireshark 中查看它。如果我是对的,您会看到多个请求,并且它们都具有相同的序列号。
至于如果这是问题如何解决,我不确定。服务器应该在收到请求后立即确认。
iOS Swift 应用程序随机 EXC_BAD_ACCESS 崩溃:swift_bridgeObjectRetain swift_retain swift::RefCounts
如何解决iOS Swift 应用程序随机 EXC_BAD_ACCESS 崩溃:swift_bridgeObjectRetain swift_retain swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>
我不断收到来自随机用户的随机崩溃报告。不幸的是,我无法定期重现这一点。用户说崩溃是在 discussionViewController
中随机发生的。所有崩溃报告都有类似的内容:
0 libswiftCore.dylib 0x00000001a53face4 swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1> >::incrementSlow(swift::RefCountBitsT<(swift::RefCountInlinedness)1>,unsigned int) + 60 (atomic:1003)
1 libswiftCore.dylib 0x00000001a53c59e0 swift_retain + 124 (RefCount.h:813)
2 libswiftCore.dylib 0x00000001a5401d60 swift_bridgeObjectRetain + 56 (SwiftObject.mm:585)
3 APPNAME 0x0000000102b59734 closure #1 in discussionViewController.fetchPostData() + 7916
这是完整的崩溃日志和崩溃的线程:
Hardware Model: iphone11,6
Process: APPNAME [11770]
Path: /private/var/containers/Bundle/Application/.../APPNAME.app/APPNAME
Identifier: ----
Version: 62 (62)
AppStoretools: 12E262
AppVariant: 1:iphone11,6:13
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: ---- [1824]
Date/Time: 2021-06-17 12:07:01.4346 +1000
Launch Time: 2021-06-17 12:06:56.4993 +1000
OS Version: iPhone OS 14.6 (18F72)
Release Type: User
Baseband Version: 3.04.01
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x8000000000000010 -> 0x0000000000000010 (possible pointer authentication failure)
VM Region Info: 0x10 is not in any region. Bytes before following region: 4339515376
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
--->
__TEXT 102a7c000-102a94000 [ 96K] r-x/r-x SM=COW ...APPNAME.app/APPNAME
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL,Code 0xb
Terminating Process: exc handler [11770]
Triggered by Thread: 3
Thread 3 name:
Thread 3 Crashed:
0 libswiftCore.dylib 0x00000001a53face4 swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1> >::incrementSlow(swift::RefCountBitsT<(swift::RefCountInlinedness)1>,unsigned int) + 60 (atomic:1003)
1 libswiftCore.dylib 0x00000001a53c59e0 swift_retain + 124 (RefCount.h:813)
2 libswiftCore.dylib 0x00000001a5401d60 swift_bridgeObjectRetain + 56 (SwiftObject.mm:585)
3 APPNAME 0x0000000102b59734 closure #1 in discussionViewController.fetchPostData() + 7916
4 APPNAME 0x0000000102ad09d4 thunk for @escaping @callee_guaranteed (@guaranteed Data?,@guaranteed NSURLResponse?,@guaranteed Error?) -> () + 132 (<compiler-generated>:0)
5 CFNetwork 0x00000001a1b0a3dc __40-[__NSURLSessionLocal taskForClassInfo:]_block_invoke + 540 (LocalSession.mm:687)
6 CFNetwork 0x00000001a1b1c768 __49-[__NSCFLocalSessionTask _task_onqueue_didFinish]_block_invoke + 244 (LocalSessionTask.mm:584)
7 libdispatch.dylib 0x00000001a10d1a84 _dispatch_call_block_and_release + 32 (init.c:1466)
8 libdispatch.dylib 0x00000001a10d381c _dispatch_client_callout + 20 (object.m:559)
9 libdispatch.dylib 0x00000001a10db004 _dispatch_lane_serial_drain + 620 (inline_internal.h:2557)
10 libdispatch.dylib 0x00000001a10dbc34 _dispatch_lane_invoke + 456 (queue.c:3862)
11 libdispatch.dylib 0x00000001a10e64bc _dispatch_workloop_worker_thread + 764 (queue.c:6589)
12 libsystem_pthread.dylib 0x00000001ed04a7a4 0x1ed047000 + 14244
13 libsystem_pthread.dylib 0x00000001ed05174c 0x1ed047000 + 42828
我已验证 discussionViewController.fetchPostData()
不会强制解开任何可选选项,没有 try!
并且在任何地方都使用 [weak self]
和 self?
。该函数非常大,所以我很难缩小崩溃发生的范围。
iOS URLSession 简单的文件下载
import Foundation
class DownloadFileManager: NSObject, URLSessionDownloadDelegate {
private var downloadedFilePath = ""
private var progressHandler: ((_ progress: Float) -> ())?
private var completionHandler: ((_ path: String) -> ())?
static let shared = DownloadFileManager()
private lazy var urlSession : URLSession = {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
return session
}()
func downloadFile(_ url: String!, progressHandler: ((_ progress: Float) -> ())?, completionHandler: ((_ path: String) -> ())?) {
let downloadTask = urlSession.downloadTask(with: URL(string: url)!)
downloadTask.resume()
self.progressHandler = { (progress) in
if let block = progressHandler {
block(progress)
}
}
self.completionHandler = { (path) in
if let block = completionHandler {
block(self.downloadedFilePath)
}
}
}
func cancel() {
urlSession.getAllTasks { (downloadTasks) in
for downloadTask in downloadTasks {
downloadTask.cancel()
}
}
// urlSession.getTasksWithCompletionHandler { (dataTasks, uploadTasks, downloadTasks) in
//
// }
//
// urlSession.invalidateAndCancel()
}
}
extension DownloadFileManager {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
if let block = progressHandler {
block(progress)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let block = completionHandler {
block(downloadedFilePath)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
if let suggestedFilename = downloadTask.response?.suggestedFilename {
downloadedFilePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/" + suggestedFilename
do {
try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath: downloadedFilePath))
} catch { }
}
}
}
附:AFNetworking 下载
private lazy var manager : AFURLSessionManager = {
let manager = AFURLSessionManager.init(sessionConfiguration: .default)
return manager
}()
func download(_ name: String, _ url: String!, progressHandler: ((_ progress: Float) -> ())?, completionHandler: ((_ path: String) -> ())?) {
let task = manager.downloadTask(with: URLRequest(url: URL(string: url)!), progress: { (progress) in
let p = Float(progress.completedUnitCount) / Float(progress.totalUnitCount)
if let block = progressHandler {
block(p)
}
}, destination: { (url, response) -> URL in
let path = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/" + (response.suggestedFilename ?? "")
return URL(fileURLWithPath: path)
}) { (response, url, error) in
}
task.resume()
}
func cancel() {
for task in manager.downloadTasks {
task.cancel()
}
}
iOS 中的 URLSession
URLSession
NSURLSession 是 iOS7 中新的网络接口,与 NSURLConnection 是并列的.
当程序在前台时,NSURLSession 和 NSURLConnection 大部分可以互相替代.NSURLSession 支持后台网络操作,除非用户强行关闭.
NSURLSession 提供的功能:
1> 通过 URL 将数据下载到内存;
2> 通过 URL 将数据下载到文件系统;
3> 将数据上传到指定的 URL;
4> 在后台完成上述功能.
5> 支持下载,断点续传,后台上传 / 下载,后台上传 / 下载任务跟进
对于小型数据,如用户登录,下载小图像,JSON&XML 仍然使用 NSURLConnection 的异步或同步方法即可.
NSURLSession 的使用:
使用 NSURLSessionConfiguration 来配置 NSURLSession 对象
用 NSURLSession 对象来启动一个 NSURLSessionTask 对象
也可以使用系统全局的 sharedSession 单例来满足大多数的需求
注意:
相比较 NSURLConnection 的返回处理,NSURLSession 提供了灵活的数据返回方式,可以使用简单的 block 方式来处理返回数据,也可以使用更强大的 delegate.
NSURLSessionConfiguration
用于定义和配置 NSURLSession 对象;
每一个 NSURLSession 对象都可以设置不同的 NSURLSessionConfiguration,从而满足应用内不同类型的网络请求.
NSURLSessionConfiguration 的三种类型:
1> defaultSessionConfiguration: 默认的 session 配置,类似 NSURLConnection 的标准配置,使用硬盘来存储缓存数据.
2> ephemeralSessionConfiguration: 临时的 session 配置,与默认配置相比,这个配置不会将缓存、cookie 等存在本地,只会存在内存里,所以当程序退出时,所有的数据都会消失
3> backgroundSessionConfiguration: 后台 session 配置,与默认配置类似,不同的是会在后台开启另一个线程来处理网络数据.
NSURLSessionTask
NSURLSession 使用 NSURLSessionTask 来具体执行网络请求的任务.
NSURLSessionTask 支持网络请求的取消、暂停和恢复,比如下载文件暂停之后再恢复就能够自动从上次的进度继续下载 .
NSURLSessionTask 还能获取数据的读取进度 .
NSURLSessionTask 的三种类型:
1> NSURLSessionDataTask 处理一般的 NSData 数据对象,比如通过 GET 或 POST 方式从服务器获取 JSON 或 XML 返回等等,但不支持后台获取.
2> NSURLSessionUploadTask 用于上传文件,支持后台上传 .
3> NSURLSessionDownloadTask 用于下载文件,支持后台下载 .
通过 HTTP PUT 方法实现文件上传的步骤
实例化 NSMutableURLRequest 并指定 HTTPMethod 为 PUT .
设置请求的授权 :
1> 授权字符串格式:用户名:口令 .
2> 授权模式:Basic base64 编码的授权字符串 .
3> 为 HTTPHeaderField 的 Authorization 赋值 .
文件上传的请求部分
// 1. URLRequest
NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/xxx.png"];
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f];
requestM.HTTPMethod = @"PUT";
// 设置用户授权
// 1> 授权字符串(用户名+密码)
NSString *authStr = @"admin:123456";
// 2> BASE 64编码
NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64Str = [authData base64EncodedStringWithOptions:0];
NSString *auth = [NSString stringWithFormat:@"BASIC %@", base64Str];
[requestM setValue:auth forHTTPHeaderField:@"Authorization"];
我们今天的关于在 Swift 中,如何从 UrlSession 获取错误类型和swift urlencode的分享已经告一段落,感谢您的关注,如果您想了解更多关于iOS Swift URLSession POST 请求因慢速 API 调用而重复、iOS Swift 应用程序随机 EXC_BAD_ACCESS 崩溃:swift_bridgeObjectRetain swift_retain swift::RefCounts
本文标签: