Leaked NSURLConnection in Swift
I have found several other articles on memory issues with NSURLConnection, however none of them are related to projects using ARC. My problem is I do NSURLConnections every 0.5s (yes, I know it would be better with sockets, but I have no control over the API). When I open the xCode Debug Inspector, I could clearly see my used memory growing:
When I view my application with tools, however - no leaks, persistent storage across generations of labels, just nothing:
I deliberately decoupled all my other logic and created a simple application to show my problem.
This is the main (and only) ViewController
import UIKit
class ViewController: UIViewController {
private var timer: NSTimer!
private var requests = [ServerRequest]()
override func viewDidAppear(animated: Bool)
{
self.timer = NSTimer(timeInterval: 0.5, target: self, selector: "refresh", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
}
@objc private func refresh()
{
let urlRequest = NSURLRequest(URL: NSURL(string: "https://api.stackexchange.com/2.2/questions?order=desc&sort=activity&site=stackoverflow")!)
let serverRequest = ServerRequest(request: urlRequest){
[unowned self] result, error, request in
println("yaba daba duu")
self.requests.removeAll(keepCapacity: false)
}
requests.append(serverRequest)
serverRequest.send()
}
}
And here is my asynchronous connection delegate:
import Foundation
class ServerRequest: NSObject
{
private var request: NSURLRequest!
typealias completionType = (NSData?, NSError?, ServerRequest) -> ()
private var completion: completionType!
private var receivedData: NSMutableData?
private var connection: NSURLConnection?
init(request: NSURLRequest, completion: (NSData?, NSError?, ServerRequest) -> ())
{
super.init()
self.request = request
self.completion = completion
self.connection = NSURLConnection(request: self.request, delegate: self, startImmediately:false)
//this line will help connection is fired even while tere are touch events
self.connection?.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
func send()
{
receivedData = NSMutableData()
self.connection?.start()
}
func abort()
{
self.connection?.cancel()
}
}
extension ServerRequest: NSURLConnectionDataDelegate, NSURLConnectionDelegate
{
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse)
{
self.receivedData?.length = 0
}
func connection(connection: NSURLConnection, didReceiveData data: NSData)
{
self.receivedData?.appendData(data)
}
func connection(connection: NSURLConnection, didFailWithError error: NSError)
{
self.connection = nil
self.completion(nil, error, self)
}
func connectionDidFinishLoading(connection: NSURLConnection)
{
self.connection = nil
self.completion(receivedData, nil, self)
}
//MARK: https specific(canAuthenticateAgainstProtectionSpace is depraceted first in iOS 8)
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool
{
return protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
{
challenge.sender.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust), forAuthenticationChallenge: challenge)
challenge.sender.continueWithoutCredentialForAuthenticationChallenge(challenge)
}
func connection(connection: NSURLConnection, willCacheResponse cachedResponse: NSCachedURLResponse) -> NSCachedURLResponse? {
return nil
}
}
Please note that I need to connect to the https server, so I need the appropriate delegation methods. Also, I have implemented caching methods due to some suggestions in SO that caching responses in NSURLConnection might cause my problems. The final request is scheduled in "NSRunLoopCommonModes" because I need it while it is scrolling (although removing that line does not affect the problem anyway).
UPDATE
I tried even without a custom class with a delegate and the result is almost the same. Just copy the following link:
import UIKit
class ViewController: UIViewController {
private var timer: NSTimer!
override func viewDidAppear(animated: Bool)
{
self.timer = NSTimer(timeInterval: 0.5, target: self, selector: "refresh", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
}
@objc private func refresh()
{
let urlRequest = NSURLRequest(URL: NSURL(string: "https://api.stackexchange.com/2.2/questions?order=desc&sort=activity&site=stackoverflow")!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue())
{
response, data, error in
println("yaba daba duu")
}
}
}
And my memory pressure rises again:
source to share
No one has answered this question yet
Check out similar questions: