Can't access by data via KVC during KVO
Can't access by data via KVC during KVO
- Subject: Can't access by data via KVC during KVO
- From: Daryle Walker <email@hidden>
- Date: Mon, 27 Mar 2017 17:48:06 -0400
This is from my window controller class, which has a (optional) reference to my document subclass’s model data. Through Interface Builder, that data is connected to a NSObjectController, and a property of that data is connected to a NSArrayController. The window controller has outlets to both data controllers. The model data is Core Data-based, BTW.
I have to watch the data as part of my scheme to create a character map to use for NSTextFinder. Instead of KVO-ing the model data, I do the two data controllers instead since those are the “truth” I need to track (in case they cache any editing changes).
> headerController.addObserver(self, forKeyPath: MessageWindowController.headerKeyPath, options: .initial, context: &MessageWindowController.headerObservingContext)
> messageController.addObserver(self, forKeyPath: MessageWindowController.bodyKeyPath, options: .initial, context: &MessageWindowController.bodyObservingContext)
The header observation points to the array controller’s “arrangedObjects”. The message observation points to the object controller’s “selection”, then a specific property of my model (of type “String?”). I try to test an incomplete version of the KVO function:
> override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
> guard let keyPath2 = keyPath, let object2 = object, let change2 = change, let context2 = context else {
> return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
> }
>
> let changeKind = NSKeyValueChange(rawValue: change2[.kindKey] as! UInt)!
> switch context2 {
> case &MessageWindowController.headerObservingContext:
> precondition(keyPath2 == MessageWindowController.headerKeyPath)
> precondition(object2 as AnyObject === headerController)
> break
> case &MessageWindowController.bodyObservingContext:
> precondition(keyPath2 == MessageWindowController.bodyKeyPath)
> precondition(object2 as AnyObject === messageController)
> assert(changeKind == .setting)
>
> let newString = (messageController.selection as! NSObject).value(forKey: #keyPath(RawMessage.body)) as! String? //messageController.value(forKeyPath: keyPath2) as! String?
> let bodyTextRangeIndex = textRanges.index(before: textRanges.endIndex)
> let oldBodyTextRange = textRanges[bodyTextRangeIndex]
> let newLength = (newString as NSString?)?.length ?? 0
> if oldBodyTextRange.length != newLength {
> textRanges[bodyTextRangeIndex] = NSMakeRange(oldBodyTextRange.location, newLength)
> }
> break
> default:
> super.observeValue(forKeyPath: keyPath2, of: object2, change: change2, context: context2)
> }
> }
And there’s a crash at the “newString” definition. (The current and commented-out versions get the same error. The current version was copied from my menu-item validation function, where it actually works.):
> Could not cast value of type '_NSStateMarker' (0x7fffa3003cf8) to 'NSString' (0x7fffa397df38).
> 2017-03-27 16:36:33.978709 XNW[39160:4830169] Could not cast value of type '_NSStateMarker' (0x7fffa3003cf8) to 'NSString' (0x7fffa397df38).
What is this “NSStateMarker” class? And why does it prevent me from accessing the data?
Do NSObjectController and/or NSArrayController do any caching of its editing data? If not, and we can’t directly solve this problem, I could read the data directly from the document’s model objects.
Pre-Send Update:
I took out the “.initial” from the observing setup call, and it works! Why is that? Now I have to hope that my concept of the initial text-range array is accurate, since I can’t confirm it with an initial pass (for now).
Note that when the window controller goes through “windowDidLoad”, the reference to the model data is NIL. (The object and array controllers are still bound to it by then, though.) It doesn’t get set to an actual data tree until the document’s “makeWindowControllers” call.
Pre-Send Update 2:
I completed a first try for the other property:
> let changeKind = NSKeyValueChange(rawValue: change2[.kindKey] as! UInt)!
> let bodyTextRangeIndex = textRanges.index(before: textRanges.endIndex)
> switch context2 {
> case &MessageWindowController.headerObservingContext:
> precondition(keyPath2 == MessageWindowController.headerKeyPath)
> precondition(object2 as AnyObject === headerController)
>
> let newArray = headerController.mutableArrayValue(forKeyPath: keyPath2) as NSArray
> switch changeKind {
> case .insertion, .removal, .replacement:
> fallthrough
> case .setting:
> var newFieldRanges = RangeArray()
> for newField in newArray {
> let field = newField as! RawHeaderField
> newFieldRanges.append(contentsOf: [field.name, field.body].map { NSMakeRange(0, ($0 as NSString).length) })
> }
> textRanges.replaceSubrange(textRanges.startIndex ..< bodyTextRangeIndex, with: newFieldRanges)
> }
This one still works when “.initial” is active.
—
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden