Re: Can't access by data via KVC during KVO

2017-03-27 Thread Quincey Morris
On Mar 27, 2017, at 14:48 , Daryle Walker  wrote:
> 
> The message observation points to the object controller’s “selection”, then a 
> specific property of my model (of type “String?”).

>> 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?

In short, you shouldn’t be using the “selection” property as if it means 
something for an object controller. Yes, you *can* select an object in the 
object controller’s content as *the* selection, but it doesn’t help you with 
anything — it doesn’t represent anything in the UI.**

By definition, the “selection” is a proxy for an object (NSObjectController) or 
an array of objects (NSArrayController, which is a *subclass* of 
NSObjectController), but it can also represent a marker value, indicating no 
selection or multiple selection or (IIRC) some other possibilities. I assume 
your crash occurred because at the time you referenced “selection” it was set 
to one of these markers, and hence not castable to a String.

Programming (in the sense of writing actual code) against NS…Controller objects 
is a horrible and dangerous experience, because they’re black boxes and you’ll 
never know what they’re really doing internally. I strongly, strongly recommend 
that you program against the data model directly. There’s no value (that I know 
of) in referencing a NSObjectController as you have. NSArrayController is a bit 
less clear, because if you’re using it to fetch, sort and filter Core Data 
records, then you sort of think you need to use its arrangedObjects property. 
Still, you don’t have to go through a NSArrayController — there’s nothing wrong 
with fetching, sorting and filtering Core Data records yourself.


** I’ve never really understood for sure, but I think the point of having a 
selection is that (by default in IB) NSObjectController models a 
NSMutableDictionary, and it can distinguish between multiple keys “within” the 
“dictionary” that are in most cases just different properties of the content 
object. By binding to “selection” and switching the selection, you can switch 
which key supplies a value to a single UI field (like a primitive master-detail 
arrangement).

In fact, what normally happens is that the keys just represent independent 
objects accessible via the content object, and each of them is bound directly 
to its own UI element. In this case, the selection basically has no meaning.
___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Can't access by data via KVC during KVO

2017-03-27 Thread Daryle Walker
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:
>