Swift - Log all requests in mac app - swift

I have tried to use NSURLProtocol to log all requests in a Swift 2.3 project. However not all URL requests are being logged. Specifically all the Alamofire requests are not being recorded.
Sample code
class AppDelegate: NSObject, NSApplicationDelegate{
func applicationDidFinishLaunching(aNotification: NSNotification) {
NSURLProtocol.registerClass(TestURLProtocol)
Alamofire.request(.GET, SomeURL).responseSwiftyJSON({ (request, response, json, error) in })
}
}
class TestURLProtocol: NSURLProtocol {
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
print("request \(request.URL!)") // never called
return false
}
}

I think this is because Alamofire uses the new URLSession API, which is not affected by the NSURLProtocol.registerProtocol call.
You have to create a URLSession with URLSessionConfiguration that has its protocolClasses array set to [TestURLProtocol.self].
But with this you would have to use a custom SessionManager everywhere to log the requests, instead of using the implicit Alamofire.request I think.

What I ended up using was the pod OHHTTPStubs. I added the following code to my app delegate to log every host being used.
func applicationDidFinishLaunching(aNotification: NSNotification) {
var hosts = [String: Int]()
stub({ req in
if let url = req.URL, let host = url.host{
var count = 1
if let c = hosts[host]{
count = c + 1
}
hosts[host] = count
print("Request #\(count): Host = \(host)")
}
return false
},
response:{_ in return OHHTTPStubsResponse()}
);
}

Related

Am unable to receive HTTP responses with UIViewController set as URLSessionDelegate

Wed 5/18 Additional Info added at Step 5
I am able to create a URLSesion, build a request with a file to upload and successfully call it from my app. On my server side, the proper script is called, uploaded file is saved, etc,. However, I am not receiving the HTTP responses, data, etc.
Actually had this working without the delegate, when the HTTP response functions were within the task itself. But am now trying to expand functionality and am missing something while trying implement the delegate.
The trimmed code is below, and it all works, with the exception of setting up UIViewController as the URLSession delegate. Just trying to figure out why my UIViewController is not receiving the HTTP responses.
Below is the code for:
UIViewController
Class which creates the upload session (UploadService)
Extension for
UIViewController which I want to use to process the responses
How the previous task looked, when it worked. Before I tried to implement the delegate.
Used print to confirm that my UIViewConroller is the delegate, yet it still receives no HTTP response, data, or error messages
UIViewController
class UploadInv : UIViewController {
var xFile : XFile?
...create UI....
let uploadService = UploadService()
lazy var uploadSession: URLSession = {
let configuration = URLSessionConfiguration.default
return URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
}()
override func viewWillAppear(_ animated: Bool) {
...
}
override func viewDidLoad() {
super.viewDidLoad()
uploadService.uploadSession = uploadSession
... code the lays out all buttons, labels, etc...
}
#objc func buttonAction(sender: UIButton!) {
guard let theButton = sender else { return}
let myTag = theButton.tag
switch myTag {
//button to start upload
case ButtType.up.rawValue:
uploadService.start(upFile: xFile!, script: "uploadOrig.pl", upLoadInvClass: self)
uploadService.task?.resume()
//button to select file to upload
case ButtType.file.rawValue:
... file xFile with file info
}
}
UploadService
class UploadService {
var uploadSession : URLSession!
var task: URLSessionUploadTask?
func start(upFile: XFile, script: String, upLoadInvClass: UploadInv) {
var request = upFile.makeUrlReq(upFile: upFile, script: script)
task = uploadSession.uploadTask(with: request, from: request.httpBody! )
print("\(uploadSession.delegate)")
task?.resume()
}
}
extension
extension UploadInv: UIDocumentPickerDelegate, URLSessionDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
... file xFile info for upload ....
... http request created ....
}
// Below are the three simple functions which I would handle
// responses the server, but these never seem to get called.
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let err = error {
print("Error: \(err.localizedDescription)")
}
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) {
print("didReceive response")
completionHandler(URLSession.ResponseDisposition.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("didReceive data")
if let responseText = String(data: data, encoding: .utf8) {
print(responseText)
}
}
}
Pre-Delegate model which worked
class UploadService {
var uploadSession = URLSession.shared
func start(upFile: XFile, script: String, upLoadInvClass: UploadInv) {
var request = upFile.makeUrlReq(upFile: upFile, script: script)
uploadSession.uploadTask(with: request, from: request.httpBody )
{ (data, response, error) in
if let response = response {
upLoadInvClass.upResp(resp: response)
}
if let error = error {
upLoadInvClass.upErr(error: error)
}
if let data = data {
upLoadInvClass.upData(data: data)
}
}.resume()
}
}
Step 5:
task = uploadSession.uploadTask(with: request, from: request.httpBody! )
print("\(uploadSession.delegate)")
task?.resume()
For other newbies also stuck on this, it turns out there's more than one delegate to look at. There are:
URLSessionTaskDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate, and more. So obviously I was using the wrong one, might have been fell trap to "autocomplete." Nevertheless, I have to make sure I read more documentation on the subject.
Thanks to Scott who "passively/aggressively" gave me the answer, here, while still allowing me to "think." I mean that as a compliment. He told me to add the line:
assert(uploadSession.delegate! is URLSessionDataDelegate)

URLSessionDelegate functions not called for background upload on watchOS

I'm using a Series 6 emulator on watchOS7 and I'm trying to upload some JSON data to a remote server using an URLSession background task. However the delegate functions are not being called so I cannot clean up any local data from the upload and handle any errors. I got the original idea from my implementation from this WWDC video WWDC Video. I've looked at many posts on the Internet but nothing I've tried seems to work. Here is my code:
UploadSession class
class UploadSession: NSObject, Identifiable, URLSessionDelegate {
var backgroundTasks = [WKURLSessionRefreshBackgroundTask]()
private lazy var urlSession: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "my.app.watchextension")
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func enqueueBackgroundTask(idToken: String, uploadData: Data, url: URL) throws {
//build the JSON object we want to send in this post request
let tempDir = FileManager.default.temporaryDirectory
let localURL = tempDir.appendingPathComponent("throwaway")
try? uploadData.write(to: localURL)
//set up the URLRequest
var request = URLRequest(url: url)
request.httpMethod = K.Upload.httpPost
request.setValue(K.Upload.jsonContent, forHTTPHeaderField: K.Upload.contentType)
request.setValue("\(K.Upload.bearer)\(idToken)", forHTTPHeaderField: K.Upload.authorization)
request.timeoutInterval = K.Upload.httpUploadTimeout
//keep a reference to this class
BackgroundURLSessions.shared.sessions["my.app.watchextension"] = self
//create the upload task and kick it off
let task = urlSession.uploadTask(with: request, fromFile: localURL)
task.earliestBeginDate = Date().advanced(by: 120)//when setting this to zero the upload runs straight away.
task.resume()
}
func addBackgroundRefreshTask(_ task: WKURLSessionRefreshBackgroundTask) {
backgroundTasks.append(task)
}
//gets called when the task background task completes
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
//PROBLEM. This delegate method is never called
if let sessionId = session.configuration.identifier {
//TODO delete any local data copies
//set the session to nil so the system doesn't try to execute it again
BackgroundURLSessions.shared.sessions[sessionId] = nil
}
for task in backgroundTasks {
task.setTaskCompletedWithSnapshot(false)
}
}
//gets called if the background task throws an error
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
//PROBLEM. This delegate method is never called
}
}
BackgroundURLSessions class
class BackgroundURLSessions: NSObject {
static let shared: BackgroundURLSessions = BackgroundURLSessions()
var sessions = [String: UploadSession]()
override private init() {
super.init()
}
}
Extension Delegate class
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
// Be sure to complete the URL session task once you’re done.
if let session = BackgroundURLSessions.shared.sessions[urlSessionTask.sessionIdentifier] {
session.addBackgroundRefreshTask(urlSessionTask)
} else {
urlSessionTask.setTaskCompletedWithSnapshot(false)
}
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
Any help really appreciated as I'm really struggling to get this work and it's a vital part of the application. Thanks.

Difference between a Callback and Competition Handler in Swift

In the Combine framework, I have found following text
The Combine framework provides a declarative approach for how your app
processes events. Rather than potentially implementing multiple
delegate callbacks or completion handler
Can somebody tell me what is the difference between completion handler and callback in Swift?
A delegate callback is when you have a delegate that you know in advance implements a method (e.g. because it adopts a protocol), and you call that method by name.
A completion handler is when someone hands you a function and you just call it blindly by reference.
to be clear actually you can achieve the same functionality with both ways however the there are completely different approach for designing your app
let me clarify with simple example the difference between both with the same function is making network call
delegate protocol
// enum to define the request type
enum RequestTypes {
case UserRegister
case UserLogin
}
protocol ServiceDelegate {
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes)
}
// you can also add default impl to the methods here
extension ServiceDelegate {
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes){}
}
class BaseService<ResponseModel: Codable> {
var session: URLSession!
var delegate: ServiceDelegate?
// MARK: Rebuilt Methods
func FireRequest(){
// Request Preparation
let serviceUrl = URL(string: /* your url */)!
var request = URLRequest(url: serviceUrl)
request.httpMethod = "GET"
// Firing the request
session = URLSession.init(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {/* handle error or call delegate error method here */ return }
delegate?.didCompleteRequest(responseModel: object, tag: .UserLogin)
}
}
}.resume()
}
}
class ViewController: UIViewController, ServiceDelegate {
override func viewDidLoad() {
super.viewDidLoad()
fetchNewData()
}
func fetchNewData(){
let service = BaseService<YourModel>()
service.delegate = self
service.FireRequest()
}
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes) {
if tag == /* the tag you are waiting */ .UserLogin {
// YourModel is available here
}
}
}
completion handler
class BaseService<ResponseModel: Codable> {
var session: URLSession!
// MARK: Rebuilt Methods
func FireRequest(completion: ((ResponseModel?) -> Void)?){
// Request Preparation
let serviceUrl = URL(string: /* your url */)!
var request = URLRequest(url: serviceUrl)
request.httpMethod = "GET"
// Firing the request
session = URLSession.init(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {/* handle error or call delegate error method here */ return }
DispatchQueue.main.async {
completion?(object)
}
}
}
}.resume()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchNewData()
}
func fetchNewData(){
let service = BaseService<YourModel>()
service.FireRequest(completion: { [weak self] (response) in
// yourModel Available here once the request completed
})
}
}
A delegate callback is one to one communication between various ViewControllers and classes. It basically lets you know that a particular change has been done in particular view or any where else and now you can make change after this action.
While completion handler is a block executed after completing a particular process or task.
Callback is a way to sending data back to some other function on some particular occasion. there are 2 ways to implement callbacks in swift.
Using Protocols / Delegate
Using Completion Handler
Using Protocols / Delegate Example:
Declare Protocol
protocol MyDelegate {
public method(param: String);
}
Your ViewController should extend the delegate
class YourViewController: MyDelegate {
// Your Other methods
func method(param: String) {
// Do your stuff
}
}
Now in your other classes you can send callback to ViewController through delegate object like
delegate.method(param: "your_param");
Using Completion Handler Example:
public func method(param: String, completionHandler: #escaping (_ param: String) -> Void)
{
...
// now you can send data back to the caller function using completionHandler on some particular occasion
completionHandler("param");
}
We can call this function like
method(param: String, completionHandler: { (result, alreadyUserId) in
// here you will receive callback
});
Callbacks and Completion Handlers are synonymous when referring to asynchronous methods.
I’ve found the main difference being in how its used in defining what’s returned to the caller where a callback is used when referring to a method where the scope is returned to the previous calling method and a completion handler refers to a method when it returns some Result type to the caller.

How to cancel NSURLSession when moving to another UIViewController

I am using NSURLSession for API call. Once API gets hit, before getting response I am moving to another view controller. How to stop all NSURLSession running in the current view controller? Kindly guide me.
My Sample code is not working. If I move to another UIViewController, that new session creates and working fine but previous UIViewControllers's session resuming. How to stop that Session? Kindly guide me.
Sample Get API Cal:
var testSession = NSURLSession.sharedSession()
var testTask = NSURLSessionDataTask()
override func viewDidAppear(animated: Bool) {
testTask = testSession.dataTaskWithURL(NSURL(string: "http://httpbin.org/get")!, completionHandler: { (data, response, error) -> Void in
do{
let str = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! [String:AnyObject]
print(str)
}
catch {
print("json error: \(error)")
}
})
testTask.resume()
}
override func viewDidDisappear(animated: Bool) {
testTask.cancel()
testSession.finishTasksAndInvalidate()
}
Don't use the shared session. Use a real session that you create explicitly. Then call invalidateAndCancel on the session when you no longer need any of its pending requests.

NSNotificationCenter Notification Not Being Received When Posted in a Closure

What I am trying to accomplish is posting a notification through NSNotificationCenter's default center. This is being done within a closure block after making a network call using Alamofire. The problem I am having is that a class that should be responding to a posted notification isn't receiving such notification.
My ViewController simply creates a First object that get's things moving:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let first = First()
}
}
My First class creates and instance of a Second class and adds itself as an observer to my NSNotificationCenter. This is the class that can't seem to get the notification when the notification is posted.
class First : NSObject {
let second = Second()
override init(){
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(First.gotDownloadNotification(_:)), name: "test", object: nil)
second.sendRequest()
}
// NOT REACHING THIS CODE
func gotDownloadNotification(notification: NSNotification){
print("Successfully received download notification from Second")
}
}
My Second class is what makes the network call through my NetworkService class and posts a notification in a closure once the request is successful and complete.
class Second : NSObject {
func sendRequest(){
let networkService = NetworkService()
networkService.downloadFile() { statusCode in
if let statusCode = statusCode {
print("Successfully got a status code")
// Post notification
NSNotificationCenter.defaultCenter().postNotificationName("test", object: nil)
}
}
}
}
Finally, my NetworkService class is what makes a network call using Alamofire and returns the status code from the response through a closure.
class NetworkService : NSObject {
func downloadFile(completionHandler: (Int?) -> ()){
Alamofire.download(.GET, "https://www.google.com") { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let pathComponent = response.suggestedFilename
return directoryURL.URLByAppendingPathComponent(pathComponent!)
}
.response { (request, response, _, error) in
if let error = error {
print("File download failed with error: \(error.localizedDescription)")
completionHandler(nil)
} else if let response = response{
print("File downloaded successfully")
// Pass status code through completionHandler to Second
completionHandler(response.statusCode)
}
}
}
}
The output after execution is:
File downloaded successfully
Successfully got a status code
From this output I know the download was successful and Second got the status code from the closure and posted a notification right after.
I believe that I have tried resolving most other suggestions on Stack Overflow related to not receiving notifications such as objects not being instantiated before notification is posted or syntax of either adding an observer or posting a notification.
Does anyone have any idea why the posted notification is not being received in the First class?
Since there is a direct relationship between First and Second the protocol/delegate pattern is the better way to notify. Even better with this pattern and you don't have to take care of unregistering the observer. NSNotificationCenter is supposed to be used only if there is no relationship between sender and receiver.
And basically the thread doesn't matter either.
protocol SecondDelegate {
func gotDownloadNotification()
}
class Second : NSObject {
var delegate : SecondDelegate?
init(delegate : SecondDelegate?) {
self.delegate = delegate
}
func sendRequest(){
let networkService = NetworkService()
networkService.downloadFile() { statusCode in
if let statusCode = statusCode {
print("Successfully got a status code")
// Post notification
self.delegate?.gotDownloadNotification()
}
}
}
}
class First : NSObject, SecondDelegate {
let second : Second
override init(){
super.init()
second = Second(delegate:self)
second.sendRequest()
}
func gotDownloadNotification(){
print("Successfully received download notification from Second")
}
}