For client certificate authentication I've to use URLSessionDelegate in the custom class for handling all requests. The problem is the class is not deinitialize after a request made.
Code:
class Request: NSObject, URLSessionDelegate, URLSessionTaskDelegate{
func request(){
let url:URL = URL(string: "https://stackoverflow.com")!
let config = URLSessionConfiguration.default
URLSession(configuration: config, delegate: self, delegateQueue: .main).dataTask(with: url) { (data, response, error) in
print("Received")
}.resume()
}
func request2(){
let url:URL = URL(string: "https://stackoverflow.com")!
let config = URLSessionConfiguration.default
URLSession(configuration: config).dataTask(with: url) { (data, response, error) in
print("Received")
}.resume()
}
deinit {
print("Class deinit...")
}
}
When calling Request().request() then deinit is not called. Calling Request().request2() then deinit is called.
I am not sure how to resolve this problem. Please help me to find out the solution. Thank you...
URLSession keeps a strong reference to its delegate (self in your case). Check the official documentation:
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until the app terminates.
You can invalidate the session using its methods finishTasksAndInvalidate() or invalidateAndCancel(). After that URLSession will release the strong reference to its delegate.
I guess you code example is just to demonstrate the behavior, but anyway I have to mention it's not a good practice to create a new URLSession for every single URLRequest.
In request() when assigning self to the delegate you are creating a strong reference to the instance of the class. This causes a retention cycle and stops the instance being deinitialised. The answer is to weekly capture the delegate:
URLSession(configuration: config, delegate: self, delegateQueue: .main).dataTask(with: url) { [weak self] (data, response, error) in
print("Received")
}.resume()
In request2() without a delegate this obviously isn't happening.
There are numerous blogs and tutorials about this, so rather than repeating them all I'll let you google for the detail about capture list and strong and weak references :-)
Related
I am fairly new to swift(1 week) and iOS programming, and my problem is that I seem to miss some basic understanding. Below you see a function that is triggered by a background notification. I can and have verified that I receive the background notification reliably and the app comes active (printout of the raw data values on the console) As long as the app is in the foreground everything is working just as expected, it gets fired, and sends a single https request. The background triggers come on a timer every minute.
Now the whole thing changes when the app enters into the background. In this case I am still getting the triggers through the notification (console printout) and I can see in the debugger the same function that works like a charm in the foreground stumbles. It still works, it still gets fired, but a data packet is sent only so often, randomly as it seems between 2 and 30 minutes.
let config = URLSessionConfiguration.background(withIdentifier: "org.x.Reporter")
class queryService {
let defaultSession = URLSession(configuration: config)
var dataTask: URLSessionDataTask?
var errorMessage = ""
func getSearchResults(baseURL: String, searchTerm: String) {
dataTask?.cancel()
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData;
config.timeoutIntervalForRequest = 10
if var urlComponents = URLComponents(string: "https://host.com/reportPosition.php") {
urlComponents.query = "\(searchTerm)"
guard let url = urlComponents.url else { return }
dataTask = defaultSession.dataTask(with: url)
}
// 7
dataTask?.resume()
}
}
Try using dataTaskWithCompletion so you can see what's going wrong in the error.
URLSession.shared.dataTask(with: URL.init(string: "")!) { (data, response, error) in
if error != nil {
// Error
}
}.resume()
https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask
EDIT
What you want to do is for background you get completions via delegate call backs so when you init ur URLSession do so using the following func
URLSession.init(configuration: URLSessionConfiguration.init(), delegate: self, delegateQueue: OperationQueue.init())
https://developer.apple.com/documentation/foundation/urlsession/1411597-init
Then conform ur class to the URLSessionDelegate like so
class queryService, URLSessionDelegate {
then implement the delegate methods listed here for call backs
https://developer.apple.com/documentation/foundation/urlsessiondelegate
EDIT2
Here is good tutorial about it
https://www.raywenderlich.com/158106/urlsession-tutorial-getting-started
I'm fetching xml data from server by using NSURLSession and NSURLSessionDelegate. Depends on some conditions I'm connecting with server. If I'm connecting with server everything works fine without any error but if I'm not connecting (depends on condition) to server and moving to another View Controller (by using storyboard?.instantiateViewControllerWithIdentifier(id)) I'm getting the following IOS error:
'A background URLSession with identifier backgroundSession already exists!'
Here is my code:
class MainClass: UITableViewController, NSURLSessionDelegate {
var task_service = NSURLSessionDataTask?()
override func viewDidLoad() {
super.viewDidLoad()
if(condition) {
getXMLFromServer()
}
}
func getXMLFromServer(){
task_service = getURLSession().dataTaskWithRequest() {
(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
// Fetching data from server
// In the end
self.session.invalidateAndCancel()
}
}
}
func getURLSession() -> NSURLSession {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 30.0
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
return session
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)) // Bypassing SSL error
}
}
EDIT: Found the reason for the error.
Error occurred because of the creation of NSURLSession in the Called View Controller.Called VC contains code to download PDF from server. But I don't know how to solve this. Below is the code of Called VC
class MainFormsController: UIViewController, UIPickerViewDelegate, UITextFieldDelegate, NSURLSessionDownloadDelegate, UIDocumentInteractionControllerDelegate, MFMailComposeViewControllerDelegate{
var download_task = NSURLSessionDownloadTask?()
var backgroundSession = NSURLSession()
override func viewDidLoad() {
super.viewDidLoad()
createNSURLSession()
}
/** Error occurred while creating this NSURLSession **/
func createNSURLSession() {
let backgroundSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("backgroundSession")
backgroundSession = NSURLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
}
func downloadPDF() {
//Download PDF
download_task = backgroundSession.downloadTaskWithURL(url)
download_task?.resume()
}
}
Add this code in your MainFormsController:
deinit {
self.backgroundSession.finishTasksAndInvalidate();
}
Your code probably calls createNSURLSession() more than once which invalidate the NSURLSession behavior, as documentation says:
"You must create exactly one session per identifier (specified when
you create the configuration object). The behavior of multiple
sessions sharing the same identifier is undefined."
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html
Make sure createNSURLSession is called only once (singletone) for the life cycle of your app.
I think you already have an URLSession with identifier backgroundSession. First Call
- (void)invalidateAndCancel
on that. and then try with your code.
When trying to create my own session object NSURLSession() and request an url I get an unrecognized selector exception but when I use the shared session NSURLSession.sharedSession() everything works fine. How come?
var url = NSURL(string: "http:/www.google.com")
if url != nil {
//throws unrecognized selector when dataTaskWithURL is called
let session=NSURLSession()
session.dataTaskWithURL(url!)
//works
let sharedSession=NSURLSession.sharedSession()
sharedSession.dataTaskWithURL(url!)
}
You have to init URLSession with a configuration:
URLSession(configuration: .default)
or use shared session
URLSession.shared
In SWIFT 3.0 and up:
URLSession.shared.dataTask(with: url, completionHandler:
{
(data, response, error) in
//Your code
}).resume()
Aside from the shared session NSURLSession must be initialized with one of these two methods
init(configuration configuration: NSURLSessionConfiguration)
init(configuration configuration: NSURLSessionConfiguration,
delegate delegate: NSURLSessionDelegate?,
delegateQueue queue: NSOperationQueue?)
Do the initialization while declaration :-
var session = URLSession(configuration: .default)
I'm using Swift in Xcode 6.2 (beta) but had the same problem on the 6.1 release version. I'm trying to use NSURLSession and believe I have it set up correctly (see code below). The problem is that I have a delegate setup to deal with a redirect happening through the code. I actually need to capture the cookies prior to the final redirection and I'm doing this through the delegate:
func URLSession(_:NSURLSession, task:NSURLSessionTask, willPerformHTTPRedirection:NSHTTPURLResponse, newRequest:NSURLRequest, completionHandler:(NSURLRequest!) -> Void )
This works and I'm able to execute code successfully and capture the cookies I need. The problem is that I need to add task.cancel() at the end of the function or else it never seems to complete and return to the delegator (parent?) function. Because of this I lose the results from the redirect URL (although in my current project it is inconsequential). The strange thing is that this was working for a while and seemingly stopped. I don't believe I entered any code that changed it, but something had to happen. Below is the relevant code.
NSURLSession Function:
func callURL (a: String, b: String) -> Void {
// Define the URL
var url = NSURL(string: "https://mycorrecturl.com");
// Define the request object (via string)
var request = NSMutableURLRequest(URL: url!)
// Use default configuration
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
// Create the NSURLSession object with default configuration, and self as a delegate (so calls delegate method)
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
// Change from default GET to POST (needed to call URL properly)
request.HTTPMethod = "POST"
// Construct my parameters to send in with the URL
var params = ["a":a, "b":b] as Dictionary<String, String>
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
// Do some other stuff after delegate has returned...
})
task.resume()
return
}
The delegate code:
func URLSession(_:NSURLSession, task:NSURLSessionTask, willPerformHTTPRedirection:NSHTTPURLResponse, newRequest:NSURLRequest, completionHandler:(NSURLRequest!) -> Void ) {
// Check Cookies
let url = NSURL(string: "https://mycorrecturl.com")
var all = NSHTTPCookie.cookiesWithResponseHeaderFields(willPerformHTTPRedirection.allHeaderFields, forURL: url!)
// Get the correct cookie
for cookie:NSHTTPCookie in all as [NSHTTPCookie] {
if cookie.name as String == "important_cookie" {
NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookie(cookie)
}
}
task.cancel()
}
It used to return to the calling function without calling task.cancel(). Is there anything that looks wrong with the code that would cause it to just hang in the delegate function if task.cancel() isn't called?
Edit: What code would I add to fix this.
If you are not canceling the request, your willPerformHTTPRedirection should call the completionHandler. As the documentation says, this completionHandler parameter is:
A block that your handler should call with either the value of the request parameter, a modified URL request object, or NULL to refuse the redirect and return the body of the redirect response.
I am using Alamofire to download data
How to make alamofire run download in background with swift?
Thanks
The basic idea is as follows:
The key problem is that with background downloads, your app may actually be terminated while downloads are in progress (e.g. jettisoned due to memory pressure). Fortunately, your app is fired up again when background downloads are done, but any task-level closures you originally supplied are long gone. To get around this, when using background sessions, one should rely upon session-level closures used by the delegate methods.
import UIKit
import Alamofire
import UserNotifications
fileprivate let backgroundIdentifier = ...
fileprivate let notificationIdentifier = ...
final class BackgroundSession {
/// Shared singleton instance of BackgroundSession
static let shared = BackgroundSession()
/// AlamoFire `SessionManager`
///
/// This is `private` to keep this app loosely coupled with Alamofire.
private let manager: SessionManager
/// Save background completion handler, supplied by app delegate
func saveBackgroundCompletionHandler(_ backgroundCompletionHandler: #escaping () -> Void) {
manager.backgroundCompletionHandler = backgroundCompletionHandler
}
/// Initialize background session
///
/// This is `private` to avoid accidentally instantiating separate instance of this singleton object.
private init() {
let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier)
manager = SessionManager(configuration: configuration)
// specify what to do when download is done
manager.delegate.downloadTaskDidFinishDownloadingToURL = { _, task, location in
do {
let destination = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent(task.originalRequest!.url!.lastPathComponent)
try FileManager.default.moveItem(at: location, to: destination)
} catch {
print("\(error)")
}
}
// specify what to do when background session finishes; i.e. make sure to call saved completion handler
// if you don't implement this, it will call the saved `backgroundCompletionHandler` for you
manager.delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] _ in
self?.manager.backgroundCompletionHandler?()
self?.manager.backgroundCompletionHandler = nil
// if you want, tell the user that the downloads are done
let content = UNMutableNotificationContent()
content.title = "All downloads done"
content.body = "Whoo, hoo!"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let notification = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
}
// specify what to do upon error
manager.delegate.taskDidComplete = { _, task, error in
let filename = task.originalRequest!.url!.lastPathComponent
if let error = error {
print("\(filename) error: \(error)")
} else {
print("\(filename) done!")
}
// I might want to post some event to `NotificationCenter`
// so app UI can be updated, if it's in foreground
}
}
func download(_ url: URL) {
manager.download(url)
}
}
Then I can just initiate those downloads. Note, I do not specify any task-specific closure when I initiate the download, but rather merely use the above session-level closures that use the details of the URLSessionTask to identify what to do:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// request permission to post notification if download finishes while this is running in background
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
if let error = error, !granted {
print("\(error)")
}
}
}
#IBAction func didTapButton(_ sender: Any) {
let urlStrings = [
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg"
]
let urls = urlStrings.flatMap { URL(string: $0) }
for url in urls {
BackgroundSession.shared.download(url)
}
}
}
If your app isn't running when the downloads finish, iOS needs to know that, after it restarted your app, when you're all done and that it can safely suspend your app. So, in handleEventsForBackgroundURLSession you capture that closure:
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.saveBackgroundCompletionHandler(completionHandler)
}
}
That is used by sessionDidFinishEventsForBackgroundURLSession, in step 1.
Two observations:
This is only called if your app was not running when the downloads finish.
If doing background sessions, though, you must capture this closure and call it when you're all done processing the background session delegate methods.
So, to recap, the basic limitations of background sessions are:
You can only use download and upload tasks while the app is in background;
You can only rely upon session-level delegates because the app may have been terminated since the requests were initiated; and
In iOS, you must implement handleEventsForBackgroundURLSession, capture that completion handler, and call it when your background process is done.
I must also point out that while Alamofire is a wonderful library, it's not actually adding a lot value (above and beyond what is provided by URLSession to this background download process). If you're doing simple uploads/downloads only, then you might consider just using URLSession directly. But if you are using Alamofire in your project already or if your requests consist of more complicated application/x-www-form-urlencoded requests (or whatever) which merit the advantages of Alamofire, then the above outlines the key moving parts involved in the process.