In the Home APIs for iOS, observing changes to state in the home is made
possible through the use of the
Combine framework 
in Swift. Observing changes in structures, rooms, device metadata, and
device state in the Home APIs can be done with any API using
HomeDevice. This is done
by subscribing to publishers exposing values from asynchronous events.
When any item in a collection is added, deleted, or modified, the latest snapshot of the collection is returned.
It is up to the developer to deduce the specific changes by comparing this
snapshot with an older copy. The id field provided for
each parent object type in the Home APIs
can be used for this purpose.
How to unwrap optional objects
When an object is optional, use if let or guard to safely unwrap
the object. Don't use ! because ! should never be used unless the developer
knows for certain that the object cannot be nil.
The input parameter structureID in the example is
defined as String?, which means it's a string that is optional and has
the possibility of being nil. This function finds the Structure object
based on the input in structureID.
Here's how we recommend you handle optional objects:
func structure(structureID: String?) -> Structure? {
    guard let structureID else { return nil }
    return structures.first { $0.id == structureID }
  }
How to use publishers
What follows are some basic examples of working with publishers in the Home
APIs. For the following examples, an instance of the Home
must be created.
var home: Home?
Before accessing collections, make sure to unwrap them since they're optional:
guard let home else { return nil }
Track changes to a structure
The following changes to a structure trigger this collection:
- Structure name
home.structures().batched()
  .compactMap { $0.first(where: { $0.id = myStructureID} }
  .removeDuplicates()
  .sink { structure in
    if let structure = structure {
      print("Structure \(structure.id) updated to \(structure.name)")
    }
  }
Track changes to a specific device
The following changes to a device trigger this collection:
- Connectivity state
- Device name
- Room membership
- The set of supported traits
- The set of supported types
home.devices().batched()
  .compactMap { deviceList -> HomeDevice? in
    deviceList.filter { $0.name == "Bedroom Lamp"}.first
  }
  .removeDuplicates()
  .sink { lampDevice in
    if lampDevice != nil {
      print("The bedroom lamp has changed!")
    }
  }
Track changes to a specific trait on a device
Use any trait supported by the device and the Home APIs. For a full list, see
Trait.
device.types.subscribe(OnOffLightDeviceType.self)
  .compactMap { $0.matterTraits.onOffTrait }
  .removeDuplicates()
  .sink { onOffState in
    if let onOffState = onOffState {
      print("Got new state update: \(onOffState.onOff)")
    }
  }
Track changes to a specific type on a device
The following changes to a device type trigger this collection:
- Changes to any trait within the generated device type
Use any Matter device type supported by the Home APIs. For
a full list, see
DeviceType.
device.types.subscribe(DimmableLightDeviceType.self)
  .compactMap { $0.matterTraits.levelControlTrait }
  .removeDuplicates()
  .sink { dimmableLightDevice in
    if let dimmableLightDevice = dimmableLightDevice
      print("Got new state update! \(levelControlTrait.currentLevel)")
    }
  }
Track changes to a room in a structure
The following changes to a room trigger this collection:
To track when devices are added or removed from a room, use the devices()
query.
home.rooms().batched()
  .sink { rooms in
    print("Got a new updated set of rooms!")
    for room in rooms {
      print("Got room #\(room.name)")
    }
  }
Subscribe to events
In the Home APIs, events are used to detect changes in the state of a device.
self.cancellable = self.device.types.subscribe(FanDeviceType.self)
  .receive(on: DispatchQueue.main)
  .catch { error in
    Logger.error("Error getting FanDeviceType: \(error)")
    return Empty<FanDeviceType, Never>().eraseToAnyPublisher()
  }
  .sink { [weak self] fanDeviceType in
    self?.fanDeviceType = fanDeviceType
    self?.updateTileInfo()
  }
}