Skip to content

Commit

Permalink
feat(IoT): Adding completion callbacks for registerWithShadow and `…
Browse files Browse the repository at this point in the history
…unregisterFromShadow` methods (aws-amplify#5192)
  • Loading branch information
ruisebas authored Jun 21, 2024
1 parent 70872d2 commit a8a72a3
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 5 deletions.
40 changes: 40 additions & 0 deletions AWSIoT/AWSIoTDataManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,33 @@ shadowOperationTimeoutSeconds: double, device shadow operation timeout (default
options:(NSDictionary<NSString *, NSNumber *> * _Nullable)options
eventCallback:(void(^)(NSString *name, AWSIoTShadowOperationType operation, AWSIoTShadowOperationStatusType status, NSString *clientToken, NSData *payload))callback;

/**
Register for updates on a device shadow
@param name The device shadow to register for updates on.
@param options A dictionary with device shadow registration options. The options are:
enableDebugging: BOOL, set to YES to enable additional console debugging (default NO)
enableVersioning: BOOL, set to NO to disable versioning (default YES)
enableForeignStateUpdateNotifications: BOOL, set to YES to enable foreign state updates (default NO)
enableStaleDiscards: BOOL, set to NO to disable discarding stale updates (default YES)
enableIgnoreDeltas: BOOL, set to YES to disable delta updates (default NO)
QoS: AWSIoTMQTTQoS (default AWSIoTMQTTQoSMessageDeliveryAttemptedAtMostOnce)
shadowOperationTimeoutSeconds: double, device shadow operation timeout (default 10.0)
@param callback The function to call when updates are received for the device shadow.
@param completionCallback The function to call when the operation is completed.
@return Boolean value indicating success or failure.
*/
- (BOOL) registerWithShadow:(NSString *)name
options:(NSDictionary<NSString *, NSNumber *> * _Nullable)options
eventCallback:(void(^)(NSString *name, AWSIoTShadowOperationType operation, AWSIoTShadowOperationStatusType status, NSString *clientToken, NSData *payload))callback
completionCallback:(void(^)(void))completionCallback;

/**
Unregister from updates on a device shadow
Expand All @@ -818,6 +845,19 @@ shadowOperationTimeoutSeconds: double, device shadow operation timeout (default
*/
- (BOOL) unregisterFromShadow:(NSString *)name;

/**
Unregister from updates on a device shadow
@param name The device shadow to unregister from updates on.
@param completionCallback The function to call when the operation is completed.
@return Boolean value indicating success or failure.
*/
- (BOOL) unregisterFromShadow:(NSString *)name
completionCallback:(void(^)(void))completionCallback;

/**
Update a device shadow
Expand Down
63 changes: 58 additions & 5 deletions AWSIoT/AWSIoTDataManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#import "AWSSynchronizedMutableDictionary.h"
#import "AWSIoTModel.h"
#import "AWSCocoaLumberjack.h"

#import <stdatomic.h>

@interface AWSIoTDataShadowModel : AWSMTLModel <AWSMTLJSONSerializing>

Expand Down Expand Up @@ -912,22 +912,50 @@ - (void)createSubscriptionsForShadow:(AWSIoTDataShadow *)shadow
*/
- (BOOL)handleSubscriptionsForShadow:(NSString *)name
callback:(AWSIoTMQTTExtendedNewMessageBlock)callback {
return [self handleSubscriptionsForShadow:name callback:callback completionHandler:nil];
}

/**
Subscribes or unsubscribes from all topics associated with a given shadow, depending on whether a callback is provided or not.
@param name The name of the shadow
@param callback The callback to be triggered when messages are sent to the topics associated with the shadow. Set it to `nil` to unsubscribe from all topics.
@param completionHandler The callback called when the corresponding operation (subscribe or unsuscribe) is completed
@return Boolean value indicating whether there's a shadow with the given name.
**/
- (BOOL)handleSubscriptionsForShadow:(NSString *)name
callback:(AWSIoTMQTTExtendedNewMessageBlock)callback
completionHandler:(void(^)(void))completionHandler {
BOOL rc = NO;
AWSIoTDataShadow *shadow = (AWSIoTDataShadow *)[self.shadows objectForKey:name];

if (shadow != nil) {
__block _Atomic(NSUInteger) remainingTopics = [shadow.topics count];
AWSIoTMQTTAckBlock ackCallback = NULL;
if (completionHandler) {
ackCallback = ^void {
atomic_fetch_sub(&remainingTopics, 1);
if (remainingTopics == 0) {
completionHandler();
}
};
}

for (int i = 0; i < [shadow.topics count]; i++) {
if (callback != nil) {
if (shadow.enableDebugging == YES) {
AWSDDLogInfo(@"subscribing on %@", (NSString *)shadow.topics[i]);
}
[self subscribeToTopic:shadow.topics[i] QoS:shadow.qos extendedCallback:callback];
[self subscribeToTopic:shadow.topics[i] QoS:shadow.qos extendedCallback:callback ackCallback:ackCallback];
}
else {
if (shadow.enableDebugging == YES) {
AWSDDLogInfo(@"unsubscribing from %@", (NSString *)shadow.topics[i]);
}
[self unsubscribeTopic:shadow.topics[i]];
[self unsubscribeTopic:shadow.topics[i] ackCallback:ackCallback];
}
}
rc = YES;
Expand Down Expand Up @@ -1208,6 +1236,20 @@ - (BOOL) operationWithShadow:(NSString *)name
- (BOOL) registerWithShadow:(NSString *)name
options:(NSDictionary *)options
eventCallback:(void(^)(NSString *name, AWSIoTShadowOperationType operation, AWSIoTShadowOperationStatusType status, NSString *clientToken, NSData *payload))callback {
return [self registerWithShadow:name options:options eventCallback:callback completionHandler:nil];
}

- (BOOL) registerWithShadow:(NSString *)name
options:(NSDictionary *)options
eventCallback:(void(^)(NSString *name, AWSIoTShadowOperationType operation, AWSIoTShadowOperationStatusType status, NSString *clientToken, NSData *payload))callback
completionCallback:(void(^)(void))completionCallback {
return [self registerWithShadow:name options:options eventCallback:callback completionHandler:completionCallback];
}

- (BOOL) registerWithShadow:(NSString *)name
options:(NSDictionary *)options
eventCallback:(void(^)(NSString *name, AWSIoTShadowOperationType operation, AWSIoTShadowOperationStatusType status, NSString *clientToken, NSData *payload))callback
completionHandler:(nullable void(^)(void))completionHandler {
BOOL rc = YES;

AWSIoTDataShadow *shadow = [self.shadows objectForKey:name];
Expand Down Expand Up @@ -1291,7 +1333,8 @@ - (BOOL) registerWithShadow:(NSString *)name
// Persistently subscribe to the special topics for this shadow.
//
rc = [self handleSubscriptionsForShadow:shadow.name
callback:shadowMqttMessageHandler];
callback:shadowMqttMessageHandler
completionHandler:completionHandler];

if( rc == NO ){
AWSDDLogError(@"unable to subscribe to shadow topics for (%@)", name);
Expand All @@ -1308,13 +1351,23 @@ - (BOOL) registerWithShadow:(NSString *)name
}

- (BOOL) unregisterFromShadow:(NSString *)name {
return [self unregisterFromShadow:name completionHandler:nil];
}

- (BOOL) unregisterFromShadow:(NSString *)name
completionCallback:(void (^)(void))completionCallback {
return [self unregisterFromShadow:name completionHandler:completionCallback];
}

- (BOOL) unregisterFromShadow:(NSString *)name
completionHandler:(nullable void (^)(void))completionHandler {
BOOL rc = NO;

AWSIoTDataShadow *shadow = [self.shadows objectForKey:name];

if (shadow != nil) {
// Unsubscribe from all topics associated with this shadow.
rc = [self handleSubscriptionsForShadow:shadow.name callback:nil];
rc = [self handleSubscriptionsForShadow:shadow.name callback:nil completionHandler:completionHandler];

//invalidate the timer as the shadow is being unregistered.
[shadow.timer invalidate];
Expand Down
35 changes: 35 additions & 0 deletions AWSIoTTests/AWSIoTDataManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,41 @@ class AWSIoTDataManagerTests: XCTestCase {

}

func testRegisterAndUnregisterWithShadow_withCompletionCallbacks_shouldInvokeCallbacks() {
let connectExpectation = expectation(description: "connect expectation")
let iotDataManager = AWSIoTDataManager(forKey: "iot-data-manager-broker1")
iotDataManager.connect(
withClientId: UUID().uuidString,
cleanSession: true,
certificateId: UserDefaults.standard.string(forKey: "TestCertBroker1")!,
statusCallback: { status in
if case .connected = status {
connectExpectation.fulfill()
}
}
)
wait(for: [connectExpectation], timeout: 30)

let registerExpectation = expectation(description: "registerWithShadow expectation")
iotDataManager.register(
withShadow: "iot-shadow",
eventCallback: { _, _, _, _, _ in },
completionCallback: {
registerExpectation.fulfill()
}
)
wait(for: [registerExpectation], timeout: 5)

let unregisterExpectation = expectation(description: "unregisterFromShadow expectation")
iotDataManager.unregister(
fromShadow: "iot-shadow",
completionCallback: {
unregisterExpectation.fulfill()
}
)
wait(for: [unregisterExpectation], timeout: 5)
}

func testPublishWithoutConnect() {
let iotDataManager:AWSIoTDataManager = AWSIoTDataManager(forKey: "iot-data-manager-broker-test-without-connect")

Expand Down

0 comments on commit a8a72a3

Please sign in to comment.