PubNub offers this snippet in the iOS SDK to call some client methods before the clients gets suspended as a result of the app resigning from active:
- (void)pubnubClient:(PubNub *)client willSuspendWithBlock:(void(^)(void(^)(void(^)(void))))preSuspensionBlock {
if ([client isConnected]) {
preSuspensionBlock(^(void(^completionBlock)(void)){
[client sendMessage:#"Hello my friend" toChannel:[PNChannel channelWithName:#"boom"]
withCompletionBlock:^(PNMessageState state, id data) {
if (state != PNMessageSending) {
NSString *message = #"Message has been sent";
if (state == PNMessageSendingError) {
// Handle message sending error
}
// Always call this block as soon as required amount of tasks completed.
completionBlock();
}
}];
});
}
}
Now XCode is smart enough to convert the crazy blocks syntax in the method declaration to this, which I guess is fine:
public func pubnubClient(client: PubNub!, willSuspendWithBlock preSuspensionBlock: (((((() -> Void)!) -> Void)!) -> Void)!)
I really can't figure out how to port this line though:
preSuspensionBlock(^(void(^completionBlock)(void))
Blocks syntax was always killing me.
The objective-c code is a preSuspension method accepts a block that returns a block that accepts no arguments. Try something like this:
var x: (()-> () -> Void) = { () -> ()->Void in
return {() -> Void in
println("this is returned inner void block")
}
}
preSuspensionBlock(x);
PubNub.sendMessage(chatMessage, googleCloudNotification: nil, toChannel:chatChannel, storeInHistory: true) { (state, obj) -> Void in
if (state != PNMessageState.Sending){
println("Message has been sent")
if(state == PNMessageState.SendingError){
completionHandler(responseStatus: RespStatus.unExpectedServerError)
}
else{
completionHandler(responseStatus: RespStatus.responseSuccess)
}
}
}
Related
I have a KMM app, and there is code:
fun getWeather(callback: (WeatherInfo) -> Unit) {
println("Start loading")
GlobalScope.launch(ApplicationDispatcher) {
while (true) {
val response = httpClient.get<String>(API_URL) {
url.parameters.apply {
set("q", "Moscow")
set("units", "metric")
set("appid", weatherApiKey())
}
println(url.build())
}
val result = Json {
ignoreUnknownKeys = true
}.decodeFromString<WeatherApiResponse>(response).main
callback(result)
// because ApplicationDispatcher on IOS do not support delay
withContext(Dispatchers.Default) { delay(DELAY_TIME) }
}
}
}
And if I replace withContext(Dispatchers.Default) { delay(DELAY_TIME) } with delay(DELAY_TIME) execution is never returned to while cycle and it will have only one iteration.
And ApplicationDispatcher for IOS looks like:
internal actual val ApplicationDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue())
internal class NsQueueDispatcher(
private val dispatchQueue: dispatch_queue_t
) : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatch_async(dispatchQueue) {
block.run()
}
}
}
And from delay source code I can guess, that DefaultDelay should be returned and there is should be similar behaviour with/without withContext(Dispatchers.Default)
/** Returns [Delay] implementation of the given context */
internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
Thanks!
P.S. I got ApplicationDispatcher from ktor-samples.
Probably ApplicationDispatcher is some old stuff, you don't need to use it anymore:
CoroutineScope(Dispatchers.Default).launch {
}
or
MainScope().launch {
}
And don't forget to use -native-mt version of coroutines, more info in this issue
still learning the basics. I have a function in which there is a block, that needs to be repeated without calling the whole function again. How is this done in Swift?
func connected(to peripheral: Peripheral) {
let cwConnection = CWStatusBarNotification()
cwConnection.display(withMessage: "Ring Connected", forDuration: 3)
BluejayManager.shared.getHeliosInfo { (success) in
if success {
// Go on
} else {
// Repeat this block (BluejayManager.shared.getHeliosInfo)
}
}
}
Hey Riyan it's just simple. Here is the solution to your problem. Just put the block in other small method and when you just need to call that block call that small function.
func connected(to peripheral: Peripheral) {
let cwConnection = CWStatusBarNotification()
cwConnection.display(withMessage: "Ring Connected", forDuration: 3)
self.callBluejayManagerShared() // Call of block from method
}
func callBluejayManagerShared(){
BluejayManager.shared.getHeliosInfo { (success) in
if success {
// Go on
} else {
// Repeat this block (BluejayManager.shared.getHeliosInfo)
self.callBluejayManagerShared()
}
}
}
Now when you just want to call block you just need to call self.callBluejayManagerShared() method.
Hope this help you
You can use repeat - while around and BluejayManager.shared.getHeliosInfo check for success as break condition:
repeatGetInfo: repeat {
BluejayManager.shared.getHeliosInfo
{ (success) in
if success
{
// do your stuff.
break repeatGetInfo
} else
{
continue repeatGetInfo
}
}
} while true
Hope this helps
I am using PromiseKit and would like to force sequential download of JSONs. The count of JSONs might change.
I have read this about chaining.
If I had a fixed number of say 3 downloads, this would be fine.
But what if I had a changing count of download that I would like to download sequentially?
This is my code for 2 URLs. I wonder how I could do this with dateUrlArray[i] iteration over the array?
- (void)downloadJSONWithPromiseKitDateArray:(NSMutableArray *)dateUrlArray {
[self.operationManager GET:dateUrlArray[0]
parameters:nil]
.then(^(id responseObject, AFHTTPRequestOperation *operation) {
NSDictionary *resultDictionary = (NSDictionary *) responseObject;
Menu *menu = [JsonMapper mapMenuFromDictionary:resultDictionary];
if (menu) {
[[DataAccess instance] addMenuToRealm:menu];
}
return [self.operationManager GET:dateUrlArray[1]
parameters:nil];
}).then(^(id responseObject, AFHTTPRequestOperation *operation) {
NSDictionary *resultDictionary = (NSDictionary *) responseObject;
Menu *menu = [JsonMapper mapMenuFromDictionary:resultDictionary];
if (menu) {
[[DataAccess instance] addMenuToRealm:menu];
}
})
.catch(^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleCatchwithError:error];
});
}).finally(^{
dispatch_async(dispatch_get_main_queue(), ^{
DDLogInfo(#".....finally");
});
});
}
The concept you're looking for is thenable chaining. You want to chain multiple promises in a for loop.
My Objective-C is really rusty - but it should look something like:
// create an array for the results
__block NSMutableArray *results = [NSMutableArray arrayWithCapacity:[urls count]];
// create an initial promise
PMKPromise *p = [PMKPromise promiseWithValue: nil]; // create empty promise
for (id url in urls) {
// chain
p = p.then(^{
// chain the request and storate
return [self.operationManager GET:url
parameters:nil].then(^(id responseObject, AFHTTPRequestOperation *operation) {
[results addObject:responseObject]; // reference to result
return nil;
});
});
}
p.then(^{
// all results available here
});
For those of us looking for a Swift 2.3 solution:
import PromiseKit
extension Promise {
static func resolveSequentially(promiseFns: [()->Promise<T>]) -> Promise<T>? {
return promiseFns.reduce(nil) { (fn1: Promise<T>?, fn2: (()->Promise<T>)?) -> Promise<T>? in
return fn1?.then({ (_) -> Promise<T> in
return fn2!()
}) ?? fn2!()
}
}
}
Note that this function returns nil if the promises array is empty.
Example of use
Below is an example of how to upload an array of attachments in sequence:
func uploadAttachments(attachments: [Attachment]) -> Promise<Void> {
let promiseFns = attachments.map({ (attachment: Attachment) -> (()->Promise<Void>) in
return {
return self.uploadAttachment(attachment)
}
})
return Promise.resolveSequentially(promiseFns)?.then({}) ?? Promise()
}
func uploadAttachment(attachment: Attachment) -> Promise<Void> {
// Do the actual uploading
return Promise()
}
Thanks for Vegard's answer and I rewrite for Swift 3:
extension Promise {
static func resolveSequentially(promiseFns: [()->Promise<T>]) -> Promise<T>? {
return promiseFns.reduce(nil) { (fn1: Promise<T>?, fn2: (()->Promise<T>)?) -> Promise<T>? in
return fn1?.then{ (_) -> Promise<T> in
return fn2!()
} ?? fn2!()
}
}
}
/* Example */
func uploadAttachments(_ attachments: [Attachment]) -> Promise<Void> {
let promiseFns = attachments.map({ (attachment: Attachment) -> (()->Promise<Void>) in
return {
return self. uploadAttachment(attachment)
}
})
return Promise.resolveSequentially(promiseFns: promiseFns)?.then{Void -> Void in} ?? Promise { Void -> Void in }
}
Here is code for two closures in two different IBAction button presses. The desired outcome is for the button press to turn on/off an LED, then to access a light sensor and read the light value after the change in LED status.
What happens is a race condition where the function getVariable runs and returns before the callFunction has implemented the change. The result is that the value displayed in getLightLabel.text is that of the prior condition, not the current condition.
My question is how to rewrite the code below so that myPhoton!.getVariable does not execute until after the myPhoton!.callFunction has returned (completed its task).
I have tried placing getVariable inside callFunction, both before and after the } closing if (error == nil), but the result was identical to the code shown here.
#IBAction func lightOn(sender: AnyObject) {
let funcArgs = [1]
myPhoton!.callFunction("lightLed0", withArguments: funcArgs) { (resultCode : NSNumber!, error : NSError!) -> Void in
if (error == nil) {
self.lightStateLabel.text = "LED is on"
}
}
myPhoton!.getVariable("Light", completion: { (result:AnyObject!, error:NSError!) -> Void in
if let e = error {
self.getLightLabel.text = "Failed reading light"
}
else {
if let res = result as? Float {
self.getLightLabel.text = "Light level is \(res) lumens"
}
}
})
}
#IBAction func lightOff(sender: AnyObject) {
let funcArgs = [0]
myPhoton!.callFunction("lightLed0", withArguments: funcArgs) { (resultCode : NSNumber!, error : NSError!) -> Void in
if (error == nil) {
self.lightStateLabel.text = "LED is off"
}
}
myPhoton!.getVariable("Light", completion: { (result:AnyObject!, error:NSError!) -> Void in
if let e = error {
self.getLightLabel.text = "Failed reading light"
}
else {
if let res = result as? Float {
self.getLightLabel.text = "Light level is \(res) lumens"
}
}
})
}
Here is the callFunction comments and code from the .h file. This SDK is written in Objective C. I am using it in Swift with a bridging header file.
/**
* Call a function on the device
*
* #param functionName Function name
* #param args Array of arguments to pass to the function on the device. Arguments will be converted to string maximum length 63 chars.
* #param completion Completion block will be called when function was invoked on device. First argument of block is the integer return value of the function, second is NSError object in case of an error invoking the function
*/
-(void)callFunction:(NSString *)functionName withArguments:(NSArray *)args completion:(void (^)(NSNumber *, NSError *))completion;
/*
-(void)addEventHandler:(NSString *)eventName handler:(void(^)(void))handler;
-(void)removeEventHandler:(NSString *)eventName;
*/
Here is the .m file code
-(void)callFunction:(NSString *)functionName withArguments:(NSArray *)args completion:(void (^)(NSNumber *, NSError *))completion
{
// TODO: check function name exists in list
NSURL *url = [self.baseURL URLByAppendingPathComponent:[NSString stringWithFormat:#"v1/devices/%#/%#", self.id, functionName]];
NSMutableDictionary *params = [NSMutableDictionary new]; //[self defaultParams];
// TODO: check response of calling a non existant function
if (args) {
NSMutableArray *argsStr = [[NSMutableArray alloc] initWithCapacity:args.count];
for (id arg in args)
{
[argsStr addObject:[arg description]];
}
NSString *argsValue = [argsStr componentsJoinedByString:#","];
if (argsValue.length > MAX_SPARK_FUNCTION_ARG_LENGTH)
{
// TODO: arrange user error/codes in a list
NSError *err = [self makeErrorWithDescription:[NSString stringWithFormat:#"Maximum argument length cannot exceed %d",MAX_SPARK_FUNCTION_ARG_LENGTH] code:1000];
if (completion)
completion(nil,err);
return;
}
params[#"args"] = argsValue;
}
[self setAuthHeaderWithAccessToken];
[self.manager POST:[url description] parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completion)
{
NSDictionary *responseDict = responseObject;
if ([responseDict[#"connected"] boolValue]==NO)
{
NSError *err = [self makeErrorWithDescription:#"Device is not connected" code:1001];
completion(nil,err);
}
else
{
// check
NSNumber *result = responseDict[#"return_value"];
completion(result,nil);
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
if (completion)
completion(nil,error);
}];
}
One solution is to put the second closure inside the first, where the first returns and provides and Error value. If no error,then execuet the second closure. That is one way to tightly couple the two closures without resorting to semaphores or other messaging schemes.
In this application, the problem I was encountering cannot be solved on the IOS/Swift side of the stack. The cloud API and embedded uP are not tightly coupled, so the cloud returns to the IOS with a completion before the full function code has run on the Particle uP.
The solution to this overall problem actually lies in either modifying the cloud API or adding some additional code to the uP firmware to tightly couple the process to the IOS app with additional communication.
I'm trying to implement the example LoopBack iOS app in Swift
Create a LoopBack iOS app: part one
and I'm having some trouble translating from the ObjectiveC
- (void) getBooks
{
//Error Block
void (^loadErrorBlock)(NSError *) = ^(NSError *error){
NSLog(#"Error on load %#", error.description);
};
void (^loadSuccessBlock)(NSArray *) = ^(NSArray *models){
NSLog(#"Success count %d", models.count);
self.tableData = models;
[self.myTable reloadData];
};
//This line gets the Loopback model "book" through the adapter defined in AppDelegate
LBModelRepository *allbooks = [[booksAppDelegate adapter] repositoryWithModelName:prototypeName];
//Logic - Get all books. If connection fails, load the error block, if it passes, call the success block and pass allbooks to it.
[allbooks allWithSuccess:loadSuccessBlock failure:loadErrorBlock];
};
Here's my version
func getBooks() {
var errorBlock = {
(error: NSError!) -> Void in
NSLog("Error on load %#", error.description)
}
var successBlock = {
(models: NSArray!) -> Void in
NSLog("Success count %d", models.count)
self.tableData = models
self.booksTable.reloadData()
}
// get the "book" model
var allBooks: LBModelRepository = adapter.repositoryWithModelName(prototypeName)
// get all books
allBooks.allWithSuccess(successBlock, errorBlock)
}
but I get a compiler error on the call to allWithSuccess:
Cannot convert the expressions type 'Void' to type 'LBModelAllSuccessBlock!'
What am I missing?
UPDATE:
If I declare the success block as follows, it works:
var successBlock = {
(models: AnyObject[]!) -> () in
self.tableData = models
self.booksTable.reloadData()
}
Thanks for the answer!!!!
If anyone is looking for the last version of Swift and LoopBack iOS SDK, it worked for me like this:
func getBooks() {
// Error Block
let errorBlock = {
(error: NSError!) -> Void in
NSLog("Error on load %#", error.description)
}
// Success Block
let successBlock = {
(models: [AnyObject]!) -> () in
self.tableData = models
self.myTable.reloadData()
}
// This line gets the Loopback model "book" through the adapter defined in AppDelegate
let allBooks:LBPersistedModelRepository = AppDelegate.adapter.repositoryWithModelName(prototypeName, persisted: true) as! LBPersistedModelRepository
// Logic - Get all books. If connection fails, load the error block, if it passes, call the success block and pass allbooks to it.
allBooks.allWithSuccess(successBlock, failure: errorBlock)
}