Skip to content

Commit 7817fee

Browse files
[Darwin] MTRDeviceController accessor for in-memory devices (#37770)
* [Darwin] MTRDeviceController accessor for in-memory devices * Remove test log * Update src/darwin/Framework/CHIP/MTRDeviceController.mm Co-authored-by: Justin Wood <woody@apple.com> --------- Co-authored-by: Justin Wood <woody@apple.com>
1 parent b10a85a commit 7817fee

6 files changed

+85
-1
lines changed

src/darwin/Framework/CHIP/MTRDevice.mm

+2
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ - (void)dealloc
133133
// TODO: retain cycle and clean up https://github.com/project-chip/connectedhomeip/issues/34267
134134
MTR_LOG("MTRDevice dealloc: %p", self);
135135

136+
[_deviceController deviceDeallocated];
137+
136138
// Locking because _cancelAllAttributeValueWaiters has os_unfair_lock_assert_owner(&_lock)
137139
std::lock_guard lock(_lock);
138140
[self _cancelAllAttributeValueWaiters];

src/darwin/Framework/CHIP/MTRDeviceController.h

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#import <Matter/MTROperationalCertificateIssuer.h>
2323

2424
@class MTRBaseDevice;
25+
@class MTRDevice;
2526
@class MTRServerEndpoint; // Defined in MTRServerEndpoint.h, which imports MTRAccessGrant.h, which imports MTRBaseClusters.h, which imports this file, so we can't import it.
2627

2728
@class MTRDeviceControllerAbstractParameters;
@@ -82,6 +83,12 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
8283
@property (readonly, nonatomic, nullable)
8384
NSNumber * controllerNodeID MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
8485

86+
/**
87+
* Returns the list of MTRDevice instances that this controller has loaded
88+
* into memory. Returns an empty array if no devices are in memory.
89+
*/
90+
@property (readonly, nonatomic) NSArray<MTRDevice *> * devices MTR_AVAILABLE(ios(18.4), macos(15.4), watchos(11.4), tvos(18.4));
91+
8592
/**
8693
* Set up a commissioning session for a device, using the provided setup payload
8794
* to discover it and connect to it.

src/darwin/Framework/CHIP/MTRDeviceController.mm

+26
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,11 @@ - (MTRDevice * _Nullable)_deviceForNodeID:(NSNumber *)nodeID createIfNeeded:(BOO
381381
MTRDevice * deviceToReturn = [_nodeIDToDeviceMap objectForKey:nodeID];
382382
if (!deviceToReturn && createIfNeeded) {
383383
deviceToReturn = [self _setupDeviceForNodeID:nodeID prefetchedClusterData:nil];
384+
[self _callDelegatesWithBlock:^(id<MTRDeviceControllerDelegate> delegate) {
385+
if ([delegate respondsToSelector:@selector(devicesChangedForController:)]) {
386+
[delegate devicesChangedForController:self];
387+
}
388+
} logString:__PRETTY_FUNCTION__];
384389
}
385390

386391
return deviceToReturn;
@@ -416,6 +421,27 @@ - (void)removeDevice:(MTRDevice *)device
416421
}
417422
}
418423

424+
- (NSArray<MTRDevice *> *)devices
425+
{
426+
std::lock_guard lock(*self.deviceMapLock);
427+
NSMutableArray * devicesToReturn = [NSMutableArray array];
428+
429+
for (MTRDevice * device in _nodeIDToDeviceMap.objectEnumerator) {
430+
[devicesToReturn addObject:device];
431+
}
432+
433+
return devicesToReturn;
434+
}
435+
436+
- (void)deviceDeallocated
437+
{
438+
[self _callDelegatesWithBlock:^(id<MTRDeviceControllerDelegate> delegate) {
439+
if ([delegate respondsToSelector:@selector(devicesChangedForController:)]) {
440+
[delegate devicesChangedForController:self];
441+
}
442+
} logString:__PRETTY_FUNCTION__];
443+
}
444+
419445
- (BOOL)setOperationalCertificateIssuer:(nullable id<MTROperationalCertificateIssuer>)operationalCertificateIssuer
420446
queue:(nullable dispatch_queue_t)queue
421447
{

src/darwin/Framework/CHIP/MTRDeviceControllerDelegate.h

+6
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
106106
*/
107107
- (void)controller:(MTRDeviceController *)controller
108108
suspendedChangedTo:(BOOL)suspended MTR_AVAILABLE(ios(18.2), macos(15.2), watchos(11.2), tvos(18.2));
109+
110+
/**
111+
* Notify the delegate when the list of MTRDevice objects in memory has changed.
112+
*/
113+
- (void)devicesChangedForController:(MTRDeviceController *)controller MTR_AVAILABLE(ios(18.4), macos(15.4), watchos(11.4), tvos(18.4));
114+
109115
@end
110116

111117
typedef NS_ENUM(NSUInteger, MTRPairingStatus) {

src/darwin/Framework/CHIP/MTRDeviceController_Internal.h

+5
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ NS_ASSUME_NONNULL_BEGIN
116116
- (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(nullable NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)prefetchedClusterData;
117117
- (void)removeDevice:(MTRDevice *)device;
118118

119+
/**
120+
* Called by MTRDevice object when their dealloc is called, so the controller can notify interested delegate that active devices have changed
121+
*/
122+
- (void)deviceDeallocated;
123+
119124
@end
120125

121126
/**

src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m

+39-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@ - (void)controller:(MTRDeviceController *)controller
103103
}
104104
@end
105105

106+
@interface MTRPerControllerStorageTestsDeallocDelegate : NSObject <MTRDeviceControllerDelegate>
107+
@property (nonatomic, nullable) dispatch_block_t onDevicesChanged;
108+
@end
109+
110+
@implementation MTRPerControllerStorageTestsDeallocDelegate
111+
- (void)devicesChangedForController:(MTRDeviceController *)controller
112+
{
113+
if (self.onDevicesChanged) {
114+
self.onDevicesChanged();
115+
}
116+
}
117+
@end
118+
106119
@interface MTRPerControllerStorageTestsCertificateIssuer : NSObject <MTROperationalCertificateIssuer>
107120
- (instancetype)initWithRootCertificate:(MTRCertificateDERBytes)rootCertificate
108121
intermediateCertificate:(MTRCertificateDERBytes _Nullable)intermediateCertificate
@@ -3652,9 +3665,31 @@ - (void)testMTRDeviceDealloc
36523665
// We should have established CASE using our operational key.
36533666
XCTAssertEqual(operationalKeys.signatureCount, 1);
36543667

3668+
// Before the test, clear the device controller in-memory MapTable
3669+
[controller forgetDeviceWithNodeID:deviceID];
3670+
36553671
__block BOOL subscriptionReportEnd1 = NO;
36563672
XCTestExpectation * subscriptionCallbackDeleted = [self expectationWithDescription:@"Subscription callback deleted"];
3673+
XCTestExpectation * controllerAddedDevice = [self expectationWithDescription:@"Controller added device"];
3674+
XCTestExpectation * controllerRemovedDevice = [self expectationWithDescription:@"Controller removed device"];
36573675
@autoreleasepool {
3676+
// Expected the test device was added and removed
3677+
MTRPerControllerStorageTestsDeallocDelegate * controllerDelegate = [[MTRPerControllerStorageTestsDeallocDelegate alloc] init];
3678+
__block NSUInteger lastDeviceCount = controller.devices.count;
3679+
controllerDelegate.onDevicesChanged = ^{
3680+
// Use self as lock for lastDeviceCount access, so sanitizer doesn't complain
3681+
@synchronized(self) {
3682+
NSArray<MTRDevice *> * devices = controller.devices;
3683+
if (devices.count > lastDeviceCount) {
3684+
[controllerAddedDevice fulfill];
3685+
} else if (devices.count < lastDeviceCount) {
3686+
[controllerRemovedDevice fulfill];
3687+
}
3688+
lastDeviceCount = devices.count;
3689+
}
3690+
};
3691+
[controller addDeviceControllerDelegate:controllerDelegate queue:queue];
3692+
36583693
__auto_type * device = [MTRDevice deviceWithNodeID:deviceID controller:controller];
36593694
__auto_type * delegate = [[MTRDeviceTestDelegate alloc] init];
36603695

@@ -3675,13 +3710,16 @@ - (void)testMTRDeviceDealloc
36753710
[device setDelegate:delegate queue:queue];
36763711

36773712
[self waitForExpectations:@[ subscriptionReportBegin ] timeout:60];
3713+
3714+
XCTAssertEqual(controller.devices.count, 1);
36783715
}
36793716

36803717
// report should still be ongoing
36813718
XCTAssertFalse(subscriptionReportEnd1);
3719+
XCTAssertEqual(controller.devices.count, 0);
36823720

36833721
// dealloc -> delete should be called soon after the autoreleasepool reaps
3684-
[self waitForExpectations:@[ subscriptionCallbackDeleted ] timeout:60];
3722+
[self waitForExpectations:@[ subscriptionCallbackDeleted, controllerAddedDevice, controllerRemovedDevice ] timeout:60];
36853723

36863724
// Reset our commissionee.
36873725
__auto_type * baseDevice = [MTRBaseDevice deviceWithNodeID:deviceID controller:controller];

0 commit comments

Comments
 (0)