how to simulate passing parameter with selector

  • Hi,

    I am trying to work around the fact that you cannot pass parameter
    values with a selector. Here's what I'm trying to do.

    I have a generic table (essentially a class with an array instance
    variable) filled with generic records (also a class containing an
    array instance variable). I want to be able to sort the table based
    on one of the fields in the generic record. Since the generic table
    holds an array of records, my instinct is to use sortUsingSelector:
    (SEL) s. The problem, of course, is that to set up compare methods, I
    need to know which field I'm sorting on, and that is only determined
    at runtime.

    If you COULD send a parameter, the method call would look something
    like:

    - (void) sortTableOnField: (int) f {
    ....
    [table records] sortUsingSelector: @selector(compareForFieldIndex: i)}

    with the selector somehow transferring the index "i" (the array index
    representing a particular field) so that comparisons can be made.

    But (correct me if I'm wrong) you can't send arguments. And since
    there could be tons of fields, writing out separate compare methods
    (compareFieldOne, compareFieldTwo....) seems dumb.

    Is there a way around this?

    Thanks,

    Daniel
  • On Feb 13, 2008 8:51 PM, Daniel Child <wchild...> wrote:

    > Is there a way around this?

    Use an NSSortDescriptor (you can specify the sort key, the sort
    direction and the comparison selector separately).

    Best wishes,
    Hamish
  • On Feb 13, 2008, at 2:51 PM, Daniel Child wrote:

    > Hi,
    >
    > I am trying to work around the fact that you cannot pass parameter
    > values with a selector.

    Uh, well, there are plenty of places where you _can_ pass parameter
    values with a selector.  For example,
    performSelector:withObject:withObject:.

    > Here's what I'm trying to do.
    >
    > I have a generic table (essentially a class with an array instance
    > variable) filled with generic records (also a class containing an
    > array instance variable). I want to be able to sort the table based
    > on one of the fields in the generic record. Since the generic table
    > holds an array of records, my instinct is to use sortUsingSelector:
    > (SEL) s. The problem, of course, is that to set up compare methods,
    > I need to know which field I'm sorting on, and that is only
    > determined at runtime.
    >
    > If you COULD send a parameter, the method call would look something
    > like:
    >
    > - (void) sortTableOnField: (int) f {
    > ....
    > [table records] sortUsingSelector: @selector(compareForFieldIndex: i)}
    >
    > with the selector somehow transferring the index "i" (the array
    > index representing a particular field) so that comparisons can be
    > made.
    >
    > But (correct me if I'm wrong) you can't send arguments. And since
    > there could be tons of fields, writing out separate compare methods
    > (compareFieldOne, compareFieldTwo....) seems dumb.
    >
    > Is there a way around this?

    You want to use sort descriptors, which let you specify the key of the
    elements on which to sort.

    Alternatively, you can use sortUsingFunction:context:, where you pass
    the context of your choice and use it however you like within your
    function.

    -Ken
  • I'm sorry, but looking at the documentation I don't see how using
    NSSortDescriptor works. Their example uses keys for the instances of
    a custom class (employee age / hire date, etc.). All I have is a
    generic class. The structure of a GenericRecord is

    @interface GenericRecord : NSObject
    {
      NSMutableArray *record;
    int numFields;
    }

    and the fields are inside of the record ivar. My GenericTable class
    contains an instance variable "records" (an array) that holds a
    collection of these generic records. As far as I can tell, KVC
    doesn't apply. TI am not trying to sort the records by "numFields" or
    by "record" (for which I could use KVC). I want to sort them
    according to field i within the record.

      My comparison method should be something like this:

    - (NSComparisonResult) compareRecord: (GenericRecord *) gr byField:
    (int) fieldNumber;

    By definition, this kind of method has arguments and cannot go be
    passed as a selector. (OR CAN IT?)

    As they say at the end of the chapter, "Each object in the collection
    must be key-value coding-compliant for the property key used to
    create the sort descriptor ." And it needs to use a compare: method
    which, if specified, also employs a selector. I'm left with the same
    problem I had before: no way to compare on a particular index of the
    array.... If I've missed something, please let me know. Thanks.

    On Feb 13, 2008, at 5:12 PM, Hamish Allan wrote:

    > On Feb 13, 2008 8:51 PM, Daniel Child <wchild...> wrote:
    >
    >> Is there a way around this?
    >
    > Use an NSSortDescriptor (you can specify the sort key, the sort
    > direction and the comparison selector separately).
    >
    > Best wishes,
    > Hamish
  • On Feb 13, 2008, at 12:51 PM, Daniel Child wrote:

    > Hi,
    >
    > I am trying to work around the fact that you cannot pass parameter
    > values with a selector. Here's what I'm trying to do.
    >
    > I have a generic table (essentially a class with an array instance
    > variable) filled with generic records (also a class containing an
    > array instance variable). I want to be able to sort the table based
    > on one of the fields in the generic record. Since the generic table
    > holds an array of records, my instinct is to use sortUsingSelector:
    > (SEL) s. The problem, of course, is that to set up compare methods,
    > I need to know which field I'm sorting on, and that is only
    > determined at runtime.

    The NSSortDescriptor approach is fine.  Another one is to use
    sortedArrayUsingFunction:context: which allows you to pass a context
    pointer which is received by the comparing function.

    -Peter
  • How about just using sortUsingFunction:context: and pass your field
    index in <context>. That simply gets passed uninterpreted to your
    compare function which can then use that index to pull the field from
    the record.

    If you need more context just bundle it into a struct.

    ------
    S.O.S.

    On 14/02/2008, at 12:36 PM, Daniel Child wrote:

    > I'm sorry, but looking at the documentation I don't see how using
    > NSSortDescriptor works. Their example uses keys for the instances
    > of a custom class (employee age / hire date, etc.). All I have is a
    > generic class. The structure of a GenericRecord is
    >
    > @interface GenericRecord : NSObject
    > {
    > NSMutableArray *record;
    > int numFields;
    > }
    >
    > and the fields are inside of the record ivar. My GenericTable class
    > contains an instance variable "records" (an array) that holds a
    > collection of these generic records. As far as I can tell, KVC
    > doesn't apply. TI am not trying to sort the records by "numFields"
    > or by "record" (for which I could use KVC). I want to sort them
    > according to field i within the record.
    >
    > My comparison method should be something like this:
    >
    > - (NSComparisonResult) compareRecord: (GenericRecord *) gr byField:
    > (int) fieldNumber;
    >
    > By definition, this kind of method has arguments and cannot go be
    > passed as a selector. (OR CAN IT?)
    >
    > As they say at the end of the chapter, "Each object in the
    > collection must be key-value coding-compliant for the property key
    > used to
    > create the sort descriptor ." And it needs to use a compare: method
    > which, if specified, also employs a selector. I'm left with the
    > same problem I had before: no way to compare on a particular index
    > of the array.... If I've missed something, please let me know. Thanks.
    >
    >
    > On Feb 13, 2008, at 5:12 PM, Hamish Allan wrote:
    >
    >> On Feb 13, 2008 8:51 PM, Daniel Child <wchild...> wrote:
    >>
    >>> Is there a way around this?
    >>
    >> Use an NSSortDescriptor (you can specify the sort key, the sort
    >> direction and the comparison selector separately).
    >>
    >> Best wishes,
    >> Hamish

  • On Feb 13, 2008, at 8:36 PM, Daniel Child wrote:

    > I'm sorry, but looking at the documentation I don't see how using
    > NSSortDescriptor works.

    I agree I don't think NSSortDescriptor would be useful in this case,
    since you want to sort on an array index rather than on a selector.
    However several people have also mentioned the
    sortedArrayUsingFunction:context: method of NSArray, which will allow
    you to do what you want. You'd use it something like this:

    NSArray *arrayOfArrays = [create array of arrays];
    int column = 2;  // sort on column 2

    NSInteger compare(id a, id b, void *context) {
      int column = *((int*)context);

      compare [a objectAtIndex:column] and [b objectAtIndex:column]
    }

    NSArray *sorted = [arrayOfArrays sortedArrayUsingFunction:compare
    context:&column];
  • Thanks. Others suggested this and that's what I ended up doing. I
    think it would be helpful if Cocoa included some function that did
    allow you to pass parameters through the sort functions, however. My
    case can't be unique.

    On Feb 14, 2008, at 2:07 AM, Adam P Jenkins wrote:

    >
    > On Feb 13, 2008, at 8:36 PM, Daniel Child wrote:
    >
    >> I'm sorry, but looking at the documentation I don't see how using
    >> NSSortDescriptor works.
    >
    > I agree I don't think NSSortDescriptor would be useful in this
    > case, since you want to sort on an array index rather than on a
    > selector.  However several people have also mentioned the
    > sortedArrayUsingFunction:context: method of NSArray, which will
    > allow you to do what you want. You'd use it something like this:
    >
    > NSArray *arrayOfArrays = [create array of arrays];
    > int column = 2;  // sort on column 2
    >
    > NSInteger compare(id a, id b, void *context) {
    > int column = *((int*)context);
    >
    > compare [a objectAtIndex:column] and [b objectAtIndex:column]
    > }
    >
    > NSArray *sorted = [arrayOfArrays sortedArrayUsingFunction:compare
    > context:&column];
    >
  • Cocoa does include sortedArrayUsingFunction:context:, which seems to
    be exactly what you want, or as close as you can get in Objective-
    C.    Your original post suggested @selector(compareForFieldIndex: i),
    which is a good idea but that would require extending the Objective-C
    language.  Ruby or Python, or other languages which support closures,
    support this kind of thing effortlessly.  For example in python I'd
    just write:

    column = 2 # column to sort on
    b = sorted(arrayOfArrays,
                        lambda a, b:  compare(a[column] , b[column]))

      In ObjC you'd need to either

    1. Have an array sort method which takes as an argument an
    NSInvocation object, which can encapsulate an arbitrary method
    invocation plus its arguments
    2. or have sort accept some kind of comparator object like Java's
    Collections.sort.  The comparator would have a method which does the
    same thing as the function passed to
    sortedArrayUsingFunction:context:, but since it's an object it can
    also contain other state, such as the column index to sort on in your
    case.

    Both of the above solutions end up being quite verbose in ObjC
    compared to just using sortedArrayUsingFunction:context:.  Creating
    NSInvocation objects is quite a verbose process, and since you can't
    create anonymous classes in ObjC, solution 2 becomes a lot more
    verbose in ObjC than it would be in Java also.

    On Feb 14, 2008, at 9:41 AM, Daniel Child wrote:

    > Thanks. Others suggested this and that's what I ended up doing. I
    > think it would be helpful if Cocoa included some function that did
    > allow you to pass parameters through the sort functions, however. My
    > case can't be unique.
    >
    > On Feb 14, 2008, at 2:07 AM, Adam P Jenkins wrote:
    >
    >>
    >> On Feb 13, 2008, at 8:36 PM, Daniel Child wrote:
    >>
    >>> I'm sorry, but looking at the documentation I don't see how using
    >>> NSSortDescriptor works.
    >>
    >> I agree I don't think NSSortDescriptor would be useful in this
    >> case, since you want to sort on an array index rather than on a
    >> selector.  However several people have also mentioned the
    >> sortedArrayUsingFunction:context: method of NSArray, which will
    >> allow you to do what you want. You'd use it something like this:
    >>
    >> NSArray *arrayOfArrays = [create array of arrays];
    >> int column = 2;  // sort on column 2
    >>
    >> NSInteger compare(id a, id b, void *context) {
    >> int column = *((int*)context);
    >>
    >> compare [a objectAtIndex:column] and [b objectAtIndex:column]
    >> }
    >>
    >> NSArray *sorted = [arrayOfArrays sortedArrayUsingFunction:compare
    >> context:&column];
    >>
    >