I'm trying to set up custom property validation for a property belonging to an NSManagedObject subclass. According to the docs the general signature for such a method in Swift 3 is as follows:
func validate<Key>(value: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws
My property is called amount, so I've got:
class Transaction: NSManagedObject {
#NSManaged var amount: Float
func validateAmount(value: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
// custom validation logic...
}
}
As I understand it, when I create a new Transaction instance, set its amount property, and then try to save the managed object context, validateValue:forKey: should be called on the relevant instance, which should in turn locate and call my custom validation method. Unfortunately this isn't happening: validateValue:forKey: is called, but my method is ignored.
You can see all this for yourself in this sample project (written in Xcode 8.3.3), and I'd be grateful if someone can tell me where I'm going wrong.
func validateAmount(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws is called. Add a _ like in validateValue(_ value.
Related
I'm working with Property wrappers in a project that is divided into few Swift package modules dependent on each others. I have been using my own property wrappers in the code, but I want to switch to Combine, as it contains a lot of similar functionalities to my own property wrappers. In the process of converting I often occur the compiler issue:
Abort trap: 6 with error message: Global is external, but doesn't have external or weak linkage!.
As the first step I have decided to get rid of this error while still using my own property wrappers, because it can occur even there, but I managed to get rid of it. But in not a clean way, and I would like to get more knowledge on what is going on, so I could proceed with Combine later - with the same errors.
Ok, so to get rid of the Abort trap: 6 with my old property wrappers I needed to switch the way I used this property in some of modules. Instead of writing and reading it directly, I access it with $property.wrappedValue. Then it works, but this is very ugly in code, and kinda denies the puropse of using a property wrapper. Could someone explain me why this error occurs? In some of the modules Im able to use it directly with no problem. I have no idea what is going on, and what I can do to resolve this. I tried to convert a lot of similar properties with Combine, and I just get more of this errors, and actually I was not even able to resolve them like this.
Please tell me what is this error about, why it happens and what I can do to resolve it.
If his helps, this is how this property wrapper is defined:
#propertyWrapper
public class ObservableChangedValue<Value: Equatable>: Observable<Value> {
public var projectedValue: ObservableChangedValue { self }
public override var wrappedValue: Value {
get { super.wrappedValue }
set { super.wrappedValue = newValue }
}
override func shouldExecuteNotifications(oldValue: Value, newValue: Value) -> Bool {
oldValue != newValue
}
}
public class Observable<Value> {
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
internal var observations = [Observation<Value>]() { didSet { isBeingObserved = !observations.isEmpty } }
public var wrappedValue: Value {
didSet {
guard shouldExecuteNotifications(oldValue: oldValue, newValue: wrappedValue) else { return }
observations.forEach {
$0.executeNotification(oldValue: oldValue, newValue: wrappedValue)
}
}
}
public weak var delegate: ObservableDelegate?
public var isBeingObserved = false {
didSet {
guard isBeingObserved != oldValue else { return }
delegate?.isBeingObservedChanged(isBeingObserved)
}
}
internal func shouldExecuteNotifications(oldValue: Value, newValue: Value) -> Bool { true }
}
One big thing - I have noticed that the error only occurs when I access this variable from other file than where it was defined. I added getters and setters for now, but still not a nice solution. This seems to be a compiler error, but Im not sure.
Using property wrappers in extensions from different file seems to be
causing this
see what they claims in documentation
Property wrapper types
A property wrapper type is a type that can be used as a property wrapper. There are two basic requirements for a property wrapper type:
The property wrapper type must be defined with the attribute
#propertyWrapper. The attribute indicates that the type is meant to
be used as a property wrapper type, and provides a point at which
the compiler can verify any other consistency rules.
The property wrapper type must have a property named wrappedValue,
whose access level is the same as that of the type itself. This is
the property used by the compiler to access the underlying value on
the wrapper instance.
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
}
Some context first:
I am building a generic API for my CoreData Database. All Objects in my model live in pairs:
An NSManagedObject class that is stored in CoreData and can be converted into an NSObject with a protocol called ManagedObjectProtocol
An NSObject class that is actually used throughout my app and can be converted into an NSManagedObject with a protocol called DataObject
My ManagedObject Protocol
//MANAGED OBJECT PROTOCOL - Should be adhered to by all NSManagedObject classes
protocol ManagedObjectProtocol where Self: NSManagedObject {
//var managedObjectID: NSManagedObjectID { get set }
func populateRegularObject() -> DataObject
func populateRegularObjectFromRelated<T: TypeErasedDataObject>(relatedObject: T, at key: String) -> DataObject
}
In my API, I load the objects as follows:
let managedObject = API.shared.persistentContainer.newBackgroundContext().object(with: someObjectID) as! ManagedObjectProtocol
let toReturn = managedObject.populateRegulardObject() //<-- This Crashes
The problem:
This successfully loads my object. I should now be able to populate the DataObject that belongs to this ManagedObjectProtocol and use it in my app. But I can't because, apparently, typecasting to a Protocol loads the object differently than when I TypeCast it as a normal NSManagedObject. Immediately when I access a property of the loaded ManagedObject, my app crashes with error EXC_BAD_ACCESS.
Question:
How can I access my NSManagedObject's properties when I need to typecast it to a protocol?
To me, it would make sense to be able to do something like this:
extension NSManagedObject where Self: ManagedObjectProtocol {
func populateDataObject() -> DataObject
}
But this can't be done in swift. Can anyone suggest a solution? Any help would be highly appreciated.
The following post will help you better understand the issue
https://www.lesstroud.com/dynamic-dispatch-with-nsmanaged-in-swift/
Essentially, it seems that core data is unable to handle protocols which are unmanaged. It seems like core data rewrites the class definition to pass #NSManaged through proxy methods, but is unable to do so for protocols.
Adding the dynamic keyword to your property declaration will solve this issue.
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.
Please don't ask me to go read a link on the apple website, because I've read all of it!
Im confused on where the actual initialization of the property 'text' is taking place? Is it in the init method or is it happening in the instance declaration?
Because it seems to me that the initialization of the text property is happening in the instance declaration where it's being given the value "How about beets?"
And if so, then why does the apple book state that all properties must be initialized before an instance is created; within the class definition (correct me if I'm wrong)
Lastly if 'text' is actually being initialized in the class definition....where is the initialization??
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
The initialization takes place in the init method. Note that init takes one parameter, text, and assigns that parameter to self.text.
I believe the process followed in you code is:
You call SurveyQuestion(text: "How about beets?") to get an instance of the SurveyQuestion class.
When the class is running it's initialization method init(text: String) it initializes all the properties. That means you're initializing the text property, giving it a value.
Finally the class finishes initialization and you get an instance of that class.
That corresponds to Apple's documentation as when you initialize the class the properties are initialized but you don't get the class instance until the init method has finished, and that means until all properties are initialized.
Sorry for the initialization redundance, I didn't find another way to explain it.
I hope that solves your doubts.