I'm developing an iOS application with Simple Notification Service (SNS) from Amazon Web Services. At this point the app registers the device to a Topic and can receive push notifications, which are published to the Topic. It is possible to subscribe a device to many Topics.
Now I'm trying to unsubscribe a device from a specific Topic, but the SNSUnsubscribeRequest needs a SubscriptionARN. I've tried to use the EndpointARN from the device, but it seems I've to use an extra SubscriptionARN for the combination of EndpointARN and TopicARN. How do I get this ARN?
In this post: How do you get the arn of a subscription? they ask for the whole list of subscribers and compare each EndpointARN with the EndpointARN of the device. This cant be the right way i think.
Subscribe to Topic
// Check if endpoint exist
if (endpointARN == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
[[self universalAlertsWithTitle:#"endpointARN not found!" andMessage:#"Please create an endpoint for this device before subscribe to topic"] show];
});
return NO;
}
// Create topic if not exist
NSString *topicARN = [self findTopicARNFor:topic];
if (!topicARN) {
[self createTopic:topic];
topicARN = [self findTopicARNFor:topic];
}
// Subscribe to topic if exist
if (topicARN) {
SNSSubscribeRequest *subscribeRequest = [[SNSSubscribeRequest alloc] initWithTopicArn:topicARN andProtocol:#"application" andEndpoint:endpointARN];
SNSSubscribeResponse *subscribeResponse = [snsClient subscribe:subscribeRequest];
if (subscribeResponse.error != nil) {
NSLog(#"Error: %#", subscribeResponse.error);
dispatch_async(dispatch_get_main_queue(), ^{
[[self universalAlertsWithTitle:#"Subscription Error" andMessage:subscribeResponse.error.userInfo.description] show];
});
return NO;
}
}
return YES;
The method findTopicARNForTopic already iterates over the list of Topics and compare the suffix with the topic name. I really don't know if this is the best practice.
Unsubscribe from Topic
NSString *topicARN = [self findTopicARNFor:topic];
if (topicARN) {
SNSUnsubscribeRequest *unsubscribeRequest = [[SNSUnsubscribeRequest alloc] initWithSubscriptionArn:topicARN];
SNSUnsubscribeResponse *unsubscribeResponse = [snsClient unsubscribe:unsubscribeRequest];
if (unsubscribeResponse.error) {
NSLog(#"Error: %#", unsubscribeResponse.error);
}
}
For now I ask for the whole subscriber list and compare the EndpointARN with the EndpointARN of the Device. With the following method i get the subscription arn:
- (NSString *)findSubscriptionARNForTopicARN:(NSString *)topicARN
{
// Iterate over each subscription arn list for a topic arn
NSString *nextToken = nil;
do {
SNSListSubscriptionsByTopicRequest *listSubscriptionRequest = [[SNSListSubscriptionsByTopicRequest alloc] initWithTopicArn:topicARN];
SNSListSubscriptionsByTopicResponse *response = [snsClient listSubscriptionsByTopic:listSubscriptionRequest];
if (response.error) {
NSLog(#"Error: %#", response.error);
return nil;
}
// Compare endpoint arn of subscription arn with endpoint arn of this device
for (SNSSubscription *subscription in response.subscriptions) {
if ([subscription.endpoint isEqualToString:endpointARN]) {
return subscription.subscriptionArn;
}
}
nextToken = response.nextToken;
} while (nextToken != nil);
return nil;
}
and with this method i remove the device from a topic:
- (void)unsubscribeDeviceFromTopic:(NSString *)topic
{
NSString *subscriptionARN = [self findSubscriptionARNForTopic:topic];
if (subscriptionARN) {
SNSUnsubscribeRequest *unsubscribeRequest = [[SNSUnsubscribeRequest alloc] initWithSubscriptionArn:subscriptionARN];
SNSUnsubscribeResponse *unsubscribeResponse = [snsClient unsubscribe:unsubscribeRequest];
if (unsubscribeResponse.error) {
NSLog(#"Error: %#", unsubscribeResponse.error);
}
}
}
You could also store the SubscriptionArn in the SubscribeResponse and use this value in the UnSubscribeRequest.
Related
I've done as the guide says
This is the message manager
[GNSMessageManager setDebugLoggingEnabled:YES];
messageManager = [[GNSMessageManager alloc] initWithAPIKey:API_KEY paramsBlock:^(GNSMessageManagerParams *params) {
params.bluetoothPowerErrorHandler = ^(BOOL hasError) {
// Update the UI for Bluetooth power
};
params.bluetoothPermissionErrorHandler = ^(BOOL hasError) {
// Update the UI for Bluetooth permission
};
}];
These are my methods to publish and subscribe with the Nearby API.
- (IBAction)onPublish:(id)sender {
NSLog(#"publish");
NSString* str = #"hello world";
NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
GNSMessage* message = [GNSMessage messageWithContent:data];
id<GNSPublication> publication = [messageManager publicationWithMessage:message paramsBlock:^(GNSPublicationParams *publicationParams) {
publicationParams.strategy = [GNSStrategy strategyWithParamsBlock:^(GNSStrategyParams * strategyParams) {
strategyParams.allowInBackground = YES;
strategyParams.discoveryMediums = kGNSDiscoveryMediumsBLE;
strategyParams.discoveryMode = kGNSDiscoveryModeDefault;
}];;
}];
}
- (IBAction)onSubscribe:(id)sender {
NSLog(#"subscribe");
id<GNSSubscription> subscription = [messageManager subscriptionWithMessageFoundHandler:^(GNSMessage *msg) {
// Add the name to a list for display
NSLog(#"message found %#", [msg description]);
} messageLostHandler:^(GNSMessage *msg) {
// Add the name to a list for display
NSLog(#"message lost %#", [msg description]);
} paramsBlock:^(GNSSubscriptionParams *subscriptionParams) {
subscriptionParams.strategy = [GNSStrategy strategyWithParamsBlock:^(GNSStrategyParams * strategyParams) {
strategyParams.allowInBackground = YES;
strategyParams.discoveryMediums = kGNSDiscoveryMediumsBLE;
strategyParams.discoveryMode = kGNSDiscoveryModeDefault;
}];;
}];
}
Both Bletooth central and peripheral background capabilities are enabled, and the permission string for the peripheral is set.
Finally I subscribe on an iOS device and publish from another one but nothing happens.
Be sure to retain the publication and subscription objects. They stop publishing/subscribing when they're deallocated. The usual way is to store them as properties/ivars in one of your classes.
The developer docs are misleading on this point, and I apologize. We'll improve the docs in the next release.
I have a query regarding concurrency in Azure mobile client SDK.
for windows I found Conflict link for handling concurrency but I could not found the same for iOS client SDK.
Can anyone please suggest or help how to handle concurrency in iOS client SDK.
Here is the old Mobile Services page for handling conflicts with the iOS SDK. The setup for offline sync with the iOS SDK hasn't changed since then.
1) Set up an MSSyncContextDelegate and pass it to the MSSyncContext constructor when you create it.
2) Implement tableOperation:(MSTableOperation *)operation onComplete:(MSSyncItemBlock)completion in your delegate. After executing the operation, check for an MSErrorPreconditionFailed error code and decide what to do from there based on your app's needs.
- (void)tableOperation:(MSTableOperation *)operation onComplete:(MSSyncItemBlock)completion
{
[operation executeWithCompletion:^(NSDictionary *item, NSError *error) {
NSDictionary *serverItem = [error.userInfo objectForKey:MSErrorServerItemKey];
if (error.code == MSErrorPreconditionFailed) {
QSUIAlertViewWithBlock *alert = [[QSUIAlertViewWithBlock alloc] initWithCallback:^(NSInteger buttonIndex) {
if (buttonIndex == 1) { // Client
NSMutableDictionary *adjustedItem = [operation.item mutableCopy];
[adjustedItem setValue:[serverItem objectForKey:MSSystemColumnVersion] forKey:MSSystemColumnVersion];
operation.item = adjustedItem;
[self doOperation:operation complete:completion];
return;
} else if (buttonIndex == 2) { // Server
NSDictionary *serverItem = [error.userInfo objectForKey:MSErrorServerItemKey];
completion(serverItem, nil);
} else { // Cancel
[operation cancelPush];
completion(nil, error);
}
}];
NSString *message = [NSString stringWithFormat:#"Client value: %#\nServer value: %#", operation.item[#"text"], serverItem[#"text"]];
[alert showAlertWithTitle:#"Server Conflict"
message:message
cancelButtonTitle:#"Cancel"
otherButtonTitles:[NSArray arrayWithObjects:#"Use Client", #"Use Server", nil]];
} else {
completion(item, error);
}
}];
}
I am working on iOS application where I am using Twilio SDK to manage client calling through device. To implement this i am using hello monkey demo application which i have successfully imported in Xcode.
After initial setup i am able to establish connection successfully but receiving delegate is no longer working. I have gone through complete twilio documentation but no success. Please suggest any alternative or solution ASAP.
Here is my code of Hello Monkey sample project
- (void)viewDidLoad
{
NSLog(#"CLINT ID----------------------- %#",name);
//check out https://github.com/twilio/mobile-quickstart to get a server up quickly
NSString *urlString = [NSString stringWithFormat:#"https://testdemo786.herokuapp.com/token?client=%#", name];
NSURL *url = [NSURL URLWithString:urlString];
NSError *error = nil;
NSString *token = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (token == nil) {
NSLog(#"Error retrieving token: %#", [error localizedDescription]);
} else {
_phone = [[TCDevice alloc] initWithCapabilityToken:token delegate:self];
}
}
- (IBAction)dialButtonPressed:(id)sender
{
NSString *to;
if ([name isEqualToString:#"jenny"]) {
to=#"client:tommy";
}
else
{
to=#"client:jenny";
}
to=#"4nmf5j";
NSLog(#"TO---------------------%#",to);
NSDictionary *params = #{#"To": to};
_connection = [_phone connect:params delegate:nil];
}
- (IBAction)hangupButtonPressed:(id)sender
{
[_connection disconnect];
}
- (void)device:(TCDevice *)device didReceiveIncomingConnection:(TCConnection *)connection
{
NSLog(#"Incoming connection from: %#", [connection parameters][#"From"]);
if (device.state == TCDeviceStateBusy) {
[connection reject];
} else {
[connection accept];
_connection = connection;
}
}
-(void)connection:(TCConnection*)connection didFailWithError: (NSError*)error{
NSLog(#"Connection failed with error : %#", error);
}
-(void)connectionDidStartConnecting:(TCConnection*)connection{
NSLog(#"connection started");
}
-(void)connectionDidDisconnect:(TCConnection*)connection{
NSLog(#"connection disconnected");
}
-(void)connectionDidConnect:(TCConnection*)connection{
NSLog(#"connected");
}
- (void)deviceDidStartListeningForIncomingConnections: (TCDevice*)device
{
NSLog(#"Device: %# deviceDidStartListeningForIncomingConnections", device);
}
- (void)device:(TCDevice *)device didStopListeningForIncomingConnections:(NSError *)error
{
NSLog(#"Device: %# didStopListeningForIncomingConnections: %#", device, error);
}
Twilio evangelist here.
Its a bit hard to tell from the code you included, which does look correct to me.
Here are a couple of things to check:
Did you add the TCDeviceDelegate as a protocol on your interface:
#interface FooViewController() <TCDeviceDelegate>
Are you sure you passing the correct client name to the connect method, and that the TwiML being returned from your TwiML Apps Voice Request URL is including that name properly?
You could check this by looking at the Twilio Monitor to see if Twilio logged any errors when retrieving or parsing your TwiML. You can also check your Twilio call logs to see what Twilio says the outcome of the inbound and outbound call legs were.
Hope that helps.
did you set the delegate to self?
_device = [[TCDevice alloc] initWithCapabilityToken:capabilityToken delegate:self];
Problem
When I unsubscribe to my endpoints subscriptions, instruments memory graph goes from 32MB to 300MB and app is killed Jetsam. It was my understanding that AWS BFTasks clean up after themselves… If I remove the do...while and allow it to process the first 100 subscriptions, all works fine.
Process
I have about 8000 subscriptions in my development app that uses AWS SNS to push alerts to iOS devices and email. After the user selects the subscriptions they want, I remove their existing subscriptions, then subscribe them to the new ones. The [_awsSnsClient listSubscriptions:request] returns a max of 100 subscriptions, I loop through these, performing [_awsSnsClient unsubscribe:unsubscribeRequest] as needed. Then get the next 100...
Code
- (void) awsUsubscribeAllSubscriptions {
DLog(#"Removing existing subscriptions");
AWSSNSListSubscriptionsInput *request = [AWSSNSListSubscriptionsInput new];
__block Boolean sentLastUnsubscribe = NO; // YES when the last unsubscribe request was sent
__block int pendingUnsubscribe = 0; // Number of outsianding unsubscribes (async process not yet complete)
do {
[[[_awsSnsClient listSubscriptions:request] continueWithSuccessBlock:^id(BFTask *task) {
AWSSNSListSubscriptionsResponse *response = (AWSSNSListSubscriptionsResponse *)task.result;
NSString *nextToken = response.nextToken;
NSArray *subscriptions = response.subscriptions;
for (AWSSNSSubscription *subscription in subscriptions) {
if ([subscription.endpoint isEqualToString:_awsPlatformEndpoint.endpointArn]) {
AWSSNSUnsubscribeInput *unsubscribeRequest = [AWSSNSUnsubscribeInput new];
unsubscribeRequest.subscriptionArn = subscription.subscriptionArn;
pendingUnsubscribe++;
[[[_awsSnsClient unsubscribe:unsubscribeRequest] continueWithSuccessBlock:^id(BFTask *task) {
DLog(#"Unsubscribed from:%#",subscription.subscriptionArn);
return nil;
}] continueWithBlock:^id(BFTask *task) {
pendingUnsubscribe--;
if (task.error) {
// failed with error
ALog(#"Error unsubscribing to: %#, %#, %#", subscription, [task.error localizedDescription], [task.error localizedFailureReason]);
}
// If we have processed the last unsubscribe, then create topics and subscribe
if (sentLastUnsubscribe && (pendingUnsubscribe <= 0)) {
[self awsCreateTopicsAndSubscriptions];
}
return nil;
}];
}
}
request.nextToken = nextToken;
if(!nextToken) sentLastUnsubscribe = YES; // Reached the end of the SNS subscriptions
return nil;
}] continueWithBlock:^id(BFTask *task) {
if (task.error) {
// failed with error
ALog(#"Error listing subscriptions: %#, %#", [task.error localizedDescription], [task.error localizedFailureReason]);
}
return nil;
}];
} while (!sentLastUnsubscribe);
}
Is there a better way to remove all SNS subscriptions from an endpoint? Is there a way for force release any retained data during this loop?
Solution
Using both recursion and - waitUntilFinished. Memory load is now much lower.
-(void) awsUnsubscribeFromAllSubscriptionsWithNextToken:(NSString *)nextToken AndSubscribe:(BOOL)subscribe {
AWSSNSListSubscriptionsInput *request = [AWSSNSListSubscriptionsInput new];
request.nextToken = nextToken;
[[[_awsSnsClient listSubscriptions:request] continueWithSuccessBlock:^id(BFTask *task) {
AWSSNSListSubscriptionsResponse *response = (AWSSNSListSubscriptionsResponse *)task.result;
NSArray *subscriptions = response.subscriptions;
for (AWSSNSSubscription *subscription in subscriptions) {
if ([subscription.endpoint isEqualToString:_awsPlatformEndpoint.endpointArn]) {
DLog(#"Unsubscribe from %#", subscription.topicArn);
AWSSNSUnsubscribeInput *unsubscribeRequest = [AWSSNSUnsubscribeInput new];
unsubscribeRequest.subscriptionArn = subscription.subscriptionArn;
[[_awsSnsClient unsubscribe:unsubscribeRequest] waitUntilFinished];
}
}
if(response.nextToken) {
[self awsUnsubscribeFromAllSubscriptionsWithNextToken:response.nextToken AndSubscribe:subscribe];
} else if (subscribe) {
[self awsCreateTopicsAndSubscriptions];
}
return nil;
}] continueWithBlock:^id(BFTask *task) {
if (task.error) {
// failed with error
ALog(#"Error listing subscriptions: %#, %#", [task.error localizedDescription], [task.error localizedFailureReason]);
}
return nil;
}];
}
Please note that - listSubscriptions: is an asynchronous method and returns immediately. You are calling an async method in a for loop. It means you are potentially calling - listSubscriptions: hundreds or even thousands of times in a short period.
The easiest thing you can do to fix it is to call - waitUntilFinished at the end of the async block. It makes the entire method synchronous
However, in general, you should avoid calling - waitUntilFinished as much as possible. AWSKinesisRecorderTests.m has a function to call - getRecords: recursively. You can adopt a similar pattern.
Hitting a very strange issue here, which seems to me to be an issue with the EventKit API and I just want to check it's nothing I'm doing.
Test case 1:
Reminders are enabled in Privacy for the app
The device has an iCloud account, but it's set to not sync reminders
I can create a local reminders list in the 'Reminders' app from Apple
ISSUE - Trying to create a new calendar of entity type EKEntityTypeReminder with a source of type EKSourceTypeLocal fails
Test case 2:
Reminders are enabled in Privacy for the app
The device has no iCloud account
I can create a local reminders list in the 'Reminders' app from Apple
I can create a local reminders list via the EK API
Test case 3:
Reminders are enabled in Privacy for the app
The device has an iCloud account and is set to sync reminders
I can create an iCloud reminders list in the 'Reminders' app from Apple
I can create an iCloud reminders list via the EK API
Am I going crazy or is this a bug with the API?
Cheers!
Here's the code:
EKCalendar *remindersList = nil;
NSString *remindersListIdent = [[NSUserDefaults standardUserDefaults] objectForKey:kReminderListIdentDefaultsKey];
if(remindersListIdent) {
remindersList = [store calendarWithIdentifier:remindersListIdent];
if(remindersList) {
// has valid reminders list so save reminder and return (don't run rest of function)
[self saveReminder:reminder toCalendar:remindersList withTypeLabel:reminderTypeLabel];
return;
}
}
NSArray *currentCalendars = [store calendarsForEntityType:EKEntityTypeReminder];
for(EKCalendar *cal in currentCalendars) {
if([[cal title] isEqualToString:#"My App Name"]) {
remindersList = cal;
[[NSUserDefaults standardUserDefaults] setObject:[remindersList calendarIdentifier] forKey:kReminderListIdentDefaultsKey];
[[NSUserDefaults standardUserDefaults] synchronize];
[self saveReminder:reminder toCalendar:remindersList withTypeLabel:reminderTypeLabel];
return;
}
}
EKSource *localSource = nil;
for (EKSource *source in store.sources) {
if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:#"iCloud"]) {
localSource = source;
break;
}
}
if(localSource) {
remindersList = [self newCalendarListInSource:localSource];
}
if(!remindersList) {
for (EKSource *source in store.sources) {
if (source.sourceType == EKSourceTypeLocal) {
localSource = source;
remindersList = [self newCalendarListInSource:localSource];
if(remindersList) {
break;
}
}
}
}
if(!remindersList) {
dispatch_async(dispatch_get_main_queue(), ^{
// show error message
});
}
else {
[[NSUserDefaults standardUserDefaults] setObject:[remindersList calendarIdentifier] forKey:kReminderListIdentDefaultsKey];
[[NSUserDefaults standardUserDefaults] synchronize];
[self saveReminder:reminder toCalendar:remindersList withTypeLabel:reminderTypeLabel];
}
And this is the contents of newCalendarListInSource:
EKCalendar *remindersList;
remindersList = [EKCalendar calendarForEntityType:EKEntityTypeReminder eventStore:store];
remindersList.source = localSource;
[remindersList setTitle:#"My App Name"];
NSError *listCreateError = nil;
[store saveCalendar:remindersList commit:YES error:&listCreateError];
if(!listCreateError) {
return remindersList;
}
else {
NSLog(#"Failed to create reminders list with error: %#", [listCreateError localizedDescription]);
For all still having a problem with this, check your EKSourceType that you use if it exists in your phone. For Swift, try using the EKSourceType.Exchange. Note that I have an Outlook/Exchange account synced to my phone.
I know that this is very vague, but when I used EKSourceType.Exchange, it worked for me. (Play with it)