In my app, I want to use KVO to update UI when an update sent by my server is retrieved by the app. For that, I have a singleton class called "AutomaticRefreshProcess", which polls the server every second, and, if there is a new update, store it in a property lastUpdate. I want this very property to be observable by any object of my app if they need (a UIView, a UIViewController but also maybe by business model NSObject for example)
I saw (at least empirically) that the keyPath when you add an observer couldn't have any of its "node" being a static variable. So I can't do, in my observers :
addObserver(self, forKeyPath:#keyPath(AutomaticRefreshProcess.lastUpdate), options: [.new], context: nil)
I tried, this make the app crash. What I can do is, for all my observers, declare a property set at init to AutomaticRefreshProcess.lastUpdate. I tried, it works.
class MyObserver : NSObject {
#objc dynamic let lastUpdate = AutomaticRefreshProcess.lastUpdate
init()
{
addObserver(self, #keyPath(lastUpdate), ...)
}
}
This works well but, I find it redundant. Plus, I want to customize observeValue raw function to override it by a convenient more specific function to handle these updates, like :
override observeUpdate(newUpdates:[Update], ofType: UpdateType) { ... }
For that, I can create a class ARPObserver, and subclassing all the observers from it. The thing is, how can I make all the UIKit object "conforming" to this parent class, at once ? I don't want to rewrite every UIKit object to make it "ARPOberser" like UIViewARPObserver, UIViewControllerARPObserver etc.
So my question is : is there a better design pattern than subclassing all UIView, UIViewController, and so on to make all UIKit object as ARP Observers ? I would have loved to use extensions but you can't store props there, and I need to store arpInstance to make it available in the keyPath of addObserver
Related
I have a NSViewController that has a class variable as follows:
class ServerAdminViewController: NSViewController, NSTextFieldDelegate, MyTableViewDelegate {
var myClass = MyClass()
func showAdmin(window: NSWindow, myClass: MyClass) {
self.myClass = myClass
window.makeKeyAndOrderFront(self)
NSApp.activate(ignoringOtherApps: true)
}
An instance of this class is instantiated in the App initialization. Later when user clicks on a menu item, I want to show a window and use that value as a singleton for the app, during the time this window is open, also being able to modify myClass. However, the line where the instance variable is declared is being executed multiple times and after the self.myClass = myClassassignment, resetting the object (this is a menubar app that opens a window).
Tried
Several things, ex. removing the parameterless constructor from myClass, declaring that variable private and with a "!", adding init methods to the ViewController (without success) and some other failed attempts.
Question
How to make myClass a singleton and share it accross the application (i.e. make it available to this window) without reinitializing it?
Soo there use to be a bunch of stuff to do this with dispatchOnce and things like that. But now the way I do this is by declaring a shared static variable on my class. This will not handle persistence but it will solve your problem creating your class many times. The shared reference with only be recreated on use after being released. Or if it has never been used before. The class variable is also handy for retrieving the class for many controllers.
class MyClass {
static let shared = MyClass()
}
class MyViewController: UIViewController {
let myClass = MyClass.shared
}
I have a Core Data model with an entity generated into class Task. I am trying to get the Combine publisher objectWillChange from the NSManagedObject to send (automatically, without manual work), but it won't. The task entity has a name attribute.
let task = Task(context: container.viewContext)
let taskSubscription = task.objectWillChange.sink(receiveValue: { _ in
print("Task changed")
})
task.name = "Foo" // WILL NOT trigger
If I call send manually, the subscription will work:
task.objectWillChange.send() // Will trigger
If I replace this with a simple ObservableObject, it will work as expected:
class DummyTask: ObservableObject {
#Published var name: String?
}
let dummy = DummyTask()
let dummySubscription = dummy.objectWillChange.sink(receiveValue: { _ in
print("Dummy changed")
})
dummy.name = "Foo" // Will trigger
dummy.objectWillChange.send() // Will trigger
Is NSManagedObject bugged? How should I observe the general entity object for changes? How should I get SwiftUI to see them?
This is using Xcode 11.0 and iOS 13.
I believe it is a bug. There is no point for NSManagedObject to conform to ObservableObject but unable to mark any property as #Published.
While we are waiting Apple to rectify this, I've come across a cleaner solution than #jesseSpencer's suggested one. The logic behind is the same, by adding a objectWillChange.send(), but adding globally into willChangeValue(forKey key: String) instead of adding into individual properties.
override public func willChangeValue(forKey key: String) {
super.willChangeValue(forKey: key)
self.objectWillChange.send()
}
Credits: https://forums.developer.apple.com/thread/121897
My guess is that it is a bug. NSManagedObject conformance to ObservableObject was added in beta 5 which also introduced other significant changes, including deprecation of BindableObject (for replacement by ObservableObject).
See the SwiftUI section:
https://developer.apple.com/documentation/ios_ipados_release_notes/ios_13_release_notes)
I ran into the same issue and despite NSManagedObject conforming to ObservableObject, it was not emitting notifications for changes. This might have something to do with NSManagedObject properties needing to be wrapped with #NSManaged which cannot be combined with #Published, while the ObservableObject doc states that, by default, an ObservableObject will synthesize objectWillChange publishers for #Published property changes. https://developer.apple.com/documentation/combine/observableobject
I first tried to get around this by bootstrapping a call to objectWillChange.send() in overrides to Key-Value methods in my NSManagedObject subclass, which only resulted in incorrect behavior.
The solution I went with is the simplest and unfortunately maybe the bulkiest if you need to change a lot of codependent properties in your SwiftUI view. But, so far it is working fine for me and maintains use of SwiftUI as intended.
In Swift:
Create an NSManagedObject subclass for your entity.
In that subclass, create setter methods for the properties you wish to change from your SwiftUI views and at the beginning of the method add a call to objectWillChange.send(), which should look something like this:
func setTitle(_ text: String) {
objectWillChange.send()
self.title = text
}
I only advise this as a temporary workaround, as it is not ideal and hopefully will be addressed soon.
I will be submitting a bug report in FeedbackAssistant and I recommend to anyone else encountering this issue to do the same, so we can get Apple to take another look at this!
Edit:
A warning about #Anthony’s answer:
While the suggested approach does work, be aware that it will not work when changing collection type relationships, i.e. adding an object to an array associated with the NSManagedObject.
To observe NSManagedObject changes, please look at: https://developer.apple.com/documentation/combine/performing-key-value-observing-with-combine.
Pay attention that, when you use that method in custom class of UICollectionViewListCell or UITableViewCell, you should override the prepareForReuse method and put there alike code:
override func prepareForReuse() {
super.prepareForReuse()
nameObserver?.cancel()
nameObserver = nil
}
I have a class ToolbarManager that inherits from NSObject. It has a toolbar property defined as follows:
#objc dynamic let toolbar: NSToolbar
In the initializer of my class, I call the following code:
init(toolbar: NSToolbar) {
self.toolbar = toolbar
toolbar.allowsUserCustomization = true
observation = observe(\.toolbar.customizationPaletteIsRunning, options: [.old, .new]) {
(_, change) in
print("was running: \(change.oldValue)")
print("is running: \(change.newValue)")
}
}
where observation is another property defined as follows:
var observation: NSKeyValueObservation?
When I run the app and select Customize Toolbar... or close the customization view, the observation closure is never called, and thus nothing is printed to the console.
Why?
(And how can I fix that?)
Edit:
The Apple documentation states:
You can only use key-value observing with classes that inherit from NSObject.
I'm not sure how to understand this. Does it mean that any property defined on NSObject subclasses can be observed or does it mean that only properties that are NSObject subclasses can be tracked?
(The latter would explain why my code doesn't work but then this example which observes a plain Swift string wouldn't work either. And it does work.)
You can only use key-value observing with classes that inherit from NSObject.
means it's required that only objects in a subclass of NSObject can be observed.
It does not mean that any object in a subclass of NSObject is implicitly observable.
It seems that this property of NSToolbar is not KVO compliant.
I am designing a framework that uses protocols and extensions to allow for third-parties to add support for my framework to their existing classes.
I'd also like to include some built-in extensions for known classes like UIView, but I don't want to prevent users from defining their own additional support for the same classes.
My question is is there any way that I can extend the same class twice, and override the same (protocol) method in that class both times, while still having some way to call the other if the first one fails.
Elaboration: I really have three goals here I want to achieve:
I want to allow users of my framework to provide their own extensions for their own (or any) UIView subclasses.
I also need some way to allow general behavior that can apply to all UIViews as a fallback option (i.e. if the specific class extension can't handle it, fall back on the generic UIView extension).
I'd also like to separate out my own implementation, by providing some built-in generic view handling, but in such a way that it doesn't prevent third parties from also defining their own additional generic handling. (If I can't do this, it's not a big deal, the first two parts are the most important.)
I have part 1 working already. The problem is how to get this fallback behavior implemented. If I do it all with extensions, the subclass will override the superclass's implementation of the protocol method. It could call super.method, but I'd like to avoid putting that responsibility on the subclass (in case the author forgets to call super).
I'd like to do this all from the framework code: first, call the object's protocol method. If it returns false, I'd like to somehow call the generic UIView handler.
Now that I'm typing it all out, I'm wondering if I can just use a different method for the generic fallback and be done with it. I just figured it would be elegant if I could do it all with one method.
No! It can't be extended multiple times.
extension Int {
var add: Int {return self + 100} // Line A
}
extension Int {
var add: Int {return self + 105} //Line B
}
Doing so would create a compile time error ( on Line B) indicating: Invalid redeclaration of 'add'
Swift is a static typing language and helps you find these sorts of errors before runtime
In Objective-C you can write this and still not get an error, however the result would be undefined, because you wouldn't know which method gets loaded first during runtime.
Overriding a single protocol method twice in 2 separate extensions wouldn't work, because the protocol method names would collide. Once compiled, they're all just methods on the same class. With that in mind, perhaps put all the protocol methods in their own extension & call them from within the other ones?
The following could be one general option. Could get messy if you decide to keep adding additional extension functionality.
class baseClass {
//stuff
}
extension baseClass: myProtocol {
override func myProtocolMethod(args) -> returnType {
//Repeat this in a separate extension & your method names collide
var status: Bool
//protocol method code sets status as appropriate...
return status = true ? optOne(status) : optTwo(status)
}
func optOne(status:Bool) -> returnType{
//do the 'true' thing
return returnType
}
func optTwo(status:Bool) -> returnType{
//do the 'false' thing
return returnType
}
}
extension baseClass {
var oneExtension = myProtocolMethod(someArg)
}
extension baseClass {
var twoExtension = myProtocolMethod(someArg)
}
I realize this Question is over a year old and the original poster has probably moved on to other things, but I'd like to share an idea anyways and perhaps get some feedback.
You say that you want a method that can be overwritten multiple times. The short answer, like many in this thread have given is no, but the long answer is yes.
We can solve the issue with a bit of generic magic.
class MyView: UIView {
var customizer: MyProtocol<MyView> = Defaults()
func willCallCustomizer() {
customizer.coolMethod(self)
}
}
// Use this class as if it were a protocol
class MyProtocol<T: UIView>: NSObject {
func coolMethod(_ view: T) {}
}
// Class inherits from the "protocol"
class Defaults: MyProtocol<MyView> {
override func coolMethod(_ view: MyView) {
// Some default behavior
}
}
/// on the clients end...
class CustomerCustomizer: MyProtocol<MyView> {
override func coolMethod(_ view: MyView) {
// customized behavior
}
}
So if the client wants to use their own customizer they can just set it, otherwise it will just use the default one.
myViewInstance.customizer = CustomerCustomizer()
The benefit of this approach is that the client can change the customizer object as many times as they want. Because MyProtocol is generic, it may be used for other UIView's as well; thus fulfilling the role of a protocol.
Background:
I designed a TableViewDataSource class that provides an implementation for UITableViewDataSource and UITableViewDelegate. You instantiate TableViewSection objects, which are passed to the TableViewDataSource which are used to configure cells, section headers, handle selection, row insertion, etc.
The TableViewSection object has a property called dataSource: [AnyObject]?, which, when set, is used to calculate the number of rows in the section, and provide an object for the cell configuration block:
// get the section, dequeue a cell for that section, retrieve the item from the dataSource
// ...
tableSection.cellConfigurationBlock?(cell: AnyObject, item: AnyObject?, indexPath: NSIndexPath)
return cell
What I'd like to do is assign a reference to an array from my viewModel to my tableSection.dataSource, having my viewModel update the array, in turn updating the table view. In Swift, you cannot pass an array by reference. The workaround seems to be to use an NSMutableArray, but with that comes a loss of type safety, and greater cognitive load while translating objects back and forth from Swift to Foundation.
Working Example:
let kCellIdentifier = "SomeCellIdentifier"
class MyViewController: UITableViewController {
// Property declarations
#IBOutlet var tableDataSource: TableViewDataSource!
var viewModel: MyViewControllerViewModel = MyViewControllerViewModel()
override func viewDidLoad() {
super.viewDidLoad()
self.setupTableView()
self.refresh()
}
func setupTableView() {
var tableSection = TableViewSection(cellIdentifier: kCellIdentifier)
tableSection.dataSource = self.viewModel.collection
// tableSection configuration
// ...
self.tableDataSource.addSection(tableSection)
}
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
}
The refresh() method on the viewModel hits my API service, updates it's collection property on response, and provides the result on the next event of an RACSignal (RACSignal is a class provided by Reactive Cocoa and really, besides the point).
I've found one workaround, which involves reassigning the data source each time a single update is made, or after a batch update.
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.updateDataSource()
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
func updateDataSource() {
self.tableDataSource.tableSectionForIndex(0)?.dataSource = viewModel.collection
}
This approach works, but only temporarily as a workaround. As a TableViewDataSource grows and becomes more complex, this method becomes increasingly more complex with imperative, procedural code, the opposite of what I set out to achieve when writing the class.
Question
Is there any workaround to stick to native Swift Array's to achieve the equivalent of passing a Foundation NSArray or NSMutableArray by reference?
Bonus Question
Can someone provide me with some class/struct design tips to accomplish the desired goal in pure Swift?
The simple solution is to wrap the array in a class. The class instance is passed by reference so the problem is effectively solved: a change to the array through any reference to the class instance affects the array as seen through every reference to that class instance.
The class in question can be extremely lightweight - basically, it just serves as a thin wrapper that carries the array along with it, and a client accesses the array directly through the class instance - or, just the opposite, you can design the class to manage the array, i.e. the class deliberately presents an array-like API that shields clients from the underlying implementation. Either approach might be appropriate; I've certainly done both.
Here's an example of the first kind of situation. My model object is an array belonging to a UIDocument subclass. My view controller is a UITableViewController. The user is going to view, add, and edit model entities in the table. Thus, the UITableViewController needs access to the UIDocument's array (which happens to be called people).
In Objective-C, my UITableViewController simply held a reference to the array, self.people, which was an NSMutableArray. This was just a pointer, so changes to self.people were also changes to the UIDocument's people - they are one and the same object.
In Swift, my UITableViewController holds a reference to the UIDocument object, self.doc. The array, which is now a Swift array, is "inside" it, so I can refer to it as self.doc.people. However, that's too much rewriting! Instead, I've created a calculated variable property self.people which acts as a gateway to self.doc.people:
var doc : PeopleDocument!
var people : [Person] { // front end for the document's model object
get {
return self.doc.people
}
set (val) {
self.doc.people = val
}
}
Hey presto, problem solved. Whenever I say something like self.people.append(newPerson), I'm passed right through to the UIDocument's model object people and I'm actually appending to that. The code thus looks and works just like it did in Objective-C, with no fuss at all.