Date   

Re: Data structures and user interaction conundrum

Graham Cox
 

Thanks to everyone who chipped in so far — interesting ideas.

The zOrder is in fact a float (CGFloat, so 64 bit), but as Jens points out, it can’t go on indefinitely.

In experimenting with this, it turns out that limiting the reordering to just the objects that are visually intersected is a really, really nice behaviour from the user’s perspective. It means that every operation has a visible effect, rather than appearing to do nothing in the cases where you are reordering relative to an object some distance away. I believe from a usability perspective this is a good solution. I may add “global” reordering as an option, assuming I can find a solution I like. The trie idea is interesting, but possibly overkill for the feature… and tbh, learning enough about tries to implement one correctly is probably going to take more time than I’m willing to spend. Though I notice there is a class in Core Foundation - CFBurstTrie - that might fit the bill (not sure if it has a public interface however, seems it may be something private).

Regarding running out of precision when ’splitting the difference’ when reordering, the reality is that this is an uncommon operation in terms of sheer numbers. It probably comes up moderately frequently, but my feeling is that it would be unlikely to run out of numbers in most cases. Of course that’s not good enough - it means a potential bug in the limit when things don’t work as they should. But occasionally renumbering the whole stack is likely to work to solve that, and that can be done when the model is built or saved. Or Jens’ idea of using strings could work for reordering, but may be too slow to sort on that order for drawing (which is my main priority - drawing has to be fast). With local reordering, zOrder value clashes inevitably do occur, because objects further away are just not considered while the local renumbering is performed. Again, that doesn’t matter too much because nothing critical rests on this (unlike Jens’ To Do list example), the only time it would reveal itself is if the user drags an object across the canvas and it gets occluded by something and they didn’t expect that. But in an interactive drawing environment, I'd suggest that doesn’t especially matter - they can simply reorder as necessary in that new local area to get the visual result they want.


—Graham




On 24 Jul 2020, at 5:11 am, Jens Alfke <jens@...> wrote:



On Jul 22, 2020, at 7:19 PM, Peter Teeson via groups.io <pteeson@...> wrote:

Are the zObjects integers? If so would it help to make them floats? Intial value 3.0; Back one 2.9  or 2.09; Forward one 3.1 or 3.01?

Sadly, this only postpones the problem — you run out of precision after a while. A  64-bit double only has about 52 bits of mantissa, meaning that some sequences of 52 reorders will cause a collision between adjacent Z values.

I've actually seen this exact problem in other domains — usually having to do with distributed algorithms for preserving ordering, as in a collaborative to-do list with offline support. IIRC it also shows up in some P2P distributed hash tables like Chord and Kademlia.

A workable way to do this is to use strings, since you never run out of room. So the initial objects are labeled A, B, C, D… When you need to put an object in between C and D, you label it CM. When something has to go between CM and D, you label it CS. Eventually you have to fit something between CR and CS, so you label it CRM. (In a real implementation you'd probably use bit-strings instead. Which you can also interpret as fractional binary numbers … exactly like your suggestion except using infinite precision instead of floating point.)

The eventual problem is that over time the Z labels get longer and longer. Probably not an issue for a graphics editor? The usual solution is to occasionally renumber everything with short strings, perhaps when saving.

This is probably equivalent to David Young's radix-trie suggestion … the trie is a more complex data structure than a sorted array, but performs better. Sliding a mere 25,000 pointers around in an array shouldn't take a noticeable amount of time, though (it's only 200KB) provided you only do it once per user command. (And actually, I believe NSArray already turns into something more tree-like when the count grows large, to make inserts/deletes faster.)

—Jens


CATiledLayer in NSView (Mac) -- how?

Graham Cox
 

Does anyone have sample code or just a few hints how to use CATiledLayer in a NSView on Mac?

I’m reasonably au fait with using layers in a view in general, but this one is very minimally documented and doesn’t seem to work in quite the expected way. I’d like my app to work similarly to how Maps works, which I believe uses this class internally.

—Graham


Determine System Time Format (12hr vs 24hr)

Sandor Szatmari
 

I have been using the following code to determine if a system is set to 12hr or 24hr time for sometime…

            NSString *formatStringForHours = [NSDateFormatter dateFormatFromTemplate:@"j" options:0 locale:[NSLocale currentLocale]];
            NSRange containsH = [formatStringForHours rangeOfString:@"H"];
            BOOL shouldUse24HrTime = containsH.location != NSNotFound;
            format = (shouldUse24HrTime) ? @"H:mm" : @"h:mm";

Now, on Catalina, I am realizing that this code is failing.  Unfortunately, I don't know when this started to fail. 
It always returns 'h a', whereas previously it returned 'H'.

macOS: 10.15.5 (19F101)
Xcode: 11.6 (11E708)
Deployment Target: 10.11
Build SDK: 10.15

Is anyone else seeing the same thing?  If others can confirm this same apparently errant behaviour I'll file a bug report.  First, I want to be sure it's not just my system…

Also, does anyone know an alternative, or better, way to determine the time format of the system? ('Use 24-hour clock' as found in Date & Time System Preferences)

Thanks,
Sandor


Re: Data structures and user interaction conundrum

 



On Jul 22, 2020, at 7:19 PM, Peter Teeson via groups.io <pteeson@...> wrote:

Are the zObjects integers? If so would it help to make them floats? Intial value 3.0; Back one 2.9  or 2.09; Forward one 3.1 or 3.01?

Sadly, this only postpones the problem — you run out of precision after a while. A  64-bit double only has about 52 bits of mantissa, meaning that some sequences of 52 reorders will cause a collision between adjacent Z values.

I've actually seen this exact problem in other domains — usually having to do with distributed algorithms for preserving ordering, as in a collaborative to-do list with offline support. IIRC it also shows up in some P2P distributed hash tables like Chord and Kademlia.

A workable way to do this is to use strings, since you never run out of room. So the initial objects are labeled A, B, C, D… When you need to put an object in between C and D, you label it CM. When something has to go between CM and D, you label it CS. Eventually you have to fit something between CR and CS, so you label it CRM. (In a real implementation you'd probably use bit-strings instead. Which you can also interpret as fractional binary numbers … exactly like your suggestion except using infinite precision instead of floating point.)

The eventual problem is that over time the Z labels get longer and longer. Probably not an issue for a graphics editor? The usual solution is to occasionally renumber everything with short strings, perhaps when saving.

This is probably equivalent to David Young's radix-trie suggestion … the trie is a more complex data structure than a sorted array, but performs better. Sliding a mere 25,000 pointers around in an array shouldn't take a noticeable amount of time, though (it's only 200KB) provided you only do it once per user command. (And actually, I believe NSArray already turns into something more tree-like when the count grows large, to make inserts/deletes faster.)

—Jens


Re: Data structures and user interaction conundrum

David Young
 

Graham,

I am picturing a radix trie with n-bit keys where n is 32 or 64—whichever suits your z-orders. Keys represent the z order. Each object is a leaf in the trie. In each object, store its z-order (key).

Say higher z is farther away. To move object X forward, first locate the preceding object, P, in the tree. If P does not exist, you’re done, because X is at the front. If P exists, swap the places in the tree of X and P. Swap the z-orders.

If you initially assign z-orders starting with 2^n - 1 and decreasing, then I don’t think you will have to re-z-order all objects.

You could use some balanced binary tree, too. That may be more suitable, actually.

Hope this helps.

Dave

Spilling kerrectud by iPhone

On Jul 22, 2020, at 7:22 PM, Graham Cox <graham@...> wrote:

Hi all,

I Have an interesting little problem, thought I’d throw it out there and see if it stirs up any discussion….

Graphics app, wherein the user can draw stuff - arbitrary vectors on a canvas. My bread-and-butter, really. Over the years of doing this, I’ve tried a variety of underlying storage schemes to ensure efficiency when the number of objects grows large. We’re talking 25,000 objects+, potentially.

One challenge is to avoid the need to iterate all 25,000 in order to figure out which ones need drawing for any given update/dirty rects. The naive approach of just storing an array and testing the bounding rects of every object bogs down fast above a few thousand.

So I use a spatial database which is an R-Tree [R-tree] - this rapidly eliminates vast numbers of objects by collecting objects into groups of bounding rects at each higher level of the tree. This works great - I can store tens of thousands of objects and drawing remains efficient provided the update area is small, which it typically is - the user will typically only zoom part of the canvas and work there, then scroll to a different part. There are remaining issues with drawing performance when zooming out to show the whole thing, etc, but that’s not the issue I want to talk about here (tiling helps a lot there).

Objects stored in the R-Tree are only sorted into groups according to their bounding rects, otherwise there is no ordering. Everything is implemented using sets, and the tree itself is very agnostic about what objects it references - as long as they conform to a very basic protocol, it’s happy:

@protocol GCRTreeStorable <NSObject, NSCoding>

- (CGRect) extent;

@end


The R-Tree’s only job is to (very quickly) return a set of objects that intersect a given rectangle. That could be the update area, or a selection rect, for example.

So the returned objects are unsorted.

For drawing, there is a specific Z-order that the objects need to be drawn in, so that the visual layering is stable. That’s no problem - each one has a ‘zOrder’ property which defines its level, and the objects to be drawn are sorted on that property, then drawn in order. This works no matter which set of objects is returned in any given update area - the zOrder value applies to the whole set of all objects (it is incremented as new objects are created), but in any subset, the order is the same even though the vast majority of objects are skipped and not drawn. So far, so good.

The user needs the ability to change the z-ordering of objects at will, usually implemented in terms of commands: “move to front”, “move to back”, “move forward”, “move backward”.

Moving to the front or back is straightforward; changing the z-order property to be zero or max is all that is required.

Moving back one or forward one is more problematic however. Classically, all objects were held in an array and this array defined the z-order. This means a huge array for one thing, but it is also a poor fit for sorting objects to be drawn. When each object has a zOrder property, that is trivial. But the zOrder property, if an array is used, means the index of the object in the array. Renumbering the zOrder of every object to match the array is slow. Looking up the index of the object in the array is slow (linear search). Just changing the value of ‘zOrder’ is simple, effective and extremely fast. No array required.

The problem is with moving back one or forward one zOrder position. What value do I give that? It can’t just be incremented or decremented, because that could mean that it now has the same zOrder value as another object. Furthermore, we don’t know WHICH other object it might clash with. It could be something nearby, or something somewhere else entirely. Without the array, we have no way to quickly find objects having similar zOrder values.

zOrder applies to the whole set of objects, but maybe it doesn’t need to. Vector apps have always taken the approach that zOrder is ‘global’ in this sense, but I’m wondering if that really makes sense. One effect is that move forward or move backward often have no visible effect, because the objects that they are ordered relative to don’t intersect it. In fact they could be miles away.

So I’m wondering if the solution to my data structure problem is not to come up with a way to mimic the ‘global zOrder’ behaviour of antiquity, but instead to make zOrdering changes very localised. In other words, only reorder objects relative to other nearby objects. This would be better behaviour in some ways, because it matches the user’s visual expectations - if they are reordering objects in z, it’s because they want to move something behind or in front of something else they can see there, not some unrelated object on the other side of the canvas. Finding a suitable value for zOrder in this situation is much easier, because I only need to renumber objects in a local group (or using a floating point value, it can be set to half way between two others). These may end up clashing with others elsewhere, but unless the user decides to move something to a part of the canvas where that becomes visible, they will never see it. This of course could occur - but then adjusting zOrder in THAT  local group would be straightforward too.

If you’ve read and understood this far, well done.. haha.

So my question for discussion, should anyone feel interested enough, is: should I try to find a data structure that allows me to do things the classic way, even though in some ways that’s less usable (but perhaps more in line with expectations), or should I change the way user interact with the zOrder and avoid the coding problem altogether?



—Graham


Re: Data structures and user interaction conundrum

Peter Teeson
 

Are the zObjects integers? If so would it help to make them floats? Intial value 3.0; Back one 2.9  or 2.09; Forward one 3.1 or 3.01?

Or have I not understood your architecture or the memory implications?

respect….

Peter

On Jul 22, 2020, at 8:22 PM, Graham Cox <graham@...> wrote:

Hi all,

I Have an interesting little problem, thought I’d throw it out there and see if it stirs up any discussion….

Graphics app, wherein the user can draw stuff - arbitrary vectors on a canvas. My bread-and-butter, really. Over the years of doing this, I’ve tried a variety of underlying storage schemes to ensure efficiency when the number of objects grows large. We’re talking 25,000 objects+, potentially.

One challenge is to avoid the need to iterate all 25,000 in order to figure out which ones need drawing for any given update/dirty rects. The naive approach of just storing an array and testing the bounding rects of every object bogs down fast above a few thousand.

So I use a spatial database which is an R-Tree [R-tree] - this rapidly eliminates vast numbers of objects by collecting objects into groups of bounding rects at each higher level of the tree. This works great - I can store tens of thousands of objects and drawing remains efficient provided the update area is small, which it typically is - the user will typically only zoom part of the canvas and work there, then scroll to a different part. There are remaining issues with drawing performance when zooming out to show the whole thing, etc, but that’s not the issue I want to talk about here (tiling helps a lot there).

Objects stored in the R-Tree are only sorted into groups according to their bounding rects, otherwise there is no ordering. Everything is implemented using sets, and the tree itself is very agnostic about what objects it references - as long as they conform to a very basic protocol, it’s happy:

@protocol GCRTreeStorable <NSObject, NSCoding>

- (CGRect) extent;

@end


The R-Tree’s only job is to (very quickly) return a set of objects that intersect a given rectangle. That could be the update area, or a selection rect, for example.

So the returned objects are unsorted.

For drawing, there is a specific Z-order that the objects need to be drawn in, so that the visual layering is stable. That’s no problem - each one has a ‘zOrder’ property which defines its level, and the objects to be drawn are sorted on that property, then drawn in order. This works no matter which set of objects is returned in any given update area - the zOrder value applies to the whole set of all objects (it is incremented as new objects are created), but in any subset, the order is the same even though the vast majority of objects are skipped and not drawn. So far, so good.

The user needs the ability to change the z-ordering of objects at will, usually implemented in terms of commands: “move to front”, “move to back”, “move forward”, “move backward”.

Moving to the front or back is straightforward; changing the z-order property to be zero or max is all that is required.

Moving back one or forward one is more problematic however. Classically, all objects were held in an array and this array defined the z-order. This means a huge array for one thing, but it is also a poor fit for sorting objects to be drawn. When each object has a zOrder property, that is trivial. But the zOrder property, if an array is used, means the index of the object in the array. Renumbering the zOrder of every object to match the array is slow. Looking up the index of the object in the array is slow (linear search). Just changing the value of ‘zOrder’ is simple, effective and extremely fast. No array required.

The problem is with moving back one or forward one zOrder position. What value do I give that? It can’t just be incremented or decremented, because that could mean that it now has the same zOrder value as another object. Furthermore, we don’t know WHICH other object it might clash with. It could be something nearby, or something somewhere else entirely. Without the array, we have no way to quickly find objects having similar zOrder values.

zOrder applies to the whole set of objects, but maybe it doesn’t need to. Vector apps have always taken the approach that zOrder is ‘global’ in this sense, but I’m wondering if that really makes sense. One effect is that move forward or move backward often have no visible effect, because the objects that they are ordered relative to don’t intersect it. In fact they could be miles away.

So I’m wondering if the solution to my data structure problem is not to come up with a way to mimic the ‘global zOrder’ behaviour of antiquity, but instead to make zOrdering changes very localised. In other words, only reorder objects relative to other nearby objects. This would be better behaviour in some ways, because it matches the user’s visual expectations - if they are reordering objects in z, it’s because they want to move something behind or in front of something else they can see there, not some unrelated object on the other side of the canvas. Finding a suitable value for zOrder in this situation is much easier, because I only need to renumber objects in a local group (or using a floating point value, it can be set to half way between two others). These may end up clashing with others elsewhere, but unless the user decides to move something to a part of the canvas where that becomes visible, they will never see it. This of course could occur - but then adjusting zOrder in THAT  local group would be straightforward too.

If you’ve read and understood this far, well done.. haha.

So my question for discussion, should anyone feel interested enough, is: should I try to find a data structure that allows me to do things the classic way, even though in some ways that’s less usable (but perhaps more in line with expectations), or should I change the way user interact with the zOrder and avoid the coding problem altogether?



—Graham


Data structures and user interaction conundrum

Graham Cox
 

Hi all,

I Have an interesting little problem, thought I’d throw it out there and see if it stirs up any discussion….

Graphics app, wherein the user can draw stuff - arbitrary vectors on a canvas. My bread-and-butter, really. Over the years of doing this, I’ve tried a variety of underlying storage schemes to ensure efficiency when the number of objects grows large. We’re talking 25,000 objects+, potentially.

One challenge is to avoid the need to iterate all 25,000 in order to figure out which ones need drawing for any given update/dirty rects. The naive approach of just storing an array and testing the bounding rects of every object bogs down fast above a few thousand.

So I use a spatial database which is an R-Tree [R-tree] - this rapidly eliminates vast numbers of objects by collecting objects into groups of bounding rects at each higher level of the tree. This works great - I can store tens of thousands of objects and drawing remains efficient provided the update area is small, which it typically is - the user will typically only zoom part of the canvas and work there, then scroll to a different part. There are remaining issues with drawing performance when zooming out to show the whole thing, etc, but that’s not the issue I want to talk about here (tiling helps a lot there).

Objects stored in the R-Tree are only sorted into groups according to their bounding rects, otherwise there is no ordering. Everything is implemented using sets, and the tree itself is very agnostic about what objects it references - as long as they conform to a very basic protocol, it’s happy:

@protocol GCRTreeStorable <NSObject, NSCoding>

- (CGRect) extent;

@end


The R-Tree’s only job is to (very quickly) return a set of objects that intersect a given rectangle. That could be the update area, or a selection rect, for example.

So the returned objects are unsorted.

For drawing, there is a specific Z-order that the objects need to be drawn in, so that the visual layering is stable. That’s no problem - each one has a ‘zOrder’ property which defines its level, and the objects to be drawn are sorted on that property, then drawn in order. This works no matter which set of objects is returned in any given update area - the zOrder value applies to the whole set of all objects (it is incremented as new objects are created), but in any subset, the order is the same even though the vast majority of objects are skipped and not drawn. So far, so good.

The user needs the ability to change the z-ordering of objects at will, usually implemented in terms of commands: “move to front”, “move to back”, “move forward”, “move backward”.

Moving to the front or back is straightforward; changing the z-order property to be zero or max is all that is required.

Moving back one or forward one is more problematic however. Classically, all objects were held in an array and this array defined the z-order. This means a huge array for one thing, but it is also a poor fit for sorting objects to be drawn. When each object has a zOrder property, that is trivial. But the zOrder property, if an array is used, means the index of the object in the array. Renumbering the zOrder of every object to match the array is slow. Looking up the index of the object in the array is slow (linear search). Just changing the value of ‘zOrder’ is simple, effective and extremely fast. No array required.

The problem is with moving back one or forward one zOrder position. What value do I give that? It can’t just be incremented or decremented, because that could mean that it now has the same zOrder value as another object. Furthermore, we don’t know WHICH other object it might clash with. It could be something nearby, or something somewhere else entirely. Without the array, we have no way to quickly find objects having similar zOrder values.

zOrder applies to the whole set of objects, but maybe it doesn’t need to. Vector apps have always taken the approach that zOrder is ‘global’ in this sense, but I’m wondering if that really makes sense. One effect is that move forward or move backward often have no visible effect, because the objects that they are ordered relative to don’t intersect it. In fact they could be miles away.

So I’m wondering if the solution to my data structure problem is not to come up with a way to mimic the ‘global zOrder’ behaviour of antiquity, but instead to make zOrdering changes very localised. In other words, only reorder objects relative to other nearby objects. This would be better behaviour in some ways, because it matches the user’s visual expectations - if they are reordering objects in z, it’s because they want to move something behind or in front of something else they can see there, not some unrelated object on the other side of the canvas. Finding a suitable value for zOrder in this situation is much easier, because I only need to renumber objects in a local group (or using a floating point value, it can be set to half way between two others). These may end up clashing with others elsewhere, but unless the user decides to move something to a part of the canvas where that becomes visible, they will never see it. This of course could occur - but then adjusting zOrder in THAT  local group would be straightforward too.

If you’ve read and understood this far, well done.. haha.

So my question for discussion, should anyone feel interested enough, is: should I try to find a data structure that allows me to do things the classic way, even though in some ways that’s less usable (but perhaps more in line with expectations), or should I change the way user interact with the zOrder and avoid the coding problem altogether?



—Graham


Re: Abstracting a group of commonly named Selectors

robmartin@frontiernet.net
 

I learned a lot from this thread - thanks!


Re: Abstracting a group of commonly named Selectors

Sandor Szatmari
 

Jeff,

On Jul 22, 2020, at 17:48, Jeff Laing <jefflaing@...> wrote:



On review, one thing. Don't do this:

if ( [self respondsToSelector:sel] == NO )

It's a boolean method, never use an equality check for "true" or "false" on a boolean method, even if it seems to work, it sets you up for pain when you switch languages.

Switch languages!!! Noooo…


Re: Abstracting a group of commonly named Selectors

Sandor Szatmari
 

Jeff,

On Jul 22, 2020, at 17:44, Jeff Laing <jefflaing@...> wrote:



This capability of Objective-C, to create classes on the fly, is one of the primo reasons why I can't understand people's fascination with Swift.

Couldn’t agree more…

Even C# can do this sort of thing, but people these days seem to be terrified of taking complete control.

Personally, I would be avoiding turning something into an NSString until the last minute. You could strcmp() the selector name against your fail pattern and just create your string from "selName+offset" rather than the more expensive substringFromIndex. Not quite sure why you need to change the case of the table but that's your grammar, I guess.

There is no need to optimize for performance here…  higher level Obj-c interfaces are preferable.

Nevertheless, don't waste time optimising till it's a known performance hit.

If this were being called millions of times, I would be building a static CFDictionary(), keyed by _cmd values I had previously encountered and storing their corresponding "snakeCased substrings" so that I only did that construction once. Or I would be asking myself "could stepDictForTableName: take a char* instead of an NSString*, etc. Again, CFDictionary is so much better than NSDictionary in that space. But that's me, I hate wasting cycles.

Yes, lots of efficiencies can be made.  Unfortunately, it’s just not a necessity for us.

Thanks,
Sandor

Good luck with it.


Re: Abstracting a group of commonly named Selectors

Jeff Laing
 

On review, one thing. Don't do this:

if ( [self respondsToSelector:sel] == NO )

It's a boolean method, never use an equality check for "true" or "false" on a boolean method, even if it seems to work, it sets you up for pain when you switch languages.


Re: Abstracting a group of commonly named Selectors

Jeff Laing
 

This capability of Objective-C, to create classes on the fly, is one of the primo reasons why I can't understand people's fascination with Swift. Even C# can do this sort of thing, but people these days seem to be terrified of taking complete control.

Personally, I would be avoiding turning something into an NSString until the last minute. You could strcmp() the selector name against your fail pattern and just create your string from "selName+offset" rather than the more expensive substringFromIndex. Not quite sure why you need to change the case of the table but that's your grammar, I guess.

Nevertheless, don't waste time optimising till it's a known performance hit.

If this were being called millions of times, I would be building a static CFDictionary(), keyed by _cmd values I had previously encountered and storing their corresponding "snakeCased substrings" so that I only did that construction once. Or I would be asking myself "could stepDictForTableName: take a char* instead of an NSString*, etc. Again, CFDictionary is so much better than NSDictionary in that space. But that's me, I hate wasting cycles.

Good luck with it.


Re: Abstracting a group of commonly named Selectors

Sandor Szatmari
 

Jeff,

Works great!  See my implementation below…
The one thing I wasn’t sure about was the types array argument for class_addMethod() The fourth param…
I used ‘i’ to specify the return type of integer.  The return type is an enum.  Wasn’t sure if there was a better choice
This removed a ton of unnecessarily redundant code! 

-(id)init
{
self = [super init];
if ( self != nil )
{
IMP parser_imp = class_getMethodImplementation([self class], @selector(parseStep) );
for ( SelectorStep *step in [self parseSteps] )
{
SEL sel = step.selector;
if ( [self respondsToSelector:sel] == NO )
class_addMethod( [self class], sel, parser_imp, "i@:" );
}
}
return self;
}

// --------------------------------------------

-(ParserStepStatus)parseStep
{
const char *selName = sel_getName(_cmd);
NSString *table = [NSString stringWithCString:selName
       encoding:NSUTF8StringEncoding];
if ( [table isEqualToString:@"parseRoutePartitions"] )
NSLog( @"Stop" );
NSAssert1( [table hasPrefix:@"parse"], @"Selector must start with parse. Bad Selector: '%@'", table );
table = [[table substringFromIndex:5] snakeCase];
return [self parseStepByNameWithParams:[self stepDictForTableName:table]];
}

Sandor

On Jul 21, 2020, at 19:56, Sandor Szatmari via groups.io <admin.szatmari.net@...> wrote:

Jeff,

So what you’re suggesting is…

1. Define a template implementation
2. Associate each selector pattern with that implementation

That seems like it should work.  I’ll give it a try and let you know how it goes…

Thanks,
Sandor

On Jul 21, 2020, at 17:10, Jeff Laing <jefflaing@...> wrote:



In the designated initialise of your Parser class, you can use class_addMethod to add whatever you like to your instance's class.

https://developer.apple.com/documentation/objectivec/1418901-class_addmethod?language=objc

The method implementation gets called with the selector as its second argument and you can cut the string up at runtime to then call your Parse. Here's a snippet from an app I did in the past that wanted to dynamically adjust to any entries that were added to a popup menu in Interface Builder. It iterates over the popup, and for every entry present, looks for then adds a dynamic method called _popup.

        IMP popup_imp = class_getMethodImplementation([self class], @selector(_popup00:));
...
        for (NSArray *item in popup) {
                // make sure that we implement the required method
                SEL sel = NSSelectorFromString([NSString stringWithFormat:@"_popup%02ld:",(long)[menuItems count]]);
                if (![self respondsToSelector:sel]) {
                        class_addMethod([self class], sel, popup_imp, "v@:@");  // -(void)method:(id)argument;
                }

The implementation of the method gets the name of the selector using set_getName, then punts across to the actual code.

- (void)_popup00:(id)sender {
//          01234567
        const char *name = sel_getName(_cmd);
        const int idx = atoi(name+6);
        id resp = [self.popupResponders objectAtIndex:idx];
        [resp callWithArrayOfArgs:self.popupArguments];
}

Extracting your specific token from the message is just name+. This example is using (void) but it should work just fine - you aren't using the "no such method" path, you are just binding the method really late in the compile/link/run process...


Re: Abstracting a group of commonly named Selectors

Sandor Szatmari
 

Jeff,

So what you’re suggesting is…

1. Define a template implementation
2. Associate each selector pattern with that implementation

That seems like it should work.  I’ll give it a try and let you know how it goes…

Thanks,
Sandor

On Jul 21, 2020, at 17:10, Jeff Laing <jefflaing@...> wrote:



In the designated initialise of your Parser class, you can use class_addMethod to add whatever you like to your instance's class.

https://developer.apple.com/documentation/objectivec/1418901-class_addmethod?language=objc

The method implementation gets called with the selector as its second argument and you can cut the string up at runtime to then call your Parse. Here's a snippet from an app I did in the past that wanted to dynamically adjust to any entries that were added to a popup menu in Interface Builder. It iterates over the popup, and for every entry present, looks for then adds a dynamic method called _popup.

        IMP popup_imp = class_getMethodImplementation([self class], @selector(_popup00:));
...
        for (NSArray *item in popup) {
                // make sure that we implement the required method
                SEL sel = NSSelectorFromString([NSString stringWithFormat:@"_popup%02ld:",(long)[menuItems count]]);
                if (![self respondsToSelector:sel]) {
                        class_addMethod([self class], sel, popup_imp, "v@:@");  // -(void)method:(id)argument;
                }

The implementation of the method gets the name of the selector using set_getName, then punts across to the actual code.

- (void)_popup00:(id)sender {
//          01234567
        const char *name = sel_getName(_cmd);
        const int idx = atoi(name+6);
        id resp = [self.popupResponders objectAtIndex:idx];
        [resp callWithArrayOfArgs:self.popupArguments];
}

Extracting your specific token from the message is just name+. This example is using (void) but it should work just fine - you aren't using the "no such method" path, you are just binding the method really late in the compile/link/run process...


Re: Abstracting a group of commonly named Selectors

Jeff Laing
 

In the designated initialise of your Parser class, you can use class_addMethod to add whatever you like to your instance's class.

https://developer.apple.com/documentation/objectivec/1418901-class_addmethod?language=objc

The method implementation gets called with the selector as its second argument and you can cut the string up at runtime to then call your Parse. Here's a snippet from an app I did in the past that wanted to dynamically adjust to any entries that were added to a popup menu in Interface Builder. It iterates over the popup, and for every entry present, looks for then adds a dynamic method called _popup.

        IMP popup_imp = class_getMethodImplementation([self class], @selector(_popup00:));
...
        for (NSArray *item in popup) {
                // make sure that we implement the required method
                SEL sel = NSSelectorFromString([NSString stringWithFormat:@"_popup%02ld:",(long)[menuItems count]]);
                if (![self respondsToSelector:sel]) {
                        class_addMethod([self class], sel, popup_imp, "v@:@");  // -(void)method:(id)argument;
                }

The implementation of the method gets the name of the selector using set_getName, then punts across to the actual code.

- (void)_popup00:(id)sender {
//          01234567
        const char *name = sel_getName(_cmd);
        const int idx = atoi(name+6);
        id resp = [self.popupResponders objectAtIndex:idx];
        [resp callWithArrayOfArgs:self.popupArguments];
}

Extracting your specific token from the message is just name+. This example is using (void) but it should work just fine - you aren't using the "no such method" path, you are just binding the method really late in the compile/link/run process...


Re: Abstracting a group of commonly named Selectors

Sandor Szatmari
 

Jeff,

On Jul 20, 2020, at 17:14, Jeff Laing <jefflaing@...> wrote:



You really haven't provided enough information about the caller, and you've skipped over the C programmers favourite answer. Presumably you actually NEED all those seperate selectors and can't just change the caller to call a common handler. Second, what's wrong with:

#define IMPLEMENT(m) -(mytype)parse##m { return [self parse:m]; }
IMPLEMENT(XYZ)
IMPLEMENT(ABC)

Thanks for the idea… it might be a good choice… as for additional details.

The caller can be seen as a controller.  It dynamically allocates model (parser) classes based on context.  

The model classes define the -parse### that are available by implementing a protocol (for consistency).  So, all of these selectors are already accessible in an array of dictionaries returned by the model.

The controller then asks the model for it’s available parsers and the iterates over them -invoking each one, collects a cumulative Status, and provides an overall result.

So, the selectors are already defined In this array, then I have to implement each one as a method which is one line: red two nested calls, with a constant.  So, 99% the same, save the context (constant) of which parse method is being invoked.  Historically these methods were not one line.  But parsing newer data formats (XML) leaves less individualized code in each parse method.

Now, I can’t change this design, there’s no justifiable gains from that.  I do have the opportunity to rethink how the components of this design are stitched together for this new model, and would like less unnecessarily redundant code, if the cost of achieving this is not too great.

I hope these additional details are helpful…

Thanks,
Sandor


Re: Abstracting a group of commonly named Selectors

Jeff Laing
 

You really haven't provided enough information about the caller, and you've skipped over the C programmers favourite answer. Presumably you actually NEED all those seperate selectors and can't just change the caller to call a common handler. Second, what's wrong with:

#define IMPLEMENT(m) -(mytype)parse##m { return [self parse:m]; }
IMPLEMENT(XYZ)
IMPLEMENT(ABC)


Re: Abstracting a group of commonly named Selectors

Sandor Szatmari
 

Gary, 

On Jul 20, 2020, at 15:53, Gary L. Wade <garywade@...> wrote:

You might want to consider using valueForUndefinedKey:

interesting… 

and parse it to know which value you care about, and then return the value through NSValue, which can encapsulate any value, although a number/enum mapping might be sufficient.

I’ll have to see if Returning an NSValue wrapped scalar translates to the scalar result on the other side when I call -getReturnValue: 

Thanks!

Sandor


On Jul 20, 2020, at 12:47 PM, Sandor Szatmari <admin.szatmari.net@...> wrote:

Alex,

Thanks for the idea.  I not sure how that helps me get the return value back to the original caller.   Am I missing your point?

Here’s more details, in pseudo code

// Controller class
SEL aSel
do

aSel = lookUpSel (i.e. parseXYZ)
NSInvocation *n = // make invocation with aSel
[n setTarget: ModelClass]
[n invoke]
Status s = success // I’m an optimist
[n getReturnValue:&s]

while more_sels

… Meanwhile…

// ModelClass
-(Status)parseXYZ
{
 // do the same thing for each parse method
 return [self parse:XYZ]
}

-(Status)parseRST
{
 // do the same thing for each parse method
 return [self parse:RST]
}

————

I want to change model class to do this
// remove parse SELs  i.e. remove parse[A-Z]{3} methods

// replace with something like
-doesNotRespondToSelector:(SEL)aSel
{
 NSString *sel = NSStringFromSelector(aSel)

 if ( sel has prefix ‘parse’ )
 {
   NSString *selCmd = [sel subStringFromIndex:5] // remove prefix ‘parse’. e.g.  ‘RST’ remains
   Status s = [self parse:selCmd]

   // how do I return ‘s’ to the original caller??? The Controller

 }
 else
   [super doesNotRespondToSelector:aSel]
}


Sandor

On Jul 20, 2020, at 13:27, Alex Zavatone via groups.io <zav@...> wrote:

Would it be possible to put them in an array or dictionary as NSStrings and use performSelector on one of them if it is present within the array or dict?

On Jul 20, 2020, at 12:11 PM, Sandor Szatmari <admin.szatmari.net@...> wrote:

I have a certain group of selectors that all follow a strict naming convention,

-(Status)parseXYZ
-(Status)parseRST

All have one line that does the same thing and return a scalar (enum) as a result.
Each method would do something XYZ or RST and return their status

There are about 200 of these… it’s quite annoying to maintain and look at this boiler plate interface.  It should not be necessary, I hope.

I would like to abstract them using something like:
 -doesNotRecognizeSelector:
 or
 -forwardInvocation:
 or
 By some other means, which I cannot conceive of…

The issue I don’t understand is, how would I return the scalar to the original caller once one of the methods has received the program flow.  Neither of these methods return anything.

Is what I want to do possible?
If so, what should I be looking at?

Sandor


Re: Abstracting a group of commonly named Selectors

Sandor Szatmari
 

Alex,

On Jul 20, 2020, at 15:51, Alex Zavatone via groups.io <zav=mac.com@groups.io> wrote:

Well, I’m guessing here, but can’t you construct the method and use a perform selector, get the result and return it?
I am proposing letting the runtime handle the fact the object does not respond to the selector. So, under this assumption I am inside one of two methods:

-doesNotRespondToSelector: or -forwardInvocation:

Both of which have a void return. So I don’t see how I can return data to the original caller… I’m not sure there’s a way to do it, but was hoping to be unaware of how to use these methods, or an alternate means of generically responding to selectors.


It seems like you are making methods, performing them, then need to perform methods within that, right? Can’t you simplify that?
Unfortunately, this is code (The controller) at the heart of a framework and many classes depend on it. I can’t justify changing that code. But I can consider writing a new model (one with lot’s of redundant selectors) in an new way.

Thanks for your ideas!

Sandor

Or somehow define what becomes parsed in the method?

It seems like this can be simplified.

On Jul 20, 2020, at 2:47 PM, Sandor Szatmari <admin.szatmari.net@gmail.com> wrote:

Alex,

Thanks for the idea. I not sure how that helps me get the return value back to the original caller. Am I missing your point?

Here’s more details, in pseudo code

// Controller class
SEL aSel
do

aSel = lookUpSel (i.e. parseXYZ)
NSInvocation *n = // make invocation with aSel
[n setTarget: ModelClass]
[n invoke]
Status s = success // I’m an optimist
[n getReturnValue:&s]

while more_sels

… Meanwhile…

// ModelClass
-(Status)parseXYZ
{
// do the same thing for each parse method
return [self parse:XYZ]
}

-(Status)parseRST
{
// do the same thing for each parse method
return [self parse:RST]
}

————

I want to change model class to do this
// remove parse SELs i.e. remove parse[A-Z]{3} methods

// replace with something like
-doesNotRespondToSelector:(SEL)aSel
{
NSString *sel = NSStringFromSelector(aSel)

if ( sel has prefix ‘parse’ )
{
NSString *selCmd = [sel subStringFromIndex:5] // remove prefix ‘parse’. e.g. ‘RST’ remains
Status s = [self parse:selCmd]

// how do I return ‘s’ to the original caller??? The Controller

}
else
[super doesNotRespondToSelector:aSel]
}


Sandor

On Jul 20, 2020, at 13:27, Alex Zavatone via groups.io <zav=mac.com@groups.io> wrote:
Would it be possible to put them in an array or dictionary as NSStrings and use performSelector on one of them if it is present within the array or dict?

On Jul 20, 2020, at 12:11 PM, Sandor Szatmari <admin.szatmari.net@gmail.com> wrote:

I have a certain group of selectors that all follow a strict naming convention,

-(Status)parseXYZ
-(Status)parseRST

All have one line that does the same thing and return a scalar (enum) as a result.
Each method would do something XYZ or RST and return their status

There are about 200 of these… it’s quite annoying to maintain and look at this boiler plate interface. It should not be necessary, I hope.

I would like to abstract them using something like:
-doesNotRecognizeSelector:
or
-forwardInvocation:
or
By some other means, which I cannot conceive of…

The issue I don’t understand is, how would I return the scalar to the original caller once one of the methods has received the program flow. Neither of these methods return anything.

Is what I want to do possible?
If so, what should I be looking at?

Sandor







Re: Abstracting a group of commonly named Selectors

Gary L. Wade
 

You might want to consider using valueForUndefinedKey: and parse it to know which value you care about, and then return the value through NSValue, which can encapsulate any value, although a number/enum mapping might be sufficient.

On Jul 20, 2020, at 12:47 PM, Sandor Szatmari <admin.szatmari.net@...> wrote:

Alex,

Thanks for the idea.  I not sure how that helps me get the return value back to the original caller.   Am I missing your point?

Here’s more details, in pseudo code

// Controller class
SEL aSel
do

aSel = lookUpSel (i.e. parseXYZ)
NSInvocation *n = // make invocation with aSel
[n setTarget: ModelClass]
[n invoke]
Status s = success // I’m an optimist
[n getReturnValue:&s]

while more_sels

… Meanwhile…

// ModelClass
-(Status)parseXYZ
{
 // do the same thing for each parse method
 return [self parse:XYZ]
}

-(Status)parseRST
{
 // do the same thing for each parse method
 return [self parse:RST]
}

————

I want to change model class to do this
// remove parse SELs  i.e. remove parse[A-Z]{3} methods

// replace with something like
-doesNotRespondToSelector:(SEL)aSel
{
 NSString *sel = NSStringFromSelector(aSel)

 if ( sel has prefix ‘parse’ )
 {
   NSString *selCmd = [sel subStringFromIndex:5] // remove prefix ‘parse’. e.g.  ‘RST’ remains
   Status s = [self parse:selCmd]

   // how do I return ‘s’ to the original caller??? The Controller

 }
 else
   [super doesNotRespondToSelector:aSel]
}


Sandor

On Jul 20, 2020, at 13:27, Alex Zavatone via groups.io <zav@...> wrote:

Would it be possible to put them in an array or dictionary as NSStrings and use performSelector on one of them if it is present within the array or dict?

On Jul 20, 2020, at 12:11 PM, Sandor Szatmari <admin.szatmari.net@...> wrote:

I have a certain group of selectors that all follow a strict naming convention,

-(Status)parseXYZ
-(Status)parseRST

All have one line that does the same thing and return a scalar (enum) as a result.
Each method would do something XYZ or RST and return their status

There are about 200 of these… it’s quite annoying to maintain and look at this boiler plate interface.  It should not be necessary, I hope.

I would like to abstract them using something like:
 -doesNotRecognizeSelector:
 or
 -forwardInvocation:
 or
 By some other means, which I cannot conceive of…

The issue I don’t understand is, how would I return the scalar to the original caller once one of the methods has received the program flow.  Neither of these methods return anything.

Is what I want to do possible?
If so, what should I be looking at?

Sandor

141 - 160 of 1420