CFNetwork

RSS for tag

Access network services and handle changes in network configurations using CFNetwork.

CFNetwork Documentation

Pinned Posts

Posts under CFNetwork tag

129 Posts
Sort by:
Post not yet marked as solved
3 Replies
154 Views
Hello, I have some networking code that checks whether a proxy is configured via: `CFStringRef host = (CFStringRef)CFDictionaryGetValue(globalSettings, kCFNetworkProxiesHTTPProxy);` `CFNumberRef port = (CFNumberRef)CFDictionaryGetValue(globalSettings, kCFNetworkProxiesHTTPPort);` I need to do this fairly frequently, so I am wondering if there is an announcer I can subscribe to instead?
Posted Last updated
.
Post not yet marked as solved
3 Replies
147 Views
Currently I am trying to create some shortcuts for my iOS 17 app. The AppIntent looks like this: class PostClass{ public init() { let url = URL(string: "http://myurl")! var request = URLRequest(url: url) request.httpMethod = "POST" struct Message: Encodable { let device_type: String } let message = Message( device_type: "device" ) let data = try! JSONEncoder().encode(message) request.httpBody = data request.setValue( "application/json", forHTTPHeaderField: "Content-Type" ) self.request = request } } var activateDevice = PostClass() @available(iOS 17, *) struct ActivateIntent: AppIntent { static let title: LocalizedStringResource = "Activate" func perform() async throws -> some IntentResult { let task = URLSession.shared.dataTask(with: activateDevice.request) { data, response, error in let statusCode = (response as! HTTPURLResponse).statusCode if statusCode == 200 { print("SUCCESS") } else { print("FAILURE") } } task.resume() return .result() } } Unfortunately, the shortcut throws an error after every second execution. I looked into the ips-file and saw the following traceback: Thread 2 Crashed: 0 Runner 0x1028ddd30 closure #1 in ActivateIntent.perform() + 388 1 CFNetwork 0x1a6f39248 0x1a6f2a000 + 62024 2 CFNetwork 0x1a6f57210 0x1a6f2a000 + 184848 3 libdispatch.dylib 0x1adced13c _dispatch_call_block_and_release + 32 4 libdispatch.dylib 0x1adceedd4 _dispatch_client_callout + 20 5 libdispatch.dylib 0x1adcf6400 _dispatch_lane_serial_drain + 748 6 libdispatch.dylib 0x1adcf6f64 _dispatch_lane_invoke + 432 7 libdispatch.dylib 0x1add01cb4 _dispatch_root_queue_drain_deferred_wlh + 288 8 libdispatch.dylib 0x1add01528 _dispatch_workloop_worker_thread + 404 9 libsystem_pthread.dylib 0x201dd4f20 _pthread_wqthread + 288 10 libsystem_pthread.dylib 0x201dd4fc0 start_wqthread + 8 Is there any way to locate the error with these information? Has it something to do with the fact that my code is not thread-safe? Or is there any internal bug? Grateful for any help, thanks in advance!
Posted
by alex_hsv.
Last updated
.
Post not yet marked as solved
4 Replies
263 Views
Hello, Our app has an internal job processing queue. All jobs are built as a NSOperation and involve a network request, and they are added to NSOperationQueue. When the app is closed while a request is being sent, the app sometimes crashes, but it also keeps crashing whenever we build the operation again and retry it. This happens rarely, but we can systematically reproduce it after a few tries with many jobs. This issue blocks the queue in our app. I understand if this is an issue deep within the framework, but it would be very useful to at least find a way to work around this issue so the queue can continue processing other jobs. The full crash report is attached. I also submitted a bug report: FB13734737 There seems to be an internal assertion fired in CFNetwork: Assertion failed: (CFReadStreamGetStatus(_stream.get()) == kCFStreamStatusNotOpen) function _onqueue_setupStream_block_invoke file HTTPRequestBody.cpp line 878. Crashed: com.apple.NSURLConnectionLoader 0 libsystem_kernel.dylib 0xa974 __pthread_kill + 8 1 libsystem_pthread.dylib 0x60ec pthread_kill + 268 2 libsystem_c.dylib 0x75b80 abort + 180 3 libsystem_c.dylib 0x74e70 err + 282 4 CFNetwork 0x1f73b8 CFHTTPCookieStorageUnscheduleFromRunLoop + 278252 5 libdispatch.dylib 0x3dd4 _dispatch_client_callout + 20 6 libdispatch.dylib 0x786c _dispatch_block_invoke_direct + 288 7 CFNetwork 0x259ab0 estimatedPropertyListSize + 33724 8 CoreFoundation 0x24b34 CFArrayApplyFunction + 72 9 CFNetwork 0x2599a0 estimatedPropertyListSize + 33452 10 CFNetwork 0x25c084 estimatedPropertyListSize + 43408 11 CoreFoundation 0x3762c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 12 CoreFoundation 0x368a8 __CFRunLoopDoSource0 + 176 13 CoreFoundation 0x35058 __CFRunLoopDoSources0 + 244 14 CoreFoundation 0x33d88 __CFRunLoopRun + 828 15 CoreFoundation 0x33968 CFRunLoopRunSpecific + 608 16 CFNetwork 0x25ac48 estimatedPropertyListSize + 38228 17 Foundation 0x9ca9c __NSThread__start__ + 732 18 libsystem_pthread.dylib 0x2a90 _pthread_start + 136 19 libsystem_pthread.dylib 0x1fcc thread_start + 8 This is how we build the operation: -(NSOperation*)operationForRequest:(Job*)job { NSURL *url = [NSURL URLWithString:job.url]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request setValue:@"application/json, application/xml, text/plain" forHTTPHeaderField:@"Accept"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"]; [request setValue:[NSString stringWithFormat:@"Bearer %@", [self getToken]] forHTTPHeaderField:@"Authorization"]; [request setHTTPMethod:job.method]; NSData *bodyData = [job.payload dataUsingEncoding:NSUTF8StringEncoding]; [request setHTTPBody:bodyData]; return [[NetworkOperation alloc] initWithRequest:request uuid:job.jobId completionHandler:^(NSString* jobId, NSData *data, NSURLResponse *response, NSError *error) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; Job *opJob = [Job objectInRealm:realm forPrimaryKey:jobId]; [self processJobResponse:opJob response:response data:data error:error realm:realm]; } }); }]; } This is how the NetworkOperation executes the request: - (void)main { NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionTask *task = [session dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (self.networkOperationCompletionBlock) { self.networkOperationCompletionBlock(self.uuid, data, response, error); self.networkOperationCompletionBlock = nil; } [self completeOperation]; }]; [task resume]; self.task = task; } crashlog3.crash
Posted
by nikilic.
Last updated
.
Post not yet marked as solved
1 Replies
130 Views
Im using Notions API to print out some data from one of my own pages in notion and im using URLSession to make the request then parsing the unwrapped data but nothing is being returned to my console and I know my endpoint and API key is correct. I've gone through the notion API documentation can't can't seem to find anything in it that I am not doing or doing wrong. Ill provide my code as well as the documentation I've been consulting: https://developers.notion.com/reference/intro import Foundation struct Page: Codable { let id: String let title: String } let endpoint = URL(string: "https://api.notion.com/v1/pages/8efc0ca3d9cc44fbb1f34383b794b817") let apiKey = "… redacted …" let session = URLSession.shared func makeRequest() { if let endpoint = endpoint { let task = URLSession.shared.dataTask(with: endpoint) { data, response, error in if let taskError = error { print("could not establish url request:\(taskError)") return } if let unwrapData = data { //safely unwrapping the data value using if let do { let decoder = JSONDecoder() //JSONDecoder method to decode api data, let codeUnwrappedData = try decoder.decode(Page.self,from: unwrapData) //type: specifies its a struct, from: passes the data parmeter that contains the api data to be decoded } catch { print("could not parse json data") } } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 { if let apiData = data { print(String(data: apiData, encoding: .utf8)!) } } else { print("unsuccessful http response:\(httpResponse)") } makeRequest() } } task.resume() } }
Posted
by Aor1105.
Last updated
.
Post not yet marked as solved
14 Replies
316 Views
I recently re-read Performing manual server trust authentication and noticed that it does not mention having to call SecTrustEvaluate (or its replacements) in client code (anymore). Is that implicitly taken care of by ATS?
Posted
by jzilske.
Last updated
.
Post not yet marked as solved
0 Replies
4.1k Views
Questions about FTP crop up from time-to-time here on DevForums. In most cases I write a general “don’t use FTP” response, but I don’t have time to go into all the details. I’ve created this post as a place to collect all of those details, so I can reference them in other threads. IMPORTANT Apple’s official position on FTP is: All our FTP APIs have been deprecated, and you should avoid using deprecated APIs. Apple has been slowly removing FTP support from the user-facing parts of our system. The most recent example of this is that we removed the ftp command-line tool in macOS 10.13. You should avoid the FTP protocol and look to adopt more modern alternatives. The rest of this post is an informational explanation of the overall FTP picture. This post is locked so I can keep it focused. If you have questions or comments, please do create a new thread with the Network tag and I’ll respond there. Don’t Use FTP FTP is a very old and very crufty protocol. Certain things that seem obvious to us now — like being able to create a GUI client that reliably shows a directory listing in a platform-independent manner — are not possible to do in FTP. However, by far the biggest problem with FTP is that it provides no security [1]. Specifically, the FTP protocol: Provides no on-the-wire privacy, so anyone can see the data you transfer Provides no client-authenticates-server authentication, so you have no idea whether you’re talking to the right server Provides no data integrity, allowing an attacker to munge your data in transit Transfers user names and passwords in the clear Using FTP for anonymous downloads may be acceptable (see the note below) but most other uses of FTP are completely inappropriate for the modern Internet. IMPORTANT You should only use FTP for anonymous downloads if you have an independent way to check the integrity of the data you’ve downloaded. For example, if you’re downloading a software update, you could use code signing to check its integrity. If you don’t check the integrity of the data you’ve downloaded, an attacker could substitute a malicious download instead. This would be especially bad in, say, the software update case. These fundamental problems with the FTP protocol mean that it’s not a priority for Apple. This is reflected in the available APIs, which is the subject of the next section. FTP APIs Apple provides two FTP APIs: All Apple platforms provide FTP downloads via NSURLSession Most Apple platforms (everything except watchOS) support CFFTPStream, which allows for directory listings, downloads, uploads, and directory creation. All of these FTP APIs are now deprecated: NSURLSession was deprecated for the purposes of FTP in the 2022 SDKs (macOS 13, {i{,Pad},tv}OS 16, watchOS 9) [2]. CFFTPStream was deprecated in the 2016 SDKs (macOS 10.11, {i{,Pad},tv}OS 9). CFFTPStream still works about as well as it ever did, which is not particularly well. Specifically: There is at least one known crashing bug (r. 35745763), albeit one that occurs quite infrequently. There are clear implementation limitations — like the fact that CFFTPCreateParsedResourceListing assumes a MacRoman text encoding (r. 7420589) — that will not be fixed. If you’re looking for an example of how to use these APIs, check out SimpleFTPSample. Note This sample has not been updated since 2013 and is unlikely to ever be updated given Apple’s position on FTP. The FTP support in NSURLSession has significant limitations: NSURLSession only supports FTP downloads; there is no support for uploads or any other FTP operations NSURLSession does not support resumable FTP downloads [3] NSURLSession background sessions only support HTTP and HTTPS, so you can’t run FTP downloads in the background on iOS If Apple’s FTP APIs are insufficient for your needs, you’ll need to write or acquire your own FTP library. Before you do that, however, consider switching to an alternative protocol. After all, if you’re going to go to the trouble of importing a large FTP library into your code base, you might as well import a library for a better protocol. The next section discusses some options in this space. Alternative Protocols There are numerous better alternatives to FTP: HTTPS is by far the best alternative to FTP, offering good security, good APIs on Apple platforms, good server support, and good network compatibility. Implementing traditional FTP operations over HTTPS can be a bit tricky. One possible way forward is to enable DAV extensions on the server. FTPS is FTP over TLS (aka SSL). While FTPS adds security to the protocol, which is very important, it still inherits many of FTP’s other problems. Personally I try to avoid this protocol. SFTP is a file transfer protocol that’s completely unrelated to FTP. It runs over SSH, making it a great alternative in many of the ad hoc setups that traditionally use FTP. Apple does not have an API for either FTPS or SFTP, although on macOS you may be able to make some headway by invoking the sftp command-line tool. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] In another thread someone asked me about FTP’s other problems, those not related to security, so let’s talk about that. One of FTP’s implicit design goals was to provide cross-platform support that exposes the target platform. You can think of FTP as being kinda like telnet. When you telnet from Unix to VMS, it doesn’t aim to abstract away VMS commands, so that you can type Unix commands at the VMS prompt. Rather, you’re expected to run VMS commands. FTP is (a bit) like that. This choice made sense back when the FTP protocol was invented. Folks were expecting to use FTP via a command-line client, so there was a human in the loop. If they ran a command and it produced VMS-like output, that was fine because they knew that they were FTPing into a VMS machine. However, most users today are using GUI clients, and this design choice makes it very hard to create a general GUI client for FTP. Let’s consider the simple problem of getting the contents of a directory. When you send an FTP LIST command, the server would historically run the platform native directory list command and pipe the results back to you. To create a GUI client you have to parse that data to extract the file names. Doing that is a serious challenge. Indeed, just the first step, working out the text encoding, is a challenge. Many FTP servers use UTF-8, but some use ISO-Latin-1, some use other standard encodings, some use Windows code pages, and so on. I say “historically” above because there have been various efforts to standardise this stuff, both in the RFCs and in individual server implementations. However, if you’re building a general client you can’t rely on these efforts. After all, the reason why folks continue to use FTP is because of it widespread support. [2] To quote the macOS 13 Ventura Release Notes: FTP is deprecated for URLSession and related APIs. Please adopt modern secure networking protocols such as HTTPS. (92623659) [3] Although you can implement resumable downloads using the lower-level CFFTPStream API, courtesy of the kCFStreamPropertyFTPFileTransferOffset property. Revision History 2024-04-15 Added a footnote about FTP’s other problems. Made other minor editorial changes. 2022-08-09 Noted that the FTP support in NSURLSession is now deprecated. Made other minor editorial changes. 2021-04-06 Fixed the formatting. Fixed some links. 2018-02-23 First posted.
Posted
by eskimo.
Last updated
.
Post not yet marked as solved
1 Replies
163 Views
I have an issue where performing a 'POST' request fails with a 400 when done on a device running iOS 16, but succeeds with a 200 on devices running iOS 17. I have not been able to find any explanations for this behavior. I've checked the request on both versions and it's identical in both versions of iOS, as far as I can tell. BodyStream : JSON object data Headers : Content-Type:application/json TimeoutInterval: 900
Posted Last updated
.
Post not yet marked as solved
2 Replies
232 Views
I have an app with IAP which uses a URLSession object to download files from a server. The download part of the code is: let request = URLRequest(url: fromURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeoutInterval) let (data, response) = try await downloadSession.data(for: request) This code has been working without trouble for over a year with thousands of downloads. Now I have a user with a new iPhone (iOS 17.3.1) which refuses to download, failing at the above code (judging by the high level logs). My question is this: What sort of things should we be looking at in order to diagnose this issue? So far we have done the following: He has no general download issue (eg Safari works fine) His network access is 'normal' and the problem persists when the network is changed (4G, wifi etc) He runs a VPN (Nord) but the problem persists when this is off He has no 3rd party AV software His phone is not in lockdown mode Any pointers would be appreciated! NB I have no physical access to his device (yet!)
Posted
by Baylward.
Last updated
.
Post not yet marked as solved
1 Replies
180 Views
Hi, Is there any way to return cached URLSession response and then reload and return? I want show cached response while load last version of API call, and if reload works fine, show the latest version of response and in case os failure, show cached response (if exists) Thanks!
Posted Last updated
.
Post not yet marked as solved
1 Replies
212 Views
I keep getting the nw_socket_handle_socket_event [C1.1.1:2] Socket SO_ERROR [61: Connection refused] when I am trying to enter the HabitDetailView and UserDetailView. The server gives the information for the Habit/User Collection View (/habits and /users), but it does not give any of the images, UserStats or Habit Stats. I've posted below how the APIRequest and APIService code looks like. It just has me stumped that it gives some of the info, but blocks other parts. API Request import UIKit protocol APIRequest { associatedtype Response var path: String { get } var queryItems: [URLQueryItem]? { get } var request: URLRequest { get } var postData: Data? { get } } extension APIRequest { var host: String { "localhost" } var port: Int { 8080 } } extension APIRequest { var queryItems: [URLQueryItem]? { nil } var postData: Data? { nil } } extension APIRequest { var request: URLRequest { var components = URLComponents() components.scheme = "http" components.host = host components.port = port components.path = path components.queryItems = queryItems var request = URLRequest(url: components.url!) if let data = postData { request.httpBody = data request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" } return request } } API Service import UIKit struct HabitRequest: APIRequest { typealias Response = [String: Habit] var habitName: String var path: String { "/habits" } } struct UserRequest: APIRequest { typealias Response = [String: User] var path: String { "/users"} } struct HabitStatisticsRequest: APIRequest { typealias Response = [HabitStatistics] var habitNames: [String]? var path: String { "/habitStats"} var queryItems: [URLQueryItem]? { if let habitNames = habitNames { return [URLQueryItem(name: "names", value: habitNames.joined(separator: ","))] } else { return nil } } } struct UserStatisticsRequest: APIRequest { typealias Response = [UserStatistics] var userIDs: [String]? var path: String { "/userStats"} var queryItems: [URLQueryItem]? { if let userIDs = userIDs { return [URLQueryItem(name: "ids", value: userIDs.joined(separator: ","))] } else { return nil } } } struct HabitLeadStatisticsRequest: APIRequest { typealias Response = UserStatistics var userID: String var path: String { "/userLeadingStats" + userID} } struct ImageRequest: APIRequest { typealias Response = UIImage var imageID: String var path: String { "/images/" + imageID } } enum APIRequestError: Error { case itemsNotFound case requestFailed(HTTPURLResponse) } extension APIRequest where Response: Decodable { func send() async throws -> Response { let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw APIRequestError.requestFailed(HTTPURLResponse()) } guard httpResponse.statusCode == 200 else { throw APIRequestError.itemsNotFound } let decoder = JSONDecoder() let decoded = try decoder.decode(Response.self, from: data) return decoded } } enum ImageRequestError: Error { case couldNotIntializeFromData case imageDataMissing } extension APIRequest where Response == UIImage { func send() async throws -> UIImage { let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw ImageRequestError.imageDataMissing } guard let image = UIImage(data: data) else { throw ImageRequestError.couldNotIntializeFromData } return image } }
Posted Last updated
.
Post not yet marked as solved
9 Replies
4.2k Views
I've just updated to Xcode 15.3 and iOS 17.4 (simulator). Every time I launch the app, I see a CPU spike keeping the CPU at 100% for about 30 seconds on a background thread. After those 30 seconds, there's a 'Thread Performance Checker' error posted on the console. This does not happen when using Xcode 15.2 and running on iOS 17.2. or iOS 16.4. Thread Performance Checker: Thread running at User-initiated quality-of-service class waiting on a thread without a QoS class specified (base priority 33). Investigate ways to avoid priority inversions PID: 70633, TID: 2132693 Backtrace ================================================================= 3 CFNetwork 0x000000018454094c estimatedPropertyListSize + 28648 4 CFNetwork 0x00000001843d7fc0 cfnTranslateCFError + 1864 5 libdispatch.dylib 0x000000010557173c _dispatch_client_callout + 16 6 libdispatch.dylib 0x0000000105573210 _dispatch_once_callout + 84 7 CFNetwork 0x00000001843d7f8c cfnTranslateCFError + 1812 8 CFNetwork 0x0000000184540814 estimatedPropertyListSize + 28336 9 libdispatch.dylib 0x000000010557173c _dispatch_client_callout + 16 10 libdispatch.dylib 0x0000000105573210 _dispatch_once_callout + 84 11 CFNetwork 0x0000000184540728 estimatedPropertyListSize + 28100 12 CFNetwork 0x0000000184540794 estimatedPropertyListSize + 28208 13 libdispatch.dylib 0x000000010557173c _dispatch_client_callout + 16 14 libdispatch.dylib 0x0000000105573210 _dispatch_once_callout + 84 15 CFNetwork 0x0000000184540780 estimatedPropertyListSize + 28188 16 CFNetwork 0x00000001844e8664 _CFNetworkHTTPConnectionCacheSetLimit + 191584 17 CFNetwork 0x00000001844e78dc _CFNetworkHTTPConnectionCacheSetLimit + 188120 18 CFNetwork 0x000000018439ce5c _CFURLCachePersistMemoryToDiskNow + 25460 19 CFNetwork 0x0000000184483068 _CFStreamErrorFromCFError + 609680 20 CFNetwork 0x000000018445105c _CFStreamErrorFromCFError + 404868 21 CFNetwork 0x000000018443a040 _CFStreamErrorFromCFError + 310632 22 CFNetwork 0x000000018453be14 estimatedPropertyListSize + 9392 23 CFNetwork 0x000000018440fa5c _CFStreamErrorFromCFError + 137092 26 CFNetwork 0x000000018445b398 _CFStreamErrorFromCFError + 446656 27 CFNetwork 0x0000000184459db8 _CFStreamErrorFromCFError + 441056 28 CFNetwork 0x000000018445cf60 _CFStreamErrorFromCFError + 453768 29 CFNetwork 0x0000000184541838 estimatedPropertyListSize + 32468 30 libdispatch.dylib 0x000000010556fec4 _dispatch_call_block_and_release + 24 31 libdispatch.dylib 0x000000010557173c _dispatch_client_callout + 16 32 libdispatch.dylib 0x0000000105579a30 _dispatch_lane_serial_drain + 916 33 libdispatch.dylib 0x000000010557a774 _dispatch_lane_invoke + 420 34 libdispatch.dylib 0x000000010557b6e4 _dispatch_workloop_invoke + 864 35 libdispatch.dylib 0x00000001055871a8 _dispatch_root_queue_drain_deferred_wlh + 324 36 libdispatch.dylib 0x0000000105586604 _dispatch_workloop_worker_thread + 488 37 libsystem_pthread.dylib 0x0000000106b87924 _pthread_wqthread + 284 38 libsystem_pthread.dylib 0x0000000106b866e4 start_wqthread + 8
Posted
by xmollv.
Last updated
.
Post not yet marked as solved
1 Replies
232 Views
So I'm getting some weird behavior when using an NSURLSession. Our app uses two separate sessions. And we aren't doing anything special when making them. This is it: let configuration = URLSessionConfiguration.default configuration.waitsForConnectivity = false configuration.timeoutIntervalForResource = 30 self.init(configuration: configuration) All we do is create data tasks using session.dataTask(with: request) After creating about 100+ data tasks and having them complete successfully with no problems, the session will hang for the full 30 seconds and return a -1001 "The request timed out." error. The next few tasks on this session will fail, and then all of a sudden the NSURLSession will start working again. Now the weird part is... since I have two NSURLSessions, only the one that has hit 100+ tasks times out. The other NSURLSession can still contact the same server URL and run tasks just fine. I did put this through the Network Instrument and one thing I did see is that up until the timeout happens the NSURLSession will use a single connection as expected. But after the 30s timeout and the recovery it looks like the NSURLSession decides behind the scenes to make a brand new connection and that's where the "recovery" comes from. Out server team swears this isn't them as other non-iOS clients hitting the same endpoint don't have this error. Any help? (Side note: I can fix this in my app by just making a new NSURLSession every 100 tasks, but that doesn't really help me understand what could be going wrong in the first place).
Posted Last updated
.
Post not yet marked as solved
10 Replies
1.4k Views
Hello, I am getting crashes on iOS 16 devices only regarding CFNetwork. Below is the full crash report. I am not able to reproduce it on my end. I've attached the .txt crash log below and also posted it below. CFNetworkCrashLog.txt Any help is appreciated, thank you! Crashed: com.apple.NSXPCConnection.m-user.com.apple.nsurlsessiond EXC_BREAKPOINT 0x00000001cfbbecec 7 Foundation 0x42054 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 8 Foundation 0x41f3c -[NSRunLoop(NSRunLoop) runUntilDate:] + 64 9 UIKitCore 0x4d66a4 -[UIEventFetcher threadMain] + 436 10 Foundation 0x5b518 __NSThread__start__ + 716 11 libsystem_pthread.dylib 0x16cc _pthread_start + 148 12 libsystem_pthread.dylib 0xba4 thread_start + 8 com.google.firebase.crashlytics.MachExceptionServer 0 App 0x387cfc FIRCLSProcessRecordAllThreads + 393 (FIRCLSProcess.c:393) 1 App 0x3880dc FIRCLSProcessRecordAllThreads + 424 (FIRCLSProcess.c:424) 2 App 0x395be0 FIRCLSHandler + 34 (FIRCLSHandler.m:34) 3 App 0x396400 FIRCLSMachExceptionServer + 521 (FIRCLSMachException.c:521) 4 libsystem_pthread.dylib 0x16cc _pthread_start + 148 5 libsystem_pthread.dylib 0xba4 thread_start + 8 GAIThread 0 libsystem_kernel.dylib 0xda8 mach_msg2_trap + 8 1 libsystem_kernel.dylib 0x13a1c mach_msg2_internal + 80 2 libsystem_kernel.dylib 0x13c5c mach_msg_overwrite + 388 3 libsystem_kernel.dylib 0x12ec mach_msg + 24 4 CoreFoundation 0x7aac4 __CFRunLoopServiceMachPort + 160 5 CoreFoundation 0x7bd08 __CFRunLoopRun + 1232 6 CoreFoundation 0x80eb0 CFRunLoopRunSpecific + 612 7 Foundation 0x42054 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 8 Foundation 0x41ee8 -[NSRunLoop(NSRunLoop) run] + 64 9 App 0x563d00 +[GAI threadMain:] + 64 10 Foundation 0x5b518 __NSThread__start__ + 716 11 libsystem_pthread.dylib 0x16cc _pthread_start + 148 12 libsystem_pthread.dylib 0xba4 thread_start + 8 com.apple.NSURLConnectionLoader 0 libsystem_kernel.dylib 0xda8 mach_msg2_trap + 8 1 libsystem_kernel.dylib 0x13a1c mach_msg2_internal + 80 2 libsystem_kernel.dylib 0x13c5c mach_msg_overwrite + 388 3 libsystem_kernel.dylib 0x12ec mach_msg + 24 4 CoreFoundation 0x7aac4 __CFRunLoopServiceMachPort + 160 5 CoreFoundation 0x7bd08 __CFRunLoopRun + 1232 6 CoreFoundation 0x80eb0 CFRunLoopRunSpecific + 612 7 CFNetwork 0x257ff0 _CFURLStorageSessionDisableCache + 61088 8 Foundation 0x5b518 __NSThread__start__ + 716 9 libsystem_pthread.dylib 0x16cc _pthread_start + 148 10 libsystem_pthread.dylib 0xba4 thread_start + 8 CommandHandler 0 libsystem_kernel.dylib 0xda8 mach_msg2_trap + 8 1 libsystem_kernel.dylib 0x13a1c mach_msg2_internal + 80 2 libsystem_kernel.dylib 0x13c5c mach_msg_overwrite + 388 3 libsystem_kernel.dylib 0x12ec mach_msg + 24 4 CaptiveNetwork 0x9d78 ConnectionGetCommandInfo + 160 5 CaptiveNetwork 0x7c54 __add_signal_port_source_block_invoke_2 + 244 6 libdispatch.dylib 0x3f88 _dispatch_client_callout + 20 7 libdispatch.dylib 0x7418 _dispatch_continuation_pop + 504 8 libdispatch.dylib 0x1aa58 _dispatch_source_invoke + 1588 9 libdispatch.dylib 0xb518 _dispatch_lane_serial_drain + 376 10 libdispatch.dylib 0xc18c _dispatch_lane_invoke + 384 11 libdispatch.dylib 0x16e10 _dispatch_workloop_worker_thread + 652 12 libsystem_pthread.dylib 0xdf8 _pthread_wqthread + 288 13 libsystem_pthread.dylib 0xb98 start_wqthread + 8 Thread 0 libsystem_kernel.dylib 0x12b0 __workq_kernreturn + 8 1 libsystem_pthread.dylib 0xe44 _pthread_wqthread + 364 2 libsystem_pthread.dylib 0xb98 start_wqthread + 8 Thread 0 libsystem_pthread.dylib 0xb90 start_wqthread + 254 Crashed: com.apple.NSXPCConnection.m-user.com.apple.nsurlsessiond 0 libobjc.A.dylib 0x6cec objc_opt_respondsToSelector + 48 1 libsystem_trace.dylib 0x6480 _os_log_fmt_flatten_object + 248 2 libsystem_trace.dylib 0x41a0 _os_log_impl_flatten_and_send + 1864 3 libsystem_trace.dylib 0x21bc _os_log + 152 4 libsystem_trace.dylib 0x7840 _os_log_impl + 24 5 CFNetwork 0x10dc08 _CFURLConnectionCopyTimingData + 34880 6 Foundation 0x64b620 message_handler_error + 360 7 libxpc.dylib 0x1179c _xpc_connection_call_event_handler + 152 8 libxpc.dylib 0x11be8 _xpc_connection_mach_event + 1020 9 libdispatch.dylib 0x4048 _dispatch_client_callout4 + 20 10 libdispatch.dylib 0x24104 _dispatch_mach_cancel_invoke + 128 11 libdispatch.dylib 0x21720 _dispatch_mach_invoke + 916 12 libdispatch.dylib 0xb518 _dispatch_lane_serial_drain + 376 13 libdispatch.dylib 0xc1c0 _dispatch_lane_invoke + 436 14 libdispatch.dylib 0x16e10 _dispatch_workloop_worker_thread + 652 15 libsystem_pthread.dylib 0xdf8 _pthread_wqthread + 288 16 libsystem_pthread.dylib 0xb98 start_wqthread + 8
Posted Last updated
.
Post not yet marked as solved
0 Replies
6.2k Views
Transport Layer Security (TLS) is the most important security protocol on the Internet today. Most notably, TLS puts the S into HTTPS, adding security to the otherwise insecure HTTP protocol. IMPORTANT TLS is the successor to the Secure Sockets Layer (SSL) protocol. SSL is no longer considered secure and it’s now rarely used in practice, although many folks still say SSL when they mean TLS. TLS is a complex protocol. Much of that complexity is hidden from app developers but there are places where it’s important to understand specific details of the protocol in order to meet your requirements. This post explains the fundamentals of TLS, concentrating on the issues that most often confuse app developers. Note If you’re working on TLS in the local environment, for example, to talk to a Wi-Fi based accessory, see TLS For Accessory Developers. Server Certificates For standard TLS to work the server must have a digital identity, that is, the combination of a certificate and the private key matching the public key embedded in that certificate. TLS Crypto Magic™ ensures that: The client gets a copy of the server’s certificate. The client knows that the server holds the private key matching the public key in that certificate. In a typical TLS handshake the server passes the client a list of certificates, where item 0 is the server’s certificate (the leaf certificate), item N is (optionally) the certificate of the certificate authority that ultimately issued that certificate (the root certificate), and items 1 through N-1 are any intermediate certificates required to build a cryptographic chain of trust from 0 to N. Note The cryptographic chain of trust is established by means of digital signatures. Certificate X in the chain is issued by certificate X+1. The owner of certificate X+1 uses their private key to digitally sign certificate X. The client verifies this signature using the public key embedded in certificate X+1. Eventually this chain terminates in a trusted anchor, that is, a certificate that the client trusts by default. Typically this anchor is a self-signed root certificate from a certificate authority. Note Item N is optional for reasons I’ll explain below. Also, the list of intermediate certificates may be empty (in the case where the root certificate directly issued the leaf certificate) but that’s uncommon for servers in the real world. Once the client gets the server’s certificate, it evaluates trust on that certificate to confirm that it’s talking to the right server. There are three levels of trust evaluation here: Basic X.509 trust evaluation checks that there’s a cryptographic chain of trust from the leaf through the intermediates to a trusted root certificate. The client has a set of trusted root certificates built in (these are from well-known certificate authorities, or CAs), and a site admin can add more via a configuration profile. This step also checks that none of the certificates have expired, and various other more technical criteria (like the Basic Constraints extension). Note This explains why the server does not have to include the root certificate in the list of certificates it passes to the client; the client has to have the root certificate installed if trust evaluation is to succeed. In addition, TLS trust evaluation (per RFC 2818) checks that the DNS name that you connected to matches the DNS name in the certificate. Specifically, the DNS name must be listed in the Subject Alternative Name extension. Note The Subject Alternative Name extension can also contain IP addresses, although that’s a much less well-trodden path. Also, historically it was common to accept DNS names in the Common Name element of the Subject but that is no longer the case on Apple platforms. App Transport Security (ATS) adds its own security checks. Basic X.509 and TLS trust evaluation are done for all TLS connections. ATS is only done on TLS connections made by URLSession and things layered on top URLSession (like WKWebView). In many situations you can override trust evaluation; for details, see Technote 2232 HTTPS Server Trust Evaluation). Such overrides can either tighten or loosen security. For example: You might tighten security by checking that the server certificate was issued by a specific CA. That way, if someone manages to convince a poorly-managed CA to issue them a certificate for your server, you can detect that and fail. You might loosen security by adding your own CA’s root certificate as a trusted anchor. IMPORTANT If you rely on loosened security you have to disable ATS. If you leave ATS enabled, it requires that the default server trust evaluation succeeds regardless of any customisations you do. Mutual TLS The previous section discusses server trust evaluation, which is required for all standard TLS connections. That process describes how the client decides whether to trust the server. Mutual TLS (mTLS) is the opposite of that, that is, it’s the process by which the server decides whether to trust the client. Note mTLS is commonly called client certificate authentication. I avoid that term because of the ongoing confusion between certificates and digital identities. While it’s true that, in mTLS, the server authenticates the client certificate, to set this up on the client you need a digital identity, not a certificate. mTLS authentication is optional. The server must request a certificate from the client and the client may choose to supply one or not (although if the server requests a certificate and the client doesn’t supply one it’s likely that the server will then fail the connection). At the TLS protocol level this works much like it does with the server certificate. For the client to provide this certificate it must apply a digital identity, known as the client identity, to the connection. TLS Crypto Magic™ assures the server that, if it gets a certificate from the client, the client holds the private key associated with that certificate. Where things diverge is in trust evaluation. Trust evaluation of the client certificate is done on the server, and the server uses its own rules to decided whether to trust a specific client certificate. For example: Some servers do basic X.509 trust evaluation and then check that the chain of trust leads to one specific root certificate; that is, a client is trusted if it holds a digital identity whose certificate was issued by a specific CA. Some servers just check the certificate against a list of known trusted client certificates. When the client sends its certificate to the server it actually sends a list of certificates, much as I’ve described above for the server’s certificates. In many cases the client only needs to send item 0, that is, its leaf certificate. That’s because: The server already has the intermediate certificates required to build a chain of trust from that leaf to its root. There’s no point sending the root, as I discussed above in the context of server trust evaluation. However, there are no hard and fast rules here; the server does its client trust evaluation using its own internal logic, and it’s possible that this logic might require the client to present intermediates, or indeed present the root certificate even though it’s typically redundant. If you have problems with this, you’ll have to ask the folks running the server to explain its requirements. Note If you need to send additional certificates to the server, pass them to the certificates parameter of the method you use to create your URLCredential (typically init(identity:certificates:persistence:)). One thing that bears repeating is that trust evaluation of the client certificate is done on the server, not the client. The client doesn’t care whether the client certificate is trusted or not. Rather, it simply passes that certificate the server and it’s up to the server to make that decision. When a server requests a certificate from the client, it may supply a list of acceptable certificate authorities [1]. Safari uses this to filter the list of client identities it presents to the user. If you are building an HTTPS server and find that Safari doesn’t show the expected client identity, make sure you have this configured correctly. If you’re building an iOS app and want to implement a filter like Safari’s, get this list using: The distinguishedNames property, if you’re using URLSession The sec_protocol_metadata_access_distinguished_names routine, if you’re using Network framework [1] See the certificate_authorities field in Section 7.4.4 of RFC 5246, and equivalent features in other TLS versions. Self-Signed Certificates Self-signed certificates are an ongoing source of problems with TLS. There’s only one unequivocally correct place to use a self-signed certificate: the trusted anchor provided by a certificate authority. One place where a self-signed certificate might make sense is in a local environment, that is, securing a connection between peers without any centralised infrastructure. However, depending on the specific circumstances there may be a better option. TLS For Accessory Developers discusses this topic in detail. Finally, it’s common for folks to use self-signed certificates for testing. I’m not a fan of that approach. Rather, I recommend the approach described in QA1948 HTTPS and Test Servers. For advice on how to set that up using just your Mac, see TN2326 Creating Certificates for TLS Testing. TLS Standards RFC 6101 The Secure Sockets Layer (SSL) Protocol Version 3.0 (historic) RFC 2246 The TLS Protocol Version 1.0 RFC 4346 The Transport Layer Security (TLS) Protocol Version 1.1 RFC 5246 The Transport Layer Security (TLS) Protocol Version 1.2 RFC 8446 The Transport Layer Security (TLS) Protocol Version 1.3 RFC 4347 Datagram Transport Layer Security RFC 6347 Datagram Transport Layer Security Version 1.2 RFC 9147 The Datagram Transport Layer Security (DTLS) Protocol Version 1.3 Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Revision History: 2024-03-19 Adopted the term mutual TLS in preference to client certificate authentication throughout, because the latter feeds into the ongoing certificate versus digital identity confusion. Defined the term client identity. Added the Self-Signed Certificates section. Made other minor editorial changes. 2023-02-28 Added an explanation mTLS acceptable certificate authorities. 2022-12-02 Added links to the DTLS RFCs. 2022-08-24 Added links to the TLS RFCs. Made other minor editorial changes. 2022-06-03 Added a link to TLS For Accessory Developers. 2021-02-26 Fixed the formatting. Clarified that ATS only applies to URLSession. Minor editorial changes. 2020-04-17 Updated the discussion of Subject Alternative Name to account for changes in the 2019 OS releases. Minor editorial updates. 2018-10-29 Minor editorial updates. 2016-11-11 First posted.
Posted
by eskimo.
Last updated
.
Post not yet marked as solved
2 Replies
210 Views
Ever since I've updated to Xcode 15.2 (I'm currently using Xcode 15.3) my console gets flooded with logs from CFNetwork and DataDetectorsUI. I've never seen those logs before. Is there a way to suppress them? I can't even find my own logs anymore without filters. CFNetwork Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> resuming, timeouts(25.0, 604800.0) QOS(0x15) Voucher (null) [Telemetry]: Activity <nw_activity 12:2[78557FD1-54F3-4B77-8C5C-57F500D67286] (reporting strategy default)> on Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> was not selected for reporting Connection 29: set is idle false Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> now using Connection 29 Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> sent request, body S 535 Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> received response, status 201 content U Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> done using Connection 29 Connection 29: set is idle true HTTP/2 Connection 29 Stream 7 ended successfully true Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> request *** is NOT allowed to set HSTS for main doc (null) Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> response ended Task <50C80E0E-9DA5-428F-A473-9D0228088022>.<4> finished successfully Connection 19: cleaning up Connection 19: done Applying proxy auth: response=(null), proxyURL=(null), request=(null), credentials=(null), handle=0x11c2f7d30 Connection 21: cleaning up Connection 23: cleaning up Connection 22: cleaning up DataDetectorsUI operation 0x10ff7e560 operation 0x10ff7e560 is discarded Calling the completion block for 0x10ff7e560 dispatchScanQueryCreationWithCompletionBlock of operation <DDTextKitOperation: 0x10ff7e560> completion block: success: 0 operation 0x10ff7e560 operation 0x10ff7e560 operation 0x10fb2f850 really creating scan query in operation 0x10fb2f850! operation 0x10fb2f850
Posted Last updated
.
Post not yet marked as solved
1 Replies
736 Views
I’m working on an independent watchOS app which is primarily designed to to collect and periodically send location updates to a server. The UI features a toggle that allows the user to turn this capability on or off at their discretion. The typical use case scenario would be for the user to turn the toggle on in the morning, put the app in the background and then go about their day. Given the limitations and restrictions regarding background execution on watchOS, in an ideal situation, I would be able to upload the stored location updates about every 15-20 minutes. With an active complication on the watch face, it’s my understanding that this should be possible. I’ve implemented background app refresh and indeed, I do see this reliably being triggered every 15-20 minutes or so. In my handle(_:) method, I process the WKApplicationRefreshBackgroundTask like this: func handle(_ backgroundTasks: Set&lt;WKRefreshBackgroundTask&gt;) { backgroundTasks.forEach { task in switch task { case let appRefreshBackgroundTask as WKApplicationRefreshBackgroundTask: // start background URL session to upload data; watchOS will perform the request in a separate process so that it will continue to run even if our app gets // terminated; when the system is done transferring data, it will call this method again and backgroundTasks will contain an instance of // WKURLSessionRefreshBackgroundTask which will be processed below startBackgroundURLSessionUploadTask() scheduleNextBackgroundAppRefresh() appRefreshBackgroundTask.setTaskCompletedWithSnapshot(false) case let urlSessionTask as WKURLSessionRefreshBackgroundTask: // add urlSessionTask to the pendingURLSessionRefreshBackgroundTasks array so we keep a reference to it; when the system completes the upload and // informs us via a URL session delegate method callback, then we will retrieve urlSessionTask from the pendingURLSessionRefreshBackgroundTasks array // and call .setTaskCompletedWithSnapshot(_:) on it pendingURLSessionRefreshBackgroundTasks.append(urlSessionTask) // create another background URL session using the background task’s sessionIdentifier and specify our extension as the session’s delegate; using the same // identifier to create a second URL session allows the system to connect the session to the upload that it performed for us in another process let configuration = URLSessionConfiguration.background(withIdentifier: urlSessionTask.sessionIdentifier) let _ = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) default: task.setTaskCompletedWithSnapshot(false) } } } And here is how I'm creating and starting the background URL session upload task: func startBackgroundURLSessionUploadTask() { // 1. check to see that we have locations to report; otherwise, just return // 2. serialize the locations into a temporary file // 3. create the background upload task let configuration = URLSessionConfiguration.background(withIdentifier: Constants.backgroundUploadIdentifier) configuration.isDiscretionary = false configuration.sessionSendsLaunchEvents = true let backgroundUrlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) let request: URLRequest = createURLRequest() // this is a POST request let backgroundUrlSessionUploadTask = backgroundUrlSession.uploadTask(with: request, fromFile: tempFileUrl) backgroundUrlSessionUploadTask.countOfBytesClientExpectsToSend = Int64(serializedData.count) // on average, this is ~1.5 KB backgroundUrlSessionUploadTask.countOfBytesClientExpectsToReceive = Int64(50) // approximate size of server response backgroundUrlSessionUploadTask.resume() } Note that I'm not setting the .earliestBeginDate property on the backgroundUrlSessionUploadTask because I'd like the upload to start as soon as possible without any delay. Also, this same class (my WatchKit application delegate) conforms to URLSessionTaskDelegate and I have implemented urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:) and urlSession(_:task:didCompleteWithError:). In my testing (on an actual Apple Watch Ultra running watchOS 9.3.1), I've observed that when the system performs the background app refresh, I always receive a callback to myhandle(_:) method. But when I start the background URL session upload task (in startBackgroundURLSessionUploadTask()), I was expecting that when the upload completes, I'd receive another call to myhandle(_:) method with an instance of WKURLSessionRefreshBackgroundTask but this doesn't seem to happen consistently. Sometimes I do see it but other times, I don't and when I don't, the data doesn't seem to be getting uploaded. On a side note, most of the time, startBackgroundURLSessionUploadTask() gets called as a result of my code handling a background app refresh task. But when the user turns off the toggle in the UI and I stop the location updates, I need to report any stored locations at that time and so I call startBackgroundURLSessionUploadTask() to do that. In that specific case, the upload seems to work 100% of the time but I definitely don't see a callback to my handle(_:) method when this occurs. Am I wrong in expecting that I should always be getting a callback to handle(_:) when a background URL session upload task completes? If so, under what circumstances should this occur? Thanks very much!
Posted
by bmt22033.
Last updated
.
Post marked as solved
2 Replies
421 Views
Are there any drawbacks (e.g. slower request) by setting assumesHTTP3Capable to true if the server doesn't support it? Is it OK to set it globally in the app for all URLRequests or it should be set depending on to which server the app makes the request? Thanks!
Posted
by bastawa.
Last updated
.
Post not yet marked as solved
1 Replies
409 Views
I'm using Firebase Auth and Firestore on my app. When I already signed in, my code in init() function workes fine and my screens automatically updated when data load. But when I first sign in, I get The operation couldn’t be completed. (NSURLErrorDomain error -1011.) error and the rest of the code dont work as it should be. What is the issue and how can I solve it?
Posted
by sloutales.
Last updated
.
Post not yet marked as solved
1 Replies
297 Views
I'm unable to set the "secure" property of HTTPCookieProperty (for initializing HTTPCookie) to false. tried: .secure: "FALSE" .secure: "false" .secure: false but all of above resulted in "cookie.isSecure" being true. The only thing that worked was not providing the field at all (which made the property default to false). Seems like a niche bug?
Posted
by jcp_jcp.
Last updated
.
Post not yet marked as solved
3 Replies
1.2k Views
I have an app which uses URLSession-based networking and URLCache for storing network requests on disk. I noticed that when the storage size of URLCache reaches the diskCapacity, the eviction strategy seems to be to remove all entries, which is a problem in my use case. So I decided to write an URLCache subclass which would provide a custom storage for cached responses and which would implement LRU eviction strategy with better control. As URLCache's documentation states, subclassing for this purpose should be supported: The URLCache class is meant to be used as-is, but you can subclass it when you have specific needs. For example, you might want to screen which responses are cached, or reimplement the storage mechanism for security or other reasons. However, I ran into problems with trying to use this new URLCache subclass with URLSession networking. I have a test resource which I fetch using HTTP GET. The response headers contain: Cache-Control: public, max-age=30 Etag: &lt;some-value&gt; When using the standard, non-subclassed URLCache, the first request loads the data from network as expected (verified with HTTP proxy). The second request doesn't go to the network, if done within first 30 seconds, as expected. Subsequent requests after 30 seconds cause conditional GETs with Etag, as expected. When using a URLCache subclass, all requests load the data from network - max-age doesn't seem to matter, and no conditional GETs are made. It seems that the URLCache does something special to the CachedURLResponse instances after they're loaded from its internal storage, and this something affects how URLSession handles the HTTP caching logic. What am I missing here? I've written a very minimal URLCache implementation to demonstrate this problem. This class stores and loads CachedURLResponse instances using NSKeyedArchiver / NSKeyedUnarchiver, and it supports only zero or one response. Note that there are no calls to super - this is by design, since I want to use my own storage. Here's the implementation: class CustomURLCache: URLCache { let cachedResponseFileURL = URL(filePath: NSTemporaryDirectory().appending("entry.data")) // MARK: Internal storage func read() -&gt; CachedURLResponse? { guard let data = try? Data(contentsOf: cachedResponseFileURL) else { return nil } return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! CachedURLResponse } func store(_ cachedResponse: CachedURLResponse) { try! (try! NSKeyedArchiver.archivedData(withRootObject: cachedResponse, requiringSecureCoding: false)).write(to: cachedResponseFileURL) } // MARK: URLCache Overrides override func cachedResponse(for request: URLRequest) -&gt; CachedURLResponse? { read() } override func getCachedResponse(for dataTask: URLSessionDataTask, completionHandler: @escaping (CachedURLResponse?) -&gt; Void) { guard let response = read() else { completionHandler(nil) return } completionHandler(response) } override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) { store(cachedResponse) } override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for dataTask: URLSessionDataTask) { store(cachedResponse) } } My test case: func test() { let useEvictingCache = false let config = URLSessionConfiguration.default if useEvictingCache { config.urlCache = CustomURLCache() } else { config.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 1024 * 1024 * 100) } self.urlSession = URLSession(configuration: config) let url = URL(string: "https://example.com/my-test-resource")! self.urlSession?.dataTask(with: URLRequest(url: url), completionHandler: { data, response, error in if let data { print("GOT DATA with \(data.count) bytes") } else if let error { print("GOT ERROR \(error)") } }).resume() }
Posted
by nzhuk.
Last updated
.