There is a BLEManager class, that is responsible for scanning, connecting, and receiving data from Bluetooth Low Energy (BLE) devices. Looks like that:
class BLEManager: ObservableObject, OtherProtocols {
private var myCentral: CBCentralManager!
#Published var data = 0.0
override init() {
super.init()
myCentral = CBCentralManager(delegate: self, queue: nil)
myCentral.delegate = self
}
// ...some functions that scan, establish connection, etc.
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
// here I receive raw value, handle it and get a data to work with
data = 10.0 // imagine it's the data received from BLE device
}
}
Right now the "data to work with" is stored inside this class. I'd like to move this data in such a way, so the current class (BLEManager) is responsible for the BLE logic only, and data is stored together with other user data.
Is it possible in Swift?
P.s. I'm pretty new to Swift. Have experience in JS.
EDITED
In the current case, BLEmanager receives data from one specific peripheral. The data represents a human weight, to be clear. Other than that, there is a struct with human biometric data (age, height, gender). At the end of the day, biometric data + data from the device (weight) are closely related and are used in the same calculations.
RESULT
I was able to implement the Cristik’s approach. The only difference that in my case the subsription happens in a View’s .onAppear() modifier and not on class init, as he described. Had troubles with passing a publisher to the class.
I'd like to move this data in such a way, so the current class (BLEManager) is responsible for the BLE logic only, and data is stored together with other user data
This is a good mindset, as currently your BLEManager breaks the Single Responsibility Principle, i.e. has multiple responsibilities. The ObservedObject part is a SwiftUI specific thing, so it makes sense to be extracted out of that class.
Now, implementation-wise, one first step that you could make, is to transform the data property to a publisher. This will allow clients to connect to the data stream, and allows you to circulate the publisher instead of the BLEManager class, in the rest of your app.
import Combine
class BLEManager: OtherProtocols {
// you can use `Error` instead of `Never` if you also want to
// report errors which make the stream come to an end
var dataPublisher: AnyPublisher<Int, Never> { _dataPublisher.eraseToAnyPublisher() }
private var myCentral: CBCentralManager!
// hiding/encapsulating the true nature of the publisher
private var _dataPublisher = PassthroughSubject<Int, Never>()
// ...
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
_dataPublisher.send(10.0) // imagine it's the data received from BLE device
}
This way anyone who's interested in receiving BLE data simply subscribes to that publisher.
Now, on the receiving side, assuming that you also need an ObservableObject for your SwiftUI view, you can write something along of the following:
class ViewModel: ObservableObject {
#Published var data: Int = 0
init(dataPublisher: AnyPublisher<Int, Never>) {
// automatically updates `data` when new values arrive
dataPublisher.assign(to: &$data)
}
}
If you don't use SwiftUI (I assumed you do, due to the ObservableObject conformance), then you can sink to the same publisher in order to receive the data.
Either SwiftUI, or UIKit, once you have a BLEManager instantiated somewhere, you can hide it from the rest of the app, and still provide the means to subscribe to the BLE data, by circulating the publisher. This also helps with the separation of concerns in the rest of the app.
If you plan to connect to a single device, I would keep this very simple, and let BLEManager pass this data to a delegate as it comes in. The delegate would then be responsible for deciding what to do with the data. Swift by Sundell has a nice introduction to delegates if you're not familiar with the pattern.
You'd make a separate object, a "DataManager" or whatever you'd like to call it, and it would be the delegate to the BLEManager (and would own the BLEManager). Whenever new data comes in, call methods like bleManager(_:didReceiveWeight:) or bleManager(_:didReceiveBiometricData:).
This will split up the process of getting data from the device from managing that data and performing computations, which I think is a worthy goal.
If this is a "real" project, and you're just starting, I highly recommend this pattern. It's well understood, there are dozens of blog posts about it going back many years. It's easy to understand and implement. The only pattern that's even easier to implement is to post Notifications, from BLEManager.
On the other hand, if this is more of an exploration, and you want to jump into the deep-end and the future of Swift (and limit yourself to iOS 15), you could also look at AsyncStream to emit new values as they occur into an AsyncSequence. That eventually will likely be the "Swifty" way to do this. But it's a much steeper learning curve, and the tools are not all fully developed yet, and no one really knows how to use it yet (not even Apple; it's just too new). If getting in on the ground floor of technology excites you, it's an area that everyone is exploring right now.
But if you just want this thing to work, or you want to focus on learning Swift right now rather than learning "the future of Swift," I'd use a delegate.
Related
I was watching a lecture about MVVM in Swift and how it works, and I basically understood that the Model will contain data and logic, ViewModel passes that data to the View, maybe cleans it up a bit and the View can also call intent functions in the ViewModel to notify the Model of some things that need to be modified.
Now I know I don't really have much context but there are a bunch of lectures here and there's no way for me to really explain everything for now but basically we're making a memory card game and now we are now changing it to have a MVVM design pattern(It didn't have one before). The model currently contains a Card struct and a choose function to choose a card and stuff like that, but for some reason the lecturer puts an array of emojis(The content of the cards in this game) in the ViewModel and not the Model.
I thought that the Model should be the one that stores the data and not the ViewModel? Could anyone maybe try to explain why this was done?
ViewModel:
import SwiftUI
class EmojiMemoryGame //this is the ViewModel
{
static let emojis = ["floaf","taco","george","chicken","squeaky","cat","dollar","a","b","c","d","e","f","g","h"] // these are supposed to be the emojis I just used some words instead.
static func createMemoryGame() -> MemoryGame<String>
{
MemoryGame<String>(numbersOfPairsOfCards: 4) { pairIndex in emojis[pairIndex]}
}
private var model : MemoryGame<String> = createMemoryGame()
var cards: Array<MemoryGame<String>.Card>
{
model.cards
}
}
Model:
import Foundation
struct MemoryGame<CardContent> //MemoryGame is the model for the MVVM pattern
{
private(set) var cards : Array<Card>
func choose(_ card: Card)
{
}
init(numbersOfPairsOfCards: Int, createCardContent: (Int) -> CardContent)
{
cards = Array<Card>()
//add numbersOfPairsOfCards*2 to cards array.
for pairIndex in 0..<numbersOfPairsOfCards
{
let content = createCardContent(pairIndex)
cards.append(Card(content: content))
cards.append(Card(content: content))
}
}
struct Card
{
var isFaceUp: Bool = false
var isMatched: Bool = false
var content: CardContent
}
}
It's entirely subjective, MVVM is just a design pattern and does not need to be rigorously adhered to.
Note that in MVVM a given view is typically "backed" by a single ViewModel and that a VM might interact with many different models. If the data is to be shared across views, it probably belongs on some shared model. If the data just used on a single view, it's likely fine on the VM.
I knew and understood which lecture you are referring too.
Model is for storing the data, but VM is the middleman who can uses the Model to compute data or update the View without giving the View a direct access to the Model. ViewModel also updates the Model whenever it receives any specific interaction from the View or User. You do not process or compute any data inside a Model to update the View. You must use the ViewModel.
Also, in this case the "emojis" array is not really the main model. It is just an array of String not "Card". emojis is used to store the data for initializing the "Model". Think of it like an input from the user, you need this to pass to your Model, otherwise you don't need a Model if you don't have any data to store or initialize.
The main model is still "Card", and "Card" is still completely abstract in this context.
ViewModel's job is to compute the data like a middleman and then either passing it to the Model or Update the View. Also, it is fully possible for a ViewModel to have its own implemented variables for helping the process between VM and Model or VM and View.
All in all, Model does not do any process, compute, direct interact with the View. Model only stores the data. ViewModel does all the compute, process, initialize.
ViewModel is a common incorrect interpretation of the underlying concept, stemming from the lack of spaces that we have in programming languages obfuscating intent. What you’re actually working with is a View Model, expressible in Swift via View.Model or Model.View…
struct CardView: View {
struct Model: DynamicProperty {
extension MemoryGame.Card {
struct View: SwiftUI.View
…or, when it’s a model for several views instead of one, that’s called a Store:
final class Store: ObservableObject {
Even though MVVM is a cross-platform term that Apple has largely rejected, every view has a model, regardless of if you abstract it into a separate type. The view model is anything the view needs to draw itself. Anything outside of that is a model for something else and doesn’t belong in the type.
I'm creating a simple NewsApp. I want to create the best app architecture I can made. So my question is that if I want save really simple data like username and maybe 5-6 tags as strings, should I put userDefaults logic into my viewModel or should I create a layer between ViewModel and UserDefaultsAPI which will take care about saving data?
I mean I will create StoreData protocol which UserDefaultsAPI will implement. And if I should do it how I can achieve that? I am using RxSwift and I don't now how to subscribe changing data in UserDefaults by UserDefaultsAPI.
You should create a layer between, but given an Rx/functional architecture, it shouldn't be something heavy weight like a protocol.
Learn How to Control the World and do something like this instead:
struct Storage {
static var shared = Storage()
var saveProperty: (Property) -> Void = { property in
UserDefaults.standard.set(try? JSONEncoder().encode(property), forKey: "property")
}
var deleteProperty: () -> Void = {
UserDefaults.standard.removeObject(forKey: "property")
}
var currentProperty: () -> Observable<Property?> = {
UserDefaults.standard.rx.observe(Data.self, "property")
.map { $0.flatMap { try? JSONDecoder().decode(Property.self, from: $0) } }
.distinctUntilChanged()
}
}
struct Property: Codable, Equatable { }
It depends what your doing creating a seperate layer gives you, the opportunity to have different sources for data that implement the same protocol could be useful, and your data may be complex types than need to be encoded and decoded, so it makes sense to encapsulate that out, you may also want to provide some limit range to some of your values, but UserDefaults is just a service like NotificationCenter, you are not going to automatically wrap NotificationCenter in ever class, just do what is simplest, but doesn't run the risk of painting yourself in a corner later on. You are not going to get every issue decided at the get go right, the skill is to make sure you can quickly change if the need arises, and knowing about what possibility's you will need to take advantage of in the future and how can you avoid needing to make complex changes in the moment that don't add squat. There are lots of things you need to do, and being able to prioritise them is an important part of what you do, don't try make cleaver designs, be cleaver in making designs that can be easy for other to understand and modify.
Could you help me with a Combine issue?
I'd like to make a Publisher which sends multiple values during its lifetime. In more detail, I want to wrap a method with a completion handler into a Publisher, and the completion handler is supposed to be called multiple times. For example, it's a method used for receiving messages via WebSocket like this:
webSocketClient.receiveMessage { message, error in
// this closure is called multiple times, everytime a message comes
...
}
How can I wrap this into a Publisher? I want something like AnyPublisher<String, Error> in the end.
Wrapping these is super easy when I use other FRP libraries. For example, in ReactiveSwift, it can be achieved by using SignalProducer.init(_ startHandler:). In RxSwift, it's Observable.create method.
In Combine, I found Future can be used when there's only one value to emit, but it doesn't suit my current case. I couldn't find any initializer for Publisher for this use case.
Also, I found Effect.run in TCA (The Composable Architecture) which can be used for this case. It seems custom publishers are used in its implementation, which seems a bit complicated for simple usage, but is this the only way? Are there any other easy ways to achieve the similar behavior?
I feel this is a quite common scenario, so I'd like to know how Combine users are handling this case in practice.
Thank you in advance!
Basically there are two options.
In a SwiftUI environment make the class which contains the code conform to ObservableObject and add a #Published property
class ViewModel : ObservableObject {
#Published var message = ""
func loadData() {
webSocketClient.receiveMessage { message, error in
self.message = message
...
}
}
}
In the view create a #StateObject of the view model to be notified about new messages.
Or declare a subject to send values
class ViewModel {
let subject = PassthroughSubject<String,Never>()
func loadData() {
webSocketClient.receiveMessage { message, error in
self.subject.send(message)
...
}
}
}
To receive the notifications get an instance of the class, call sink on the subject and store the result into a strong reference.
I'm trying to create some code in Swift that will enable the following functionality. There is a single class (call it the Datastore) that is the main interface into the code - this is what most users of the API will deal with most of the time.
This Datastore class is flexible. You could use it with data from a text file, or from Bluetooth data, or from data coming in over WiFi, etc. I call these providers of data. So, for example, you could have a BTProvider class which will take bluetooth data and send it to the Datastore when it arrives. One Datastore will never need to get data from multiple providers of data.
What design patterns or language tools help me to achieve this?
I thought about using protocols, but it feels backwards - a protocol defines which methods an object can respond to - but in this case, that will be the Datastore object - of which there is only one. In my head I feel like I want a reverse-protocol - something where I can guarantee "this object /calls/ these methods on another object". Then all the providers could implement this, and the Datastore could have a method "setProvider: ProviderOfData" where provider of data is the name of the reverse-protocol.
This would be a lot easier if I could poll the providers from the Datastore (then they could implement a protocol that defines methods like 'getMostRecentData', but due to the nature of it (asynchronous data receiving from WiFi, Bluetooth, etc.) this isn't possible and doesn't feel elegant - though if you have ideas I'm open to them!
This doesn't seem like this is the first time this would've been done, so I'm interested in how it's commonly done so I don't have to reinvent the wheel.
something where I can guarantee "this object /calls/ these methods on another object".
Seems like what you need is the Delegate-Pattern
You can have a DataStore (Swift is camel case) and this class can implement several delegate protocols. Example:
class DataStore {
// logic of the DataStore
}
You said that your app is mostly one class (the DataStore), so I am guessing you someone initialise your providers from it. I would suggest:
// Provider Factory
extension DataStore {
func makeBluetoothProvider() {
let btProvider = BTProvider()
btProvider.delegate = self
}
// creation of other providers, or you can create them all at once.
}
Not the important part, the DataStore is the delegate of your providers, that way, when they retrieve data, they can call the DataStore. I would have a protocol like this:
protocol ProviderDelegate: class {
func provider(_ provider: Provider, didFinishReceiving data: Data)
}
extension DataStore: ProviderDelegate {
func provider(_ provider: Provider, didFinishReceiving data: Data) {
// read data and do something with it...display it, save it, etc.
}
}
Provider would be a general class for all the providers, probably with network requests or similar basic data. One example would be:
class Provider {
var delegate: ProviderDelegate
var data: Data
}
class BTProvider: Provider {
// logic only for bluetooth provider
}
Depending on how differently your providers behave, you can have a delegate protocol for each and an extension of DataStore implementing each of this protocols. That only if the behaviours are too different from each other, which I don't think.
UPDATE ADDRESSING COMMENT: Protocol can provide code
A protocol can provide code, let me show you an example:
protocol Provider {
weak var delegate: ProviderDelegate { get set }
func fetchData(with url: URL)
func processData(data: Data)
}
extension Provider {
func processData(data: Data) {
// do some processing that all providers have to do equally
// probably also call delegate to tell DataStore it is ready
}
}
Your provider class would implement the method and can choose to implement a new processData or just use the default one. If it implements it, there is no need to call for override, you would just no longer have access to the protocol method. You provider can look like this:
class BTProvider: Provider {
weak var delegate: Provider?
func fetchData(with url: URL) {
// do some logic to fetch data for this provider
processData(data: whateverWasFetched)
}
}
Implementations of rx provide BehaviorSubject<T> and Variable<T> as mechanisms for modeling properties that change over time (a useful replacement for C# INotifyPropertyChanged).
Generally these are exposed as Observable<T> but it would be more useful to expose properties as something like:
class ObservableValue<T> : Observable<T>{
var currentValue:T { get }
}
This can be created along these lines in swift:
class ObservableValue<Element> : ObservableType {
typealias E = Element
private let subject:BehaviorSubject<E>
var currentValue:E {
get {
return try! subject.value()
}
}
init(subject:BehaviorSubject<E>) {
self.subject = subject
}
func subscribe<O: ObserverType where O.E == E>(observer: O) -> Disposable {
return self.subject.subscribe(observer)
}
}
Does this already exist? and if not is it because it's against the aims of Rx?
The only way around it is to expose a separate currentValue or write consumers that assume the concrete implementation behind the exposed Observable is a BehaviourSubject or somewhere in the chain a replay() has occured e.g. the following snippet doesn't make it explicit that as soon as I subscribe I will get a value:
class MyViewModel {
// 'I will notify you of changes perhaps including my current value'
myProperty:Observable<String>
}
so code has to be written as if its 'asynchronous' with an underlying assumption it will act in an almost synchronous manner rather than:
class MyViewModel {
// 'I have a current value and will notify you of changes going forward'
myProperty:ObservableValue<String>
}
Having thought it over and discussed it a bit more presumably the reason it doesn't (and perhaps shouldn't exist) is that it's an introduction of imperatively accessed state.
Other mechanisms of maintaining state (such as scan) do so within the confines of chained observables rather than as 'dead-end' direct calls such as 'give me the value right now'.
Perhaps it would have it's place in a hybrid reactive/imperative approach but it may just hinder full embracement of the reactive style.
It's analogous to using promises or tasks in half of the code then reverting to synchronous blocking code in other parts.
In most cases what people do is create a standard view model that exposes properties via INotifyPropertyChanged. This allows UI elements to bind to them and receive property change events and keep the UI in sync.
Then if you want an IObservable for said property you take advantage of standard Rx operators that turn events into IObservable. You can google this to find lots of different implementations. You would generally create and consume these observables from something that is observing the view model rather than expose them on the view model directly.