From 77fd9aa29410b57821a54f870f9ff821d5f753f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zielin=CC=81ski?= Date: Thu, 7 Apr 2022 17:32:28 +0200 Subject: [PATCH 1/3] Add handler for peripheral connection cancelled - add public `peripheralConnectionCancelledHandler(_:)` settable property - refactor from `.filter(_:).first` to `first(where:)` for optimisation - bump version to 1.0.6 - add project change log --- BlueSwift.podspec | 2 +- Bluetooth/ConnectionViewController.swift | 2 +- CHANGELOG.md | 12 +++++ Configurations/Common/Common-Base.xcconfig | 2 +- .../Connection/BluetoothConnection.swift | 18 +++++-- .../Connection/ConnectionService.swift | 53 ++++++++++++------- Readme.md | 4 +- docs/bluetoothConnection.md | 7 +++ 8 files changed, 70 insertions(+), 30 deletions(-) create mode 100644 CHANGELOG.md diff --git a/BlueSwift.podspec b/BlueSwift.podspec index 8e33b28..7c19f12 100644 --- a/BlueSwift.podspec +++ b/BlueSwift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |spec| spec.name = 'BlueSwift' - spec.version = '1.0.5' + spec.version = '1.0.6' spec.summary = 'Easy and lightweight CoreBluetooth wrapper written in Swift' spec.homepage = 'https://github.com/netguru/BlueSwift' diff --git a/Bluetooth/ConnectionViewController.swift b/Bluetooth/ConnectionViewController.swift index 6331ab9..3d3955e 100644 --- a/Bluetooth/ConnectionViewController.swift +++ b/Bluetooth/ConnectionViewController.swift @@ -7,7 +7,7 @@ import UIKit import BlueSwift class ConnectionViewController: UIViewController { - + @IBOutlet weak var notifyLabel: UILabel! @IBOutlet weak var readLabel: UILabel! @IBOutlet weak var textField: UITextField! diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..daea463 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Change Log +All notable changes to this project will be documented in this file. + +## [1.0.6] - 2022-04-08 + +### Added + +- added public `peripheralConnectionCancelledHandler(_:)` setable property to `BluetoothConnection` class. It is called when disconnecting a peripheral using `disconnect(_:)` is completed + +### Changed + +- refactored `.filter(_:).first` to `first(where:)` for optimisation diff --git a/Configurations/Common/Common-Base.xcconfig b/Configurations/Common/Common-Base.xcconfig index 8c90514..2a1040d 100644 --- a/Configurations/Common/Common-Base.xcconfig +++ b/Configurations/Common/Common-Base.xcconfig @@ -5,7 +5,7 @@ #include "../xcconfigs/Common/Common.xcconfig" -_BUILD_VERSION = 1.0.0 +_BUILD_VERSION = 1.0.6 _BUILD_NUMBER = 1 _DEPLOYMENT_TARGET_IOS = 11.0 diff --git a/Framework/Source Files/Connection/BluetoothConnection.swift b/Framework/Source Files/Connection/BluetoothConnection.swift index 6629927..ebbdd7e 100644 --- a/Framework/Source Files/Connection/BluetoothConnection.swift +++ b/Framework/Source Files/Connection/BluetoothConnection.swift @@ -8,13 +8,13 @@ import CoreBluetooth /// Public facing interface granting methods to connect and disconnect devices. public final class BluetoothConnection: NSObject { - + /// A singleton instance. public static let shared = BluetoothConnection() - + /// Connection service implementing native CoreBluetooth stack. private var connectionService = ConnectionService() - + /// A advertisement validation handler. Will be called upon every peripheral discovery. Return value from this closure /// will indicate if manager should or shouldn't start connection with the passed peripheral according to it's identifier /// and advertising packet. @@ -34,7 +34,15 @@ public final class BluetoothConnection: NSObject { connectionService.peripheralValidationHandler = peripheralValidationHandler } } - + + /// A peripheral connection cancelled handler. Called when disconnecting a peripheral using `disconnect(_:)` is completed. + /// Contains matched peripheral and native peripheral from CoreBluetooth. + public var peripheralConnectionCancelledHandler: ((Peripheral, CBPeripheral) -> Void)? { + didSet { + connectionService.peripheralConnectionCancelledHandler = peripheralConnectionCancelledHandler + } + } + /// Primary method used to connect to a device. Can be called multiple times to connect more than on device at the same time. /// /// - Parameters: @@ -56,7 +64,7 @@ public final class BluetoothConnection: NSObject { handler?(error) } } - + /// Primary method to disconnect a device. If it's not yet connected it'll be removed from connection queue, and connection attempts will stop. /// /// - Parameter peripheral: a peripheral you wish to disconnect. Should be exactly the same instance that was used for connection. diff --git a/Framework/Source Files/Connection/ConnectionService.swift b/Framework/Source Files/Connection/ConnectionService.swift index 305260f..25db3da 100644 --- a/Framework/Source Files/Connection/ConnectionService.swift +++ b/Framework/Source Files/Connection/ConnectionService.swift @@ -17,7 +17,10 @@ internal final class ConnectionService: NSObject { /// Closure used to manage connection success or failure. internal var connectionHandler: ((Peripheral, ConnectionError?) -> ())? - + + /// Closure called when disconnecting a peripheral using `disconnect(_:)` is completed. + internal var peripheralConnectionCancelledHandler: ((Peripheral, CBPeripheral) -> ())? + /// Returns the amount of devices already connected. internal var connectedDevicesAmount: Int { return peripherals.filter { $0.isConnected }.count @@ -33,7 +36,10 @@ internal final class ConnectionService: NSObject { /// Set of peripherals the manager should connect. private var peripherals = [Peripheral]() - + + /// Handle to peripherals which were requested to disconnect. + private var peripheralsToDisconnect = [Peripheral]() + private weak var connectingPeripheral: Peripheral? /// Connection options - means you will be notified on connection and disconnection of devices. @@ -76,7 +82,7 @@ extension ConnectionService { /// Disconnects given device. internal func disconnect(_ peripheral: CBPeripheral) { if let index = peripherals.firstIndex(where: { $0.peripheral === peripheral }) { - peripherals.remove(at: index) + peripheralsToDisconnect.append(peripherals.remove(at: index)) } centralManager.cancelPeripheralConnection(peripheral) } @@ -152,9 +158,9 @@ extension ConnectionService: CBCentralManagerDelegate { let devices = peripherals.filter({ $0.configuration.matches(advertisement: advertisementData)}) guard let handler = peripheralValidationHandler, - let matchingPeripheral = devices.filter({ $0.peripheral == nil }).first, - handler(matchingPeripheral, peripheral, advertisementData, RSSI), - connectingPeripheral == nil + let matchingPeripheral = devices.first(where: { $0.peripheral == nil }), + handler(matchingPeripheral, peripheral, advertisementData, RSSI), + connectingPeripheral == nil else { return } @@ -170,7 +176,7 @@ extension ConnectionService: CBCentralManagerDelegate { /// Called upon a successfull peripheral connection. /// - SeeAlso: CBCentralManagerDelegate public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { - guard let connectingPeripheral = peripherals.filter({ $0.peripheral === peripheral }).first else { return } + guard let connectingPeripheral = peripherals.first(where: { $0.peripheral === peripheral }) else { return } self.connectingPeripheral = connectingPeripheral connectingPeripheral.peripheral = peripheral peripheral.delegate = self @@ -185,7 +191,7 @@ extension ConnectionService: CBCentralManagerDelegate { } extension ConnectionService: CBPeripheralDelegate { - + /// Called upon discovery of services of a connected peripheral. Used to map model services to passed configuration and /// discover characteristics for each matching service. /// - SeeAlso: CBPeripheralDelegate @@ -200,13 +206,13 @@ extension ConnectionService: CBPeripheralDelegate { peripheral.discoverCharacteristics(service.characteristics.map({ $0.bluetoothUUID }), for: cbService) }) } - + /// Called upon discovery of characteristics of a connected peripheral per each passed service. Used to map CBCharacteristic /// instances to passed configuration, assign characteristic raw values and setup notifications. /// - SeeAlso: CBPeripheralDelegate public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { guard let characteristics = service.characteristics, error == nil else { return } - let matchingService = connectingPeripheral?.configuration.services.filter({ $0.bluetoothUUID == service.uuid }).first + let matchingService = connectingPeripheral?.configuration.services.first(where: { $0.bluetoothUUID == service.uuid }) let matchingCharacteristics = matchingService?.characteristics.matchingElementsWith(characteristics) guard matchingCharacteristics?.count != 0 else { centralManager.cancelPeripheralConnection(peripheral) @@ -223,19 +229,26 @@ extension ConnectionService: CBPeripheralDelegate { } connectingPeripheral = nil } - - /// Called when device is disconnected, inside this method a device is reconnected. Connect method does not have a timeout - /// so connection will be triggered anytime in the future when the device is discovered. In case the connection is no - /// longer needed we'll just return. + + /// Called when device is disconnected. + /// If connection was cancelled using `disconnect(_:)`, then `peripheralConnectionCancelledHandler(_:)` is called. + /// Otherwise device is reconnected. Connect method does not have a timeout, so connection will be triggered + /// anytime in the future when the device is discovered. In case the connection is no longer needed we'll just return. /// - SeeAlso: CBPeripheralDelegate public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { - guard - let disconnectedPeripheral = peripherals.filter({ $0.peripheral === peripheral }).first, - let nativePeripheral = disconnectedPeripheral.peripheral - else { + /// `error` is nil if disconnect resulted from a call to `cancelPeripheralConnection(_:)`. + /// SeeAlso: https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518791-centralmanager + if error == nil, + let disconnectedPeripheral = peripheralsToDisconnect.first(where: { $0.peripheral === peripheral }) { + peripheralsToDisconnect.removeAll(where: { $0 === disconnectedPeripheral }) + peripheralConnectionCancelledHandler?(disconnectedPeripheral, peripheral) return } - disconnectedPeripheral.disconnectionHandler?() - centralManager.connect(nativePeripheral, options: connectionOptions) + + if let disconnectedPeripheral = peripherals.first(where: { $0.peripheral === peripheral }), + let nativePeripheral = disconnectedPeripheral.peripheral { + disconnectedPeripheral.disconnectionHandler?() + centralManager.connect(nativePeripheral, options: connectionOptions) + } } } diff --git a/Readme.md b/Readme.md index 4fc06fc..6fc5475 100644 --- a/Readme.md +++ b/Readme.md @@ -95,7 +95,7 @@ Just drop the line below to your Podfile: `pod 'BlueSwift'` -(but probably you'd like to pin it to the nearest major release, so `pod 'BlueSwift' , '~> 1.0.0'`) +(but probably you'd like to pin it to the nearest major release, so `pod 'BlueSwift' , '~> 1.0.6'`) ### ![](https://img.shields.io/badge/carthage-compatible-green.svg) @@ -103,7 +103,7 @@ The same as with Cocoapods, insert the line below to your Cartfile: `github 'netguru/BlueSwift'` -, or including version - `github 'netguru/BlueSwift' ~> 1.0.0` +, or including version - `github 'netguru/BlueSwift' ~> 1.0.6` ## 📄 License diff --git a/docs/bluetoothConnection.md b/docs/bluetoothConnection.md index 252c1ae..b4899d3 100644 --- a/docs/bluetoothConnection.md +++ b/docs/bluetoothConnection.md @@ -21,6 +21,13 @@ An optional closure, nil by default. If set you can setup custom filtering of sc Advertising is passed in `[String: Any]` dictionary. This data is sent along with a string representing a CoreBluetooth identifier of the device(unique and not changing per each device) and corresponding peripheral instance. If false is returned from this method, no attempt to connect a given peripheral will we attempted. +```swift +var peripheralConnectionCancelledHandler: ((Peripheral, CBPeripheral) -> Void)? +``` + +An optional closure, nil by default - peripheral connection cancelled handler. Called when disconnecting a peripheral using `disconnect(_:)` is completed. +Contains matched peripheral and native peripheral from CoreBluetooth. + ```swift func connect(_ peripheral: Peripheral, handler: ((ConnectionError?) -> ())?) ``` From 0b980a2acdb3cfcbfd82cac06fa380b09cc02b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zielin=CC=81ski?= Date: Fri, 8 Apr 2022 23:39:31 +0200 Subject: [PATCH 2/3] - add convenience method for finding an element of `Peripheral` sequence with `peripheral` property identical to given `CBPeripheral` instance --- Bluetooth.xcodeproj/project.pbxproj | 8 ++ .../Connection/ConnectionService.swift | 9 ++- .../Source Files/Extensions/Sequence.swift | 41 ++++++++++ Unit Tests/SequenceExtensionTests.swift | 78 +++++++++++++++++++ 4 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 Framework/Source Files/Extensions/Sequence.swift create mode 100644 Unit Tests/SequenceExtensionTests.swift diff --git a/Bluetooth.xcodeproj/project.pbxproj b/Bluetooth.xcodeproj/project.pbxproj index 0f2dab1..d9a40d9 100644 --- a/Bluetooth.xcodeproj/project.pbxproj +++ b/Bluetooth.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 38FE6BE7200689AB00809A06 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE6BA82006898100809A06 /* ConfigurationTests.swift */; }; 3A369634210F1C4E007C62F1 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A369633210F1C4E007C62F1 /* CoreBluetooth.framework */; }; 3A369636210F1C57007C62F1 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A369635210F1C57007C62F1 /* XCTest.framework */; }; + 4BEB6E6F2800735D0061C702 /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB6E6E2800735D0061C702 /* Sequence.swift */; }; + 4BEB6E72280077010061C702 /* SequenceExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB6E70280075510061C702 /* SequenceExtensionTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -150,6 +152,8 @@ 3A142D24210F0EEE000A6089 /* Sample-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Sample-Base.xcconfig"; sourceTree = ""; }; 3A369633210F1C4E007C62F1 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/Frameworks/CoreBluetooth.framework; sourceTree = DEVELOPER_DIR; }; 3A369635210F1C57007C62F1 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 4BEB6E6E2800735D0061C702 /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; + 4BEB6E70280075510061C702 /* SequenceExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceExtensionTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -306,6 +310,7 @@ isa = PBXGroup; children = ( 38FE6BA72006898100809A06 /* ExtensionTests.swift */, + 4BEB6E70280075510061C702 /* SequenceExtensionTests.swift */, 382CD41E2028F4B50065DFF6 /* CommandTests.swift */, 38FE6BA82006898100809A06 /* ConfigurationTests.swift */, 38FE6BA92006898100809A06 /* Info.plist */, @@ -387,6 +392,7 @@ 38FE6BBB2006898100809A06 /* CBUUID.swift */, 38FE6BBC2006898100809A06 /* String.swift */, 385B01AC2007C93000E3D478 /* Array.swift */, + 4BEB6E6E2800735D0061C702 /* Sequence.swift */, 3863BE73200D582600FD4EC9 /* Int.swift */, 3863BE75200D5B9B00FD4EC9 /* Data.swift */, 3863BE68200BDDBA00FD4EC9 /* CBCharacteristic.swift */, @@ -620,6 +626,7 @@ files = ( 38BE35DC203DD67D0018EA0D /* ConnectablePeripheral.swift in Sources */, 3863BE69200BDDBA00FD4EC9 /* CBCharacteristic.swift in Sources */, + 4BEB6E6F2800735D0061C702 /* Sequence.swift in Sources */, 385B01AD2007C93000E3D478 /* Array.swift in Sources */, 38FE6BD82006898100809A06 /* String.swift in Sources */, 382CD41C2028E5950065DFF6 /* Int.swift in Sources */, @@ -645,6 +652,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4BEB6E72280077010061C702 /* SequenceExtensionTests.swift in Sources */, 382CD41F2028F4B50065DFF6 /* CommandTests.swift in Sources */, 38FE6BE7200689AB00809A06 /* ConfigurationTests.swift in Sources */, 38FE6BE6200689A700809A06 /* ExtensionTests.swift in Sources */, diff --git a/Framework/Source Files/Connection/ConnectionService.swift b/Framework/Source Files/Connection/ConnectionService.swift index 25db3da..aaf3959 100644 --- a/Framework/Source Files/Connection/ConnectionService.swift +++ b/Framework/Source Files/Connection/ConnectionService.swift @@ -82,7 +82,8 @@ extension ConnectionService { /// Disconnects given device. internal func disconnect(_ peripheral: CBPeripheral) { if let index = peripherals.firstIndex(where: { $0.peripheral === peripheral }) { - peripheralsToDisconnect.append(peripherals.remove(at: index)) + let peripheralToDisconnect = peripherals.remove(at: index) + peripheralsToDisconnect.append(peripheralToDisconnect) } centralManager.cancelPeripheralConnection(peripheral) } @@ -176,7 +177,7 @@ extension ConnectionService: CBCentralManagerDelegate { /// Called upon a successfull peripheral connection. /// - SeeAlso: CBCentralManagerDelegate public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { - guard let connectingPeripheral = peripherals.first(where: { $0.peripheral === peripheral }) else { return } + guard let connectingPeripheral = peripherals.first(withIdentical: peripheral) else { return } self.connectingPeripheral = connectingPeripheral connectingPeripheral.peripheral = peripheral peripheral.delegate = self @@ -239,13 +240,13 @@ extension ConnectionService: CBPeripheralDelegate { /// `error` is nil if disconnect resulted from a call to `cancelPeripheralConnection(_:)`. /// SeeAlso: https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518791-centralmanager if error == nil, - let disconnectedPeripheral = peripheralsToDisconnect.first(where: { $0.peripheral === peripheral }) { + let disconnectedPeripheral = peripheralsToDisconnect.first(withIdentical: peripheral) { peripheralsToDisconnect.removeAll(where: { $0 === disconnectedPeripheral }) peripheralConnectionCancelledHandler?(disconnectedPeripheral, peripheral) return } - if let disconnectedPeripheral = peripherals.first(where: { $0.peripheral === peripheral }), + if let disconnectedPeripheral = peripherals.first(withIdentical: peripheral), let nativePeripheral = disconnectedPeripheral.peripheral { disconnectedPeripheral.disconnectionHandler?() centralManager.connect(nativePeripheral, options: connectionOptions) diff --git a/Framework/Source Files/Extensions/Sequence.swift b/Framework/Source Files/Extensions/Sequence.swift new file mode 100644 index 0000000..ef2137a --- /dev/null +++ b/Framework/Source Files/Extensions/Sequence.swift @@ -0,0 +1,41 @@ +// +// Sequence.swift +// Bluetooth +// +// Created by Filip Zieliński on 08/04/2022. +// Copyright © 2022 Netguru. All rights reserved. +// + +import Foundation +import CoreBluetooth + +internal extension Sequence { + + /// Returns the first element of the sequence where given element property is identical (`===`) to given object instance. + /// - Parameters: + /// - propertyKeyPath: a key path to an element property. + /// - instance: an object instance to compare against. + /// - Returns: The first element of the sequence where given element property is identical (`===`) to given object instance, or `nil` if there is no such element. + func first(where propertyKeyPath: KeyPath, isIdenticalTo instance: Object) -> Element? { + first { $0[keyPath: propertyKeyPath] === instance } + } + + /// Returns the first element of the sequence where given element optional property is identical (`===`) to given object instance. + /// - Parameters: + /// - propertyKeyPath: a key path to an element property. + /// - instance: an object instance to compare against. + /// - Returns: The first element of the sequence where given element optional property is identical (`===`) to given object instance, or `nil` if there is no such element. + func first(where propertyKeyPath: KeyPath, isIdenticalTo instance: Object) -> Element? { + first { $0[keyPath: propertyKeyPath] === instance } + } +} + +internal extension Sequence where Element == Peripheral { + + /// Convenience method returning the first peripheral of the sequence with `peripheral` property identical (`===`) to given `CBPeripheral` instance. + /// - Parameter cbPeripheral: a `CBPeripheral` instance. + /// - Returns: The first peripheral of the sequence with `peripheral` property identical (`===`) to given `CBPeripheral` instance, or `nil` if there is no such element. + func first(withIdentical cbPeripheral: CBPeripheral) -> Element? { + first(where: \.peripheral, isIdenticalTo: cbPeripheral) + } +} diff --git a/Unit Tests/SequenceExtensionTests.swift b/Unit Tests/SequenceExtensionTests.swift new file mode 100644 index 0000000..1a8fda1 --- /dev/null +++ b/Unit Tests/SequenceExtensionTests.swift @@ -0,0 +1,78 @@ +// +// SequenceExtensionTests.swift +// Bluetooth +// +// Created by Filip Zieliński on 08/04/2022. +// Copyright © 2022 Netguru. All rights reserved. +// + +import XCTest +import CoreBluetooth +@testable import BlueSwift + +final class SequenceExtensionTests: XCTestCase { + + private let fixtureReference = SimpleClass() + private let fixtureOptionalReference = SimpleClass() + private lazy var fixtureTestClass = TestClass(someReference: fixtureReference, someOptionalReference: fixtureOptionalReference) + + func testFirstSequenceElement_withIdenticalProperty() { + // given: + var sut: [TestClass] = [ + TestClass(someReference: .init(), someOptionalReference: nil), + TestClass(someReference: .init(), someOptionalReference: .init()), + fixtureTestClass, + TestClass(someReference: .init(), someOptionalReference: .init()), + ] + + // then: + testFindingElement_withMatchingIdenticalProperty(sut: sut) + + // given + sut = [ + fixtureTestClass, + TestClass(someReference: .init(), someOptionalReference: .init()), + TestClass(someReference: .init(), someOptionalReference: .init()), + TestClass(someReference: .init(), someOptionalReference: nil) + ] + + // then: + testFindingElement_withMatchingIdenticalProperty(sut: sut) + + // given + sut = [ + TestClass(someReference: .init(), someOptionalReference: .init()), + TestClass(someReference: .init(), someOptionalReference: nil), + TestClass(someReference: .init(), someOptionalReference: .init()), + fixtureTestClass + ] + + // then: + testFindingElement_withMatchingIdenticalProperty(sut: sut) + } +} + +private extension SequenceExtensionTests { + + func testFindingElement_withMatchingIdenticalProperty(sut: [TestClass]) { + XCTAssert(sut.first(where: \.someReference, isIdenticalTo: fixtureReference) === fixtureTestClass, "Should find object with matching identical (`===`) property") + XCTAssert(sut.first(where: \.someOptionalReference, isIdenticalTo: fixtureOptionalReference) === fixtureTestClass, "Should find object with matching identical (`===`) property") + XCTAssertNil(sut.first(where: \.someReference, isIdenticalTo: fixtureOptionalReference), "Should not find any object with matching identical (`===`) property") + XCTAssertNil(sut.first(where: \.someOptionalReference, isIdenticalTo: fixtureReference), "Should not find any object with matching identical (`===`) property") + XCTAssertNil(sut.first(where: \.someReference, isIdenticalTo: .init()), "Should not find any object with matching identical (`===`) property") + XCTAssertNil(sut.first(where: \.someOptionalReference, isIdenticalTo: .init()), "Should not find any object with matching identical (`===`) property") + } +} + +private final class TestClass: Identifiable { + + let someReference: SimpleClass + let someOptionalReference: SimpleClass? + + init(someReference: SimpleClass, someOptionalReference: SimpleClass?) { + self.someReference = someReference + self.someOptionalReference = someOptionalReference + } +} + +private final class SimpleClass {} From 2d5a818300e37b4252a99753d15c08346d07a691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zielin=CC=81ski?= Date: Sun, 10 Apr 2022 12:46:42 +0200 Subject: [PATCH 3/3] - add SPM .build folder to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a1a4b2c..0589c84 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ xcuserdata profile build +.build output DerivedData *.mode1v3