ok without using NSInvocation, let's say I have this code:
...
array = [NSMutableArray arrayWithObjects:#"Yoda", #"Jedi", #"Darth Vader", #"Darth Vader", #"Darth Vader" , #"Darth Vader", nil];
SEL removeObjectMessage = #selector(removeObject:inRange:);
//does array allow us to remove and object in a range? if so let's do this
if ([array respondsToSelector:removeObjectMessage]){
NSRange darthVaderRange=NSMakeRange(2, 3);
[array removeObject:#"Darth Vader"inRange:darthVaderRange];
}
how would I perform that last line in the form of the SEL removeObjectMessage? I would have to put a wrapper around the range? I just want to see the syntax of how all that mess would look...
You could do:
if ([array respondsToSelector:removeObjectMessage]){
NSRange darthVaderRange=NSMakeRange(2, 3);
objc_msgSend(array, removeObjectMessage, #"Darth Vader", darthVaderRange);
}
Though that seems pretty fragile...
Unfortunately, if you're passing an argument that's not of type id, you have to use an NSInvocation object. (Otherwise, you could use performSelector:withObject:withObject:.)
There's no existing method that allows you to pass a selector and non-object arguments and get a valid method call. If it were a method that took only object arguments, you would do [array performSelector:removeObjectMessage withObject:#"Darth Vader" withObject:someHypotheticalRangeObject].
But to do it with an NSRange, you would have to either use NSInvocation (which you've said you don't want to do) or create a category on NSObject and use the low-level Objective-C runtime functions to define a method that takes a selector, an object argument and a non-object argument and calls the appropriate method.
Related
I have two NSResponder methods (cut, copy) and they have basically the same code except they call their own super. How to create method with parameter _CMD as selector that calls super and I won't end up with recursion?
- (void)copy:(id)sender
{
[self notifyAndPerformSelector:_cmd withObject:sender];
}
- (void)cut:(id)sender
{
[self notifyAndPerformSelector:_cmd withObject:sender];
}
- (void)notifyAndPerformSelector:(SEL)selector withObject:(id)sender
{
[super performSelector:selector withObject:sender];
//code...
}
As you have discovered your code doesn't call the superclass method as you want but the one in the current class, resulting in infinite recursion.
Your first option faced with this is to refactor your code, something along the lines of:
#implementation MyDerivedClass
{
- (void)copy:(id)sender
{
[super copy:sender];
[self commonCodeAfterSelector:_cmd withObject:sender];
}
- (void)cut:(id)sender
{
[super cut:sender];
[self commonCodeAfterSelector:_cmd withObject:sender];
}
- (void)notifyAndPerformSelector:(SEL)selector withObject:(id)sender
{
//code...
}
}
If this approach suits your situation use it. If not...
A second option is to become the compiler...
Standard and super method calls
A standard method call of the form:
[object someMethodWithArg1:x andArg2:y]
invokes a search for the method someMethodWithArg1:andArg2:. This search starts at the runtime class of object. The emphasis on runtime is important, the actual object referenced by object could be of the same class as the declared type of object or and of the subclasses of that type and the search must find the most derived implementation of the method.
A super method call of the form:
[super someMethodWithArg1:x andArg2:y]
also invokes a search for the method someMethodWithArg1:andArg2:. However in this search starts at the compile time class of superclass of the class in which code occurs. For example if MyDerivedClass above is a subclass of MyBaseClass then the search for the method starts at MyBaseClass ignoring the runtime type of self – which could be MyDerivedClass or a subclass of it (say MyDerivedDerivedClass)
Why does your current code recurse?
Your call:
[super performSelector:selector withObject:sender];
starts the search for the method performSelector:withObject: in the superclass, that search won't find the method until it reaches the NSObject class. Once found the method is invoked and starts a standard (not super) search for the method for selector, this search starts at the runtime type of self and so finds the method in MyDerivedClass... recursion.
What you need is something like:
[self performSuperSelector:selector withObject:sender];
but unfortunately that does not exist. But you can make one...
Compiling method calls
The compiler takes a standard method call of the form:
[object someMethodWithArg1:x andArg2:y]
and effectively (we're glossing over a few details, the need ones will get filled in below) compiles this to a call to the runtime function objc_msgSend():
objc_msgSend(object, #selector("someMethodWithArg1:andArg2:"), x, y)
Notice that the selector is passed as a SEL value, this is where the value for _cmd comes from.
A super call of the form:
[super someMethodWithArg1:x andArg2:y]
is effectively compiled to a call to objc_msgSendSuper() of the form:
objc_msgSendSuper(`struct` containing `self` and superclass,
#selector("someMethodWithArg1:andArg2:"), x, y)
You can call these runtime functions directly in your own code. You must import the <objc/objc-runtime.h> to obtain the definitions, cast them to the appropriate type, etc.
Becoming the compiler and bypassing performSelector
Your code uses performSelector as it has a SEL value, but as shown above the runtime calls used for method calling take a SEL directly. If you "compile" the super call yourself you do not need to use performSelector, which in turn avoids the recursion problem.
Before calling objc_msgSendSuper() the function needs to be cast so its return and argument types match the actual return and argument types of the selector you are calling. This is so that the correct code is compiled to handle the arguments and return value, and that code is dependent on the types. The two selectors you are calling, copy: and cut:, have the same type which makes the code shorter. To make the casting easier we first define a shorthand for the type:
typedef void (*CutOrCopyRunner)(struct objc_super *super, SEL op, id sender);
which defines CurOrCopyRunner as a function pointer type. Now your method:
- (void)notifyAndPerformSelector:(SEL)selector withObject:(id)sender
{
// "compile" [super selector:sender];
// first cast objc_msgSendSuper to the correct type by
// casting a function pointer to it (a function name by
// itself, e.g. objc_msgSendSuper, evaluates to a pointer
// to the function)
CutOrCopyRunner msgSender = (CutOrCopyRunner)objc_msgSendSuper;
// now build the first argument struct
struct objc_super superInfo;
superInfo.receiver = self;
superInfo.super_class = MyDerivedClass.class.superclass;
// now execute the super call
msgSender(&superInfo, selector, sender);
// code...
}
HTH
I have the following NSMutableArray:
(A|B|C|D|E|255,
F|G|H|I|J|122,
K|L|M|N|O|555)
I am trying to sort the objects in the array using the last component (255, 122, 555). Right now I have the following code:
[myArray sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
As expected, this method sorts the array by the first element (A, F, K).
I also read about NSSortDescriptor, for example:
NSSortDescriptor *sort = [[[NSSortDescriptor alloc] initWithKey:#"dateModified" ascending:YES] autorelease];
If I use it, it is not clear what I put as a parameter in initWithKey.
You can use a sort descriptor, which takes the last object of the "inner" arrays and sort by that.
Since sort descriptors use key-value coding (KVC), you need to be aware that arrays respond to valueForKey: in a special way - they pass a normal key on to each of the objects that they contain.
You also need to know that methods which do not take a parameter and return a value can be accessed through KVC is if they were normal properties.
All this adds up to the following:
Each of the objects contained in your array (i.e., the inner arrays) have a key that you want to sort by: lastObject
But since the objects are instances of NSArray they will normally pass the key on to the objects that they contain - which is not what you want.
You therefore need to use a special escape in the key name for that situation, which is #, making the actual key to use #lastObject
So to make a long story short, you can do what you want in this way:
NSMutableArray *array = ... // Your array
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey: #"#lastObject"
ascending: YES
selector: #selector(localizedCaseInsensitiveCompare:)];
[array sortUsingDescriptors: #[sd]];
You'll notice the "#" in the key name, within the string.
This escape character also works for other collection classes, for instance if you want to access allKeys from a dictionary through KVC, the key you should use is #allKeys.
I would use sortUsingComparator:
[myArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
return [obj1.lastPropertyForComparison compare:obj2.lastPropertyForComparison];
}
This method allows you to manually compare the properties or members of the objects that you want to order by. I use it almost exclusively for complex sorts, and I haven't noticed any performance differences.
UPDATE:
If your NSMutableArray contains NSArrays with the last object being the number you're trying to rank by, your comparator would be as follows:
[(NSNumber *)[obj1 lastObject] compare:(NSNumber *)[obj2 lastObject]]
Basically, you are grabbing the last object out of each NSArray, which you know is an NSNumber. You use the compare function to return NSOrderedAscending, NSOrderedDescending, or NSOrderedSame. I hope that helps.
I'm often required to retrieve the 1st object belonging to a Set. (Using that object as a representative of that set.)
I envision a Collection Object operator, akin to the
#unionOfObjects
BUT clearly
#firstObject
Is it possible to create such a Collection operator!
Currently there's no way to define custom collection operators. However, due to some internal magic there is a funny solution:
NSSet *testSet = [NSSet setWithArray:#[#"one", #(1)]];
id object = [testSet valueForKey:#"#anyObject"];
NSLog(#"anyObject (%#): %#", NSStringFromClass([object class]), object);
UPD: Forgot to mention another handy trick: you can use #lastObject on NSArray!
I want to sort this array of NSStrings:
"Page_1",
"Page_10",
"Page_11",
"Page_12",
"Page_13",
"Page_14",
"Page_15",
"Page_16",
"Page_17",
"Page_18",
"Page_19",
"Page_2",
"Page_20",
"Page_21",
"Page_22",
"Page_23",
"Page_24",
"Page_3",
"Page_4",
"Page_5",
"Page_6",
"Page_7",
"Page_8",
"Page_9"
but I keep getting an error with this code:
NSArray* sortedArray = [currentViews sortedArrayUsingSelector:#selector(compare:NSNumericSearch)];
Not even sure if this is the correct approach to take, looking at the NSArray docs makes me think I should be going with a comparator. Any help would be appreciated.
You will indeed need a comparator, as what you are trying to sort is really a NSString, not a NSNumber.
You cannot fix an argument within an #selector - #selector(compare:NSNumericSearch) is looking for a method compare:NSNumericSearch and that does not exist!
You can use a comparator to invoke the correct compare method:
NSArray *sortedArray = [currentViews sortedArrayUsingComparator:(NSComparator)^(NSString *a, NSString *b)
{
return [a compare:b options:NSNumericSearch];
}
];
I need this operation bsc method receive id and i like to using dot syntax to set object later.
Currently i do by this way. But maybe somebody know more elegant way?
-(NSError *) updateObject:(id)object operation:(NSInteger)operation;
{
CurrentCompany *obj1 = nil;
...
CompanyStuff *obj2 = nil;
if ([[[(CurrentCompany *)object entity] name] isEqualToString:#"CurrentCompany"]) obj1 = (CurrentCompany *)object;
if ([[[(CompanyStuff *)object entity] name] isEqualToString:#"CompanyStuff"]) obj2 = (CompanyStuff *)object;
NSLog(#"UpdatedObject:%#",obj1);
If these classes conform to a common protocol or inherit from a common superclass that declares the properties, you can just statically type the variable as that protocol or superclass. If neither of these are the case, it doesn't seem like they should be treated interchangeably anyway.
Also, this isn't really related, but the explicit cast from id to the specific class is pointless. You can just assign.