Write chunks of video to memory and stream them in AVKit - swift

I am making a file encryption app in Swift for iOS. I want to support video files using the stock, native system video player. I currently have code that does it well, but it falls short because of one issue - if the file is too big, the app fills the phone's memory. I wanna avoid that by streaming chunks of data to AVKit (NOT AVFoundation) and play the chunks while staying in the context of that single video. How do I do that?
Note: sending raw data IS possible with AVKit, I have found code that does it very nicely (you can pass data to an init() function that gets data and it is transformed into an AVPlayerItem. The code is complicated and I don't understand much of it, the only thing that I found is that it uses a custom URL and passes it to a normal AVPlayerItem and (I think) every time data is requested, it is being sent to that custom URL. The weirdest part is that I haven't set up ANY sort of custom URL scheme which is associated with my app's project file.
Here is the code for the Data to AVPlayerItem that I was talking about:
import Foundation
import AVFoundation
fileprivate extension URL {
func withScheme(_ scheme: String) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)
components?.scheme = scheme
return components?.url
}
}
#objc protocol CachingPlayerItemDelegate {
/// Is called when the media file is fully downloaded.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data)
/// Is called every time a new portion of data is received.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, didDownloadBytesSoFar bytesDownloaded: Int, outOf bytesExpected: Int)
/// Is called after initial prebuffering is finished, means
/// we are ready to play.
#objc optional func playerItemReadyToPlay(_ playerItem: CachingPlayerItem)
/// Is called when the data being downloaded did not arrive in time to
/// continue playback.
#objc optional func playerItemPlaybackStalled(_ playerItem: CachingPlayerItem)
/// Is called on downloading error.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, downloadingFailedWith error: Error)
}
open class CachingPlayerItem: AVPlayerItem {
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
var playingFromData = false
var mimeType: String? // is required when playing from Data
var session: URLSession?
var mediaData: Data?
var response: URLResponse?
var pendingRequests = Set<AVAssetResourceLoadingRequest>()
weak var owner: CachingPlayerItem?
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
if playingFromData {
// Nothing to load.
} else if session == nil {
// If we're playing from a url, we need to download the file.
// We start loading the file on first request only.
guard let initialUrl = owner?.url else {
fatalError("internal inconsistency")
}
startDataRequest(with: initialUrl)
}
pendingRequests.insert(loadingRequest)
processPendingRequests()
return true
}
func startDataRequest(with url: URL) {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
session?.dataTask(with: url).resume()
}
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
pendingRequests.remove(loadingRequest)
}
// MARK: URLSession delegate
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
mediaData?.append(data)
processPendingRequests()
owner?.delegate?.playerItem?(owner!, didDownloadBytesSoFar: mediaData!.count, outOf: Int(dataTask.countOfBytesExpectedToReceive))
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(Foundation.URLSession.ResponseDisposition.allow)
mediaData = Data()
self.response = response
processPendingRequests()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let errorUnwrapped = error {
owner?.delegate?.playerItem?(owner!, downloadingFailedWith: errorUnwrapped)
return
}
processPendingRequests()
owner?.delegate?.playerItem?(owner!, didFinishDownloadingData: mediaData!)
}
// MARK: -
func processPendingRequests() {
// get all fullfilled requests
let requestsFulfilled = Set<AVAssetResourceLoadingRequest>(pendingRequests.compactMap {
self.fillInContentInformationRequest($0.contentInformationRequest)
if self.haveEnoughDataToFulfillRequest($0.dataRequest!) {
$0.finishLoading()
return $0
}
return nil
})
// remove fulfilled requests from pending requests
_ = requestsFulfilled.map { self.pendingRequests.remove($0) }
}
func fillInContentInformationRequest(_ contentInformationRequest: AVAssetResourceLoadingContentInformationRequest?) {
// if we play from Data we make no url requests, therefore we have no responses, so we need to fill in contentInformationRequest manually
if playingFromData {
contentInformationRequest?.contentType = self.mimeType
contentInformationRequest?.contentLength = Int64(mediaData!.count)
contentInformationRequest?.isByteRangeAccessSupported = true
return
}
guard let responseUnwrapped = response else {
// have no response from the server yet
return
}
contentInformationRequest?.contentType = responseUnwrapped.mimeType
contentInformationRequest?.contentLength = responseUnwrapped.expectedContentLength
contentInformationRequest?.isByteRangeAccessSupported = true
}
func haveEnoughDataToFulfillRequest(_ dataRequest: AVAssetResourceLoadingDataRequest) -> Bool {
let requestedOffset = Int(dataRequest.requestedOffset)
let requestedLength = dataRequest.requestedLength
let currentOffset = Int(dataRequest.currentOffset)
guard let songDataUnwrapped = mediaData,
songDataUnwrapped.count > currentOffset else {
// Don't have any data at all for this request.
return false
}
let bytesToRespond = min(songDataUnwrapped.count - currentOffset, requestedLength)
let dataToRespond = songDataUnwrapped.subdata(in: Range(uncheckedBounds: (currentOffset, currentOffset + bytesToRespond)))
dataRequest.respond(with: dataToRespond)
return songDataUnwrapped.count >= requestedLength + requestedOffset
}
deinit {
session?.invalidateAndCancel()
}
}
fileprivate let resourceLoaderDelegate = ResourceLoaderDelegate()
fileprivate let url: URL
fileprivate let initialScheme: String?
fileprivate var customFileExtension: String?
weak var delegate: CachingPlayerItemDelegate?
open func download() {
if resourceLoaderDelegate.session == nil {
resourceLoaderDelegate.startDataRequest(with: url)
}
}
private let cachingPlayerItemScheme = "cachingPlayerItemScheme"
/// Is used for playing remote files.
convenience init(url: URL) {
self.init(url: url, customFileExtension: nil)
}
/// Override/append custom file extension to URL path.
/// This is required for the player to work correctly with the intended file type.
init(url: URL, customFileExtension: String?) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let scheme = components.scheme,
var urlWithCustomScheme = url.withScheme(cachingPlayerItemScheme) else {
fatalError("Urls without a scheme are not supported")
}
self.url = url
self.initialScheme = scheme
if let ext = customFileExtension {
urlWithCustomScheme.deletePathExtension()
urlWithCustomScheme.appendPathExtension(ext)
self.customFileExtension = ext
}
let asset = AVURLAsset(url: urlWithCustomScheme)
asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)
super.init(asset: asset, automaticallyLoadedAssetKeys: nil)
resourceLoaderDelegate.owner = self
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)
}
/// Is used for playing from Data.
init(data: Data, mimeType: String, fileExtension: String) {
guard let fakeUrl = URL(string: cachingPlayerItemScheme + "://whatever/file.\(fileExtension)") else {
fatalError("internal inconsistency")
}
self.url = fakeUrl
self.initialScheme = nil
resourceLoaderDelegate.mediaData = data
resourceLoaderDelegate.playingFromData = true
resourceLoaderDelegate.mimeType = mimeType
let asset = AVURLAsset(url: fakeUrl)
asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)
super.init(asset: asset, automaticallyLoadedAssetKeys: nil)
resourceLoaderDelegate.owner = self
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)
}
// MARK: KVO
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
delegate?.playerItemReadyToPlay?(self)
}
// MARK: Notification hanlers
#objc func playbackStalledHandler() {
delegate?.playerItemPlaybackStalled?(self)
}
// MARK: -
override init(asset: AVAsset, automaticallyLoadedAssetKeys: [String]?) {
fatalError("not implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
removeObserver(self, forKeyPath: "status")
resourceLoaderDelegate.session?.invalidateAndCancel()
}
}
I put it here in case anybody wants to use it. I didn't write this, this works was made by GitHub user "neekeetab". Here's the file.
If anyone can answer me about how to send AVKit a stream of data chunks that are inside of the iDevice's memory, I will be happy to hear from you. Even if you don't, thanks for stopping by!

Related

URLSession cannot find 'self' in scope for errors and statusCode

My question is somewhat similar to 69959018, so I have made sure to clarify as much as I can
I'm trying to use the Steam Web API to create an app that grabs everyone on my friend list in the form of a JSON dictionary. I'm trying to use foundation instead of Alamofire in order to learn Foundation better.
So far, what I've done is the following in AppDelegate.swift:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
var apiKey: String = "[REDACTED]"
var steamID: String = "[REDACTED]"
let getPlayerSummaries = URL(string: "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=\(apiKey)&steamids=\(steamID)")
let friendList = downloadPlayerSummaries(with: getPlayerSummaries)
print(friendList)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
In another file I made called networkManager.swift, I have wrote this based on what I have found in the apple documentation for "Fetching Website Data into Memory" :
//
// networkManager.swift
// Who is online?
//
// Created by Dash Interwebs on 11/21/21.
//
import Foundation
func downloadPlayerSummaries(with: URL!) {
let url = with
if url == nil {
print("url is nil")
return
}
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
}
}
task.resume()
}
After this however, self.handleClientError(error), and self.handleServerError(response) complain about being unable to find "self". I can't find anything about handleServerError or handleClientError. So where exactly is "self" in this context? I think that it might be URLSession but I'm not too sure.
You can refactor your code using a completion handler and using an enum that conforms to the Error protocol:
enum ApiError: Error {
case network(Error)
case genericError
case httpResponseError
}
func downloadPlayerSummaries(with url: URL?, completion: #escaping (_ success: Bool, _ error: ApiError?) -> Void) {
guard let url = url else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(false, .network(error))
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
completion(false, .httpResponseError)
return
}
// then handle your data. The completion should also include the kind of data your want to return
}
task.resume()
}
I haven't tested. Let me know if it works.

How to create a Swift method to use evaluateJavaScript with GCD

How do I create a macOS method that takes a URL parameter and returns HTML using evaluateJavaScript?
I want to scrape a URL that uses JavaScript, so neither String(contentsOf: url) nor URLSession.shared.dataTask(with: url) seem to work.
The code below works, but it creates a global, rather than returning a String. I want code I can reuse in other similar projects.
Can I use didSet{} or GCD DispatchGroup() and group.enter() and group.leave() to create either a class or ViewController extension to create a simple method that returns only when the page loads and the JavaScript completes? I assume I'll need a class to catch the WKNavigationDelegate calls.
I want a macOS method, not an iOS method.
Here is my working code:
ViewController.swift
import Cocoa
import WebKit
class ViewController: NSViewController, WKNavigationDelegate {
var webView: WKWebView!
var webString: String = "" {
didSet {
print("webString: \(webString.count) Bytes")
print("\(webString)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.webView = WKWebView()
webView.navigationDelegate = self
let url = URL(string: "https://www.someWebsite.com")!
let myRequest = URLRequest(url: url)
webView.load(myRequest)
}
// WKNavigationDelegate methods
func webView(_ webView: WKWebView, didFinish: WKNavigation!) {
print(#function)
webView.evaluateJavaScript(
"document.documentElement.outerHTML.toString()",
completionHandler: { (html: Any?, error: Error?) in
self.webString = html as! String
print("In compHandler, HTML: \(self.webString.count) bytes\n")
})
}
func webView(_ webView: WKWebView, didFail: WKNavigation!, withError: Error) {
print(#function)
print("ERROR: Error=\(withError)")
}
I want something similar to this code that I use for synchronous dowloads:
func downloadDataTask(url: String) -> String {
var returnStr = ""
guard let myURL = URL(string: url) else {
showErrorAlert(
title: "ERROR: Invalid URL",
message: "Invalid URL in downloadDataTask()\n URL:'\(url)'")
return("Error: Invalid URL: '\(url)'")
}
let group = DispatchGroup()
group.enter()
let task = URLSession.shared.dataTask(with: myURL) { data, urlResponse, error in
if let myData = data {
let string = String(decoding: myData, as: UTF8.self)
returnStr = string
group.leave()
} else {
showErrorAlert(
title: "ERROR: Unable to load HTML from URL",
message: "Unable to load HTML in downloadDataTask()\n")
group.leave()
}
}
task.resume()
// wait for async completion handler task to complete
group.wait()
return(returnStr)
}
Any help would be appreciated!

PDF view not presenting

I'm downloading a PDF from a url and then displaying it using PDFKit in Swift5. However, when I tap my viewCell it is not presenting the PDF and potentially not downloading the PDF. How can I verify this?
I have two files, PlanogramViewController and ShowPDFViewController.
var pdfURL: URL!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let plano = self.data[indexPath.row] as? Planogram {
guard let url = URL(string: Plano.pdfUrl) else { return }
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
let downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
let pdfViewController = ShowPDFViewController()
pdfViewController.pdfURL = self.pdfURL
present(pdfViewController, animated: false, completion: nil)
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension PlanogramViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("downloadLocation:", location)
// create destination URL with the original pdf name
guard let url = downloadTask.originalRequest?.url else { return }
let documentsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
// delete original copy
try? FileManager.default.removeItem(at: destinationURL)
// copy from temp to Document
do {
try FileManager.default.copyItem(at: location, to: destinationURL)
self.pdfURL = destinationURL
} catch let error {
print("Copy Error: \(error.localizedDescription)")
}
}
}
So I'm not getting any download verification from my extension. It should be printing my initial download location and then moving it to the cache. If it encounters an error it should also print an error, but I'm getting nothing in the console. As if nothing is downloading.
Then here is the pdfViewController it's supposed to present once it has the PDF downloaded:
import UIKit
import PDFKit
class ShowPDFViewController: UIViewController {
var pdfView = PDFView()
var pdfURL: URL!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pdfView)
if let document = PDFDocument(url: pdfURL) {
pdfView.document = document
}
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
self.dismiss(animated: true, completion: nil)
}
}
override func viewDidLayoutSubviews() {
pdfView.frame = view.frame
}
}
The current behavior is it just highlights the cell I tapped. Nothing in the console and nothing presented on the display.
You need to conform to the UITableViewDelegate and UITableViewDataSourceDelegate protocols in the view controller that has the tableView.
and add
tableView.delegate = self
tableView.dataSource = self
in the viewDidLoad()
Then, implement the required methods, and add optional methods to further customize your tableView.

Record And Play Voice in Separate Class (Swift3)

I used many codes that was for record an play the voice, but most of them are not in swift3 and they don't work in my app.
This code works, but I want to create a separate class from the viewcontroller that do recording an playing voices. Also the mentioned github code is complex an I'm searching for a simplified code.
Update:
After recording, when I check existence of the recorded file, the file doesn't exist, and it raises EXC_BAD_ACCESS error on appDelegate.
What's wrong?
Any suggestions would be appreciated.
Try to record audio by wirting line
let isRec = AudioManager.shared.record(fileName: "rec")
if isRec returned true then recording is happening else not.
To finish recording use : let recordedURL = AudioManager.shared.finishRecording()
To play recorded file send above url to setupUpPlayer() function in manager class
Not to forget to use extension code snippets give below the code snippet which are delegate functions of AVAudioRecorder and AVAudioPlayer
import Foundation
import AVFoundation
class AudioManager: NSObject {
static let shared = AudioManager()
var recordingSession: AVAudioSession?
var recorder: AVAudioRecorder?
var meterTimer: Timer?
var recorderApc0: Float = 0
var recorderPeak0: Float = 0
//PLayer
var player: AVAudioPlayer?
var savedFileURL: URL?
func setup() {
recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession?.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
try recordingSession?.setActive(true)
recordingSession?.requestRecordPermission({ (allowed) in
if allowed {
print("Mic Authorised")
} else {
print("Mic not Authorised")
}
})
} catch {
print("Failed to set Category", error.localizedDescription)
}
}
func record(fileName: String) -> Bool {
setup()
let url = getUserPath().appendingPathComponent(fileName + ".m4a")
let audioURL = URL.init(fileURLWithPath: url.path)
let recordSettings: [String: Any] = [AVFormatIDKey: kAudioFormatMPEG4AAC,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
AVNumberOfChannelsKey: 2,
AVSampleRateKey: 44100.0]
do {
recorder = try AVAudioRecorder.init(url: audioURL, settings: recordSettings)
recorder?.delegate = self
recorder?.isMeteringEnabled = true
recorder?.prepareToRecord()
recorder?.record()
self.meterTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer: Timer) in
//Update Recording Meter Values so we can track voice loudness
if let recorder = self.recorder {
recorder.updateMeters()
self.recorderApc0 = recorder.averagePower(forChannel: 0)
self.recorderPeak0 = recorder.peakPower(forChannel: 0)
}
})
savedFileURL = url
print("Recording")
return true
} catch {
print("Error Handling", error.localizedDescription)
return false
}
}
func getUserPath() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
func finishRecording() -> String {
recorder?.stop()
self.meterTimer?.invalidate()
var fileURL: String?
if let url: URL = recorder?.url {
fileURL = String(describing: url)
}
return /fileURL
}
//Player
func setupPlayer(_ url: URL) {
do {
try player = AVAudioPlayer.init(contentsOf: url)
} catch {
print("Error1", error.localizedDescription)
}
player?.prepareToPlay()
player?.play()
player?.volume = 1.0
player?.delegate = self
}
}
//MARK:- Audio Recorder Delegate
extension AudioManager: AVAudioRecorderDelegate {
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
print("AudioManager Finish Recording")
}
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
print("Encoding Error", /error?.localizedDescription)
}
}
//MARK:- Audio Player Delegates
extension AudioManager: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool) {
player.stop()
print("Finish Playing")
}
func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer,
error: Error?) {
print(/error?.localizedDescription)
}
}

Download Multiple File at a time in Swift

I was finally able to download 1 video from a server using the following code:
import UIKit
class ViewController: UIViewController, NSURLConnectionDelegate {
var file:NSFileHandle?
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
downloadVideo()
}
func downloadVideo(sender: UIButton) {
let urlPath: String = "http://www.ebookfrenzy.com/ios_book/movie/movie.mov"
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: true)!
connection.start()
}
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
var fileName = "test.mov"
var fileAtPath = fileInDocumentsDirectory(fileName)
if(NSFileManager.defaultManager().fileExistsAtPath(fileAtPath) == false)
{
NSFileManager.defaultManager().createFileAtPath(fileAtPath, contents: nil, attributes: nil)
}
file = NSFileHandle(forWritingAtPath: fileAtPath)!
if ((file) != nil){
file!.seekToEndOfFile()
}
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!){
//write,each,data,received
if(data != nil){
if((file) != nil){
file!.seekToEndOfFile()
}
file!.writeData(data)
}
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
file!.closeFile()
}
func documentsDirectory() -> String {
let documentsFolderPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] as String
return documentsFolderPath
}
func fileInDocumentsDirectory(filename: String) -> String{
return documentsDirectory().stringByAppendingPathComponent(filename)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
But I need to download a lot of video files(I have at least 100 URL), how can I do it? I was thinking of doing it one at a time but I guess that in that approach I will have a lot of NSURLConnections instances and maybe I will eat all my RAM, Can you please help me to learn the right form of multiple files download?
Thank you so much!
You can use a concurrent queue to limit the max number of connections at a time.
func downloadVideo(sender: UIButton) {
for urlPath in urlPaths {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: true)!
connection.start()
}
}
}
If you want to customize the max number of connections, check here:
Can I limit concurrent requests using GCD?