How to use NSURLConnection completionHandler with swift - block

Does anybody know how handlers (blocks) work in swift? I am trying to get this code running but i can't find any documentation of the right syntax for the completionHandler.
let url:NSURL = NSURL(string:"some url")
let request:NSURLRequest = NSURLRequest(URL:url)
let queue:NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(request:request, queue:queue, completionHandler handler:((NSURLResponse!, NSData!, NSError!) -> Void)!)

Like this:
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ response, data, error in /* Your code */ })
Or more verbose variant.
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
/* Your code */
})

You need to use this code:
NSURLConnection.sendAsynchronousRequest(request,queue:queue,completionHandler:{response,data,error in /* code goes here */ })
For more info, you can refer to this tutorial, or or check the answers to How to parse a JSON file in swift?.

sendAsynchronousRequest has been deprecated in newer versions of Swift. Move to dataTaskWithRequest, luckily it is used pretty much the same way
let request:NSURLRequest = NSURLRequest(URL:NSURL(string:"http://YOUR_DESIRED_URL.com")!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
});
task.resume()

The right term you are looking for here is Closure. Closures in Swift are similar to blocks in C and Objective-C. In addition to Tomáš's answer there is another short version to use the completion handler here:
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler: {$0; $1; $2})
Here I have used Shorthand Argument Names. I am accessing response as $0, data as $1 and error as $3. I find this syntax more easy to read and write unless the parameters are large in number otherwise the code will become unreadable.

Related

How to use an Objective-C function with block in Swift4 in a #noescap way?

I'm developing an iOS app in Swift4 with an Object-C framework called 'YapDatabase'. There is an Object-C function with a block like this in class 'YapDatabaseConnection':
- (void)readWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block;
I use the function in this way:
static func readNovelIds() -> [String]? {
let account = XFAccountManager.share().account
var events: [XFNovelClickEvent]?
OTRDatabaseManager.shared.readOnlyDatabaseConnection?.read({ (transaction) in
events = XFNovelClickEvent.allNovelClickEvents(accountId: account.uniqueId, transaction: transaction)
})
guard let clickEvents = events else {
return nil
}
let readNovelsIds = clickEvents.map {
$0.bookId ?? ""
}
return readNovelsIds
}
I thought the closure will be executed immediately after the 'events' parameter declared. In fact, the closure doesn't be executed before result returns. To search the reason, I open the file named 'YapDatabaseConnection.h(Interface)' generated by Xcode (with cmd+shift+o), found the function has been translate to Swift in this way:
open func read(_ block: #escaping (YapDatabaseReadTransaction) -> Void)
So, how do I use this function in a #noescap way?
As the caller, you can't change when the closure is executed. That's up to the read() function. If you control that function, you'll need to modify it to call the closure immediately. If you don't control it, then you can't modify how it behaves.
You can convert an asynchronous call into a synchronous call using a DispatchGroup as described in Waiting until the task finishes. However, you can't make a database call on the main queue; you risk crashing the app. As a general rule, you should just use async calls in this case (i.e. make readNovelIds also be asynchronous and take a completion handler).
The reason why Xcode bridged the objective-c block as #escaping is because the block may be executed after the function return.
Since you don’t own YapDatabase, you couldn’t modify the source code to make it non-escaped, so you may wanna make your readNovelIds function takes a closure as parameter and pass the return value through closure.
static func readNovelIds(resultHandler: #escaping ([String]?) -> ()) {
let account = XFAccountManager.share().account
var events: [XFNovelClickEvent]?
OTRDatabaseManager.shared.readOnlyDatabaseConnection?.read({ (transaction) in
events = XFNovelClickEvent.allNovelClickEvents(accountId: account.uniqueId, transaction: transaction)
if let clickEvents = events {
let readNovelsIds = clickEvents.map {
$0.bookId ?? ""
}
resultHandler(readNovelsIds)
}
resultHandler(nil)
})
}
If the method is in fact synchronous (i.e. it will not allow the block to escape its context), the Objective C header method should be decorated with NS_NOESCAPE. Looking at the documentation (which does say it is synchronous), and the implementation, it should be annotated that way.
- (void)readWithBlock:(void (NS_NOESCAPE ^)(YapDatabaseReadTransaction *transaction))block;
That, I believe, should allow the Swift interface importer to add the #noescaping declaration. You should probably file a bug request on the YapDatabase project; they can change it there.

Cannot call value of non-function type 'HTTPURLResponse?

It is giving me the Error: Cannot call value of non-function type 'HTTPURLResponse?'
It should be caused by #escaping but I cannot make it work in the sentence below. Please help, Swift 3.0, AlamofireImage.
There are other similar answers but cannot make them work with my code below.
func getNetworkImage(_ urlString: String, completion: #escaping ((UIImage) -> Void)) -> (ImageRequest) {
let queue = decoder.queue.underlyingQueue
let request = Alamofire.request(urlString)
let imageRequest = ImageRequest(request: request)
imageRequest.request.response(
queue: queue,
responseSerializer: Request.imageResponseSerializer(),
completionHandler: { response in
guard let image = response.result.value else {
return
}
let decodeOperation = self.decodeImage(image) { image in
completion(image)
self.cacheImage(image, urlString: urlString)
}
imageRequest.decodeOperation = decodeOperation
}
)
return imageRequest
}
The error message: Cannot call value of non-function type 'HTTPURLResponse?' is telling you that the instance variable value can not be accessed. The bad thing is that it is telling you this in the wrong line of code. That may be the reason why it was so difficult to find.
The structure of the response object has changed. You code:
guard let image = response.result.value else {
return
}
Won't work anymore because the response object is of type DefaultDataResponse not HTTPResponse...
In order to access the data/image you need to go with response.response. depending on what you want to access:
response.response?.value(forKey: "")
Check the other method calls and properties. I believe you will find the data you are looking for.

Objective-C To Swift: void (^safeHandler)(UIBackgroundFetchResult)

I am porting some AppDelegate code for a plugin Objective-C to Swift.
My Objective-C is pretty good but I am stumped at understanding whats going on here. This is the code I am stuck on porting:
void (^safeHandler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result){
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(result);
});
};
I understand that the the ^ is for code blocks, but looking at this as a whole I am lost as to what is going on and how to port this to Swift.
I don't understand how there are two bracketed portions here:
void (^safeHandler)(UIBackgroundFetchResult)
If you can advise what that syntax is and how to port to Swift I would greatly appreciate it!
It's a block, which is a closure in Swift. This block is named safeHandler and takes a UIBackgroundFetchResult parameter and returns void.
This is the equivalent type:
let safeHandler: (UIBackgroundFetchResult) -> ()
And the whole thing would be
let safeHandler: (UIBackgroundFetchResult) -> () = { result in
dispatch_async(dispatch_get_main_queue()) {
completionHandler(result)
}
}
Note: block syntax is notoriously wonky. You can use this site to see the various block syntax forms: http://goshdarnblocksyntax.com/

Establish callback in Swift for PubNub 4.0 to receive messages

It appears to me that the documentation PubNub has for getting started in Swift don't apply to versions earlier than PubNub 4.0. I can't successfully establish a callback to register with PubNub.
My code:
class Communicator: NSObject, PNObjectEventListener {
var pubNubClient: PubNub
override init(){
let config = PNConfiguration(
publishKey: "my_publish_key",
subscribeKey: "my_subscribe_key"
)
pubNubClient = PubNub.clientWithConfiguration(config);
super.init()
pubNubClient.addListener(self)
pubNubClient.subscribeToChannels(["my_channel"], withPresence: false)
}
func didReceiveMessage(client: PubNub!, message: PNMessageResult!){
/* THIS METHOD NEVER GETS REACHED */
}
}
Digging into the PubNub source a bit, this is the area that seems to be having problems:
- (void)addListener:(id <PNObjectEventListener>)listener {
dispatch_async(self.resourceAccessQueue, ^{
if ([listener respondsToSelector:#selector(client:didReceiveMessage:)]) {
/* this block is never reached!!! */
[self.messageListeners addObject:listener];
}
/* Remaining Lines Stripped Away */
});
}
I'm still relatively new to Swift and integrating with Objective C. I'm curious if there's a problem with the respondsToSelector since the Objective C code is referencing Swift code.
The messages are definitely getting passed; there's another lower level function in the PubNub library that's logging all the messages received.
Any help would be much appreciated.
Versions prior to 4.0 are deprecated and wont work exactly how they used to.
I would recommend migrating over to the newest (4.0) SDK entirely, the new iOS SDK has removed a lot of bloat and compiles much faster. To get started view this tutorial.
To summarize, instantiating a PubNub client look as follows:
let config = PNConfiguration(
publishKey: "Your_Pub_Key",
subscribeKey: "Your_Sub_Key")
client = PubNub.clientWithConfiguration(config)
client?.addListener(self)
client?.subscribeToChannels(["Your_Channel"], withPresence: false)
And the new didReceiveMessage function looks as follows:
func client(client: PubNub!, didReceiveMessage message: PNMessageResult!, withStatus status: PNErrorStatus!) {
//Do Something like
//println(message)
}
Resolved by adding:
func client(client: PubNub!, didReceiveMessage message: PNMessageResult!) {
}
The documentation on how to parse the received PNMessageResult is scant. Here's how I handled it:
func client(client: PubNub!, didReceiveMessage message: PNMessageResult!) {
let encodedMessage = message.data.valueForKey("message") as! NSDictionary
let messageType = encodedMessage["meta"]! as! String
let messageString = encodedMessage["data"]!["msg"]! as! String
print("PubNub: [\(messageType)] \(messageString)")
}
add _ client works for me!
func client(_ client: PubNub, didReceiveMessage message: PNMessageResult) {
print("Pubnub Message: \(message)")
}

Alamofire put request

Try to get Alamofire put request to work, but system shows "Extra Argument in Call"
Alamofire.request(.PUT, apiUrl,params,ParameterEncoding.JSON)
.responseJOSN{ (request, response, products: [Product]?,error) in
println(request)
println(response)
println(data)
println(error)
}
Anyone can solve this problem?
You have multiple issues in your code sample. Here's a corrected version that should get you going:
let apiURLString = "whatever/your/url/is"
let parameters: [String: AnyObject] = [:] // fill in your params
let request = Alamofire.request(.PUT, apiURLString, parameters: parameters, encoding: .JSON)
request.responseJSON { request, response, json, error in
println(request)
println(response)
println(json)
println(error)
}
I would also encourage you to really read through the Alamofire README in depth. It has some great information and should make it much easier for you to get the basics working.
As 'alamofire' '~2.0' has changed the in number of parameters
You can try with the following block of code:
Alamofire.request(.PUT,apiUrl,params).responseJSON { request, response, result in
print(request)
print(response)
print(result.value)
if(result.isSuccess){
//Do in success block
}else{
//Do in failure block
}
}
Alamofire.request does not have error handler parameter available, hence it was showing you that "Extra Argument in Call"