Just discovered that...
We can not have a nil NSDate field in CloudKit, when I save a record it is set to a default date 2001,01,01 but it is not nil.
I do not want to add a boolean column or a "placeholder" value to know if the date is nil or not + always think about it otherwise I will have incorrect data shown to the user.
Is there something we can do ? Does someone has an answer?
I miss something I think as I can not find many resources on this BIG disadvantage to use CloudKit!
Post
Replies
Boosts
Views
Activity
Be careful with large data sets (memory). In this example you can see why I use Active Record, I can use Movie.movies factory method in many situations.
Yes with static data, Active Record can be nice. With large dataset, we needs to be careful on the "Single Source of Truth" (memory, data sync, .)
Also from my experience ObservableObject to ObservableObject communication / observation is not good
Agree, I also avoid nested observables. Often it can be resolved with smaller views+logic / container view.
Apple use ECS for GameplayKit. It’s great for Games but don’t see it in Apps today (
Yes we used it for a game (not GameKit). And on a second project it was for everything (Gameplay + UI + Services).
When you say View = f(State), it is the same thing with ECS. We have something close with the existing SwiftUI but maybe more later with an official framework or else.
With the sheet modifier it is what we had a lot for UI: set a flag, the view controller listen and show the popup.
Global context, Gameplay context, UI context, services context, you can split as needed by the app.
Global -> static data, configuration etc
Gameplay -> player position, events, game state
UI -> UI state, rewards available, current tab, intent to show with priority
services -> initialize and handle the logic for services, analytics, socials, persistence, sync etc.
Of course, the ECS performance gain is overkill for a UI based app but the data logic and system execution (≠ OOP) is extremely easy to use, maintainable, and evolvable.
Subject for another thread!
Thank you for your detailed posts / examples.
It takes time to think differently than traditionnal OOP.
Same than you, I worked on multi layer projects and your "Massive service object strategy" is an exact copy of what we had !
other horrible things including useless unit/integration tests.
6-years ago I started a Unity project with ECS structure instead of OOP and it was really fast to do anything.
You want to change/ add a SDK => add a new system
An analytics event ? => create a new data and any systems can listen and react to this event.
Think SwiftUI with
all the app data in a global context
ObservableObjects + View are just systems / puzzle pieces on top of that and react only when their own necessary data updates (or is added/removed)
I would love that and that's why I like SwiftUI since day one as it is close to this .
Thank you for your examples.
case .home:
MovieRow(store: MovieStore(.featured))
MovieRow(store: MovieStore(.currentlyViewing))
Here, if a Movie is editable (for the example), Movie.A is edited in the first MovieRow/Grid -> Editor.
And, this same movie is in the second row (currentlyViewing), it won't be refreshed.
A Store with a computed var to filter movies can fix this problem here (or even a binding of a movie subset on the global list).
it's more an app problem and how to manage the underlying data (local chache, persistence etc)^^'
In your
struct Channel: Identifiable, Hashable, Codable {
func addToFavorites() async throws { ... }
}
I guess you have the same type of code with the service object?
struct Channel: Identifiable, Hashable, Codable {
func addToFavorites() async throws {
self.isFavorite = true
// or
try await MyTVWS.shared.favorite(movieId: self.id)
}
}
And if a Store needs Data from 2 types to do his work, you'll do something like this ?
class SearchStore {
@Published var movies: [Movie] = []
@Publisehd var channels: [Channel] = []
func search() async {
isSearching = true
do {
async let movieResults = Movie.search(option: MovieSearchOptions)
async let channelResults = Channel.search(option: ChannelSearchOptions)
let results = try await [movieResults, channelResults]
// or could be different to display results as soon as possible for a type
} catch {
searchingError = error
}
isSearching = false
}
}
ActiveRecord looks nice but how would change a service implementation (not necessary the environment at init time) as it is tightly coupled in the model.
Did you have the problem and a recommended approach? Or in many cases the question to change wasn't even here (to not over engineering before it is needed)?
MyTVWebService.shared.favorite(movieId: self.id)
with
MyTVLocalService.shared.favorite(movieId: self.id)
to
class MovieStore: ObservableObject {
// online
init(with service: MyTVWS = .remote) {}
// -- or --
// local
init(with service: MyTVWS = .local) {}
}
Anyway, I'll keep reading your posts, some interesting thought and approach :)
I was - and still - never a fan of VIPER, TCA, MVVM for everything even if not needed, and ActiveRecord is another approach that I like (+ reading Apple MusicKit / StoreKit2 confirms it - Too bad we can not see what's behind their static method.)
If the user wants to favorite channels and VOD movies, how would you handle that?
1- A list of [Channel] and [Movie] in your AccountStore (user state/data) ?
2- Or if we want to keep only the ID (eg. [Channel.ID] + [Movie.ID]), we need to map these ids to their object after app launch.
Is it the Account that access the data? or do you have a nested dependency to access the data only via ChannelStore and VODStore (requiring these stores load the data before to not have empty collection)
ChannelStore.load
VideoOnDemand.load
Account.load + map Channel/VOD.ID.
3- Can different store access the same underlying data via your Active record object? or if a View needs data from 2 different store to map them to a new object type.
4- Also curious about an active record example. what is inside these "..." in the static methods or functions.
Movie.all { ??? }
do you always call Webservice.shared.allMovies? are you using a cache? return Cacheservice.shared.allMovie ?? Webservice.shared.allmovies
thank you!