Too few arguments to function call, expected at least 2, have 0 - objective-c

I wrote the following code:
#interface TestClass : NSObject
//
-(void)testLog;
//
+(void)testLog;
#end
//===============================
SEL sel = #selector(testLog);
IMP imp = class_getMethodImplementation([TestClass class], sel);
imp();
//===============================
SEL testSel = #selector(testLog);
IMP testImp = class_getMethodImplementation(objc_getMetaClass(class_getName([TestClass class])), testSel);
testImp();
I set the Enable Strict Checking of objc_msgSend Calls to NO but still have this error。
Why is it wrong?

All Methods take a minimum of two arguments: a reference to the object (or class in the case of a class method) the method is being called on, this is the value of self within the method body; and the selector of the method. This is what is missing in your calls.
You can see this in definition of IMP given in the documentation is:
id (*IMP)(id, SEL, ...)
With the explanation:
This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.
HTH

Related

How to invoke an Objective-C block obtained at runtime?

I'm trying to write a mock of HKHealthStore. In the stubbed executeQuery: I need to call the result handler block of a HKSampleQuery instance passed to it. The block is private so I need to get it at runtime. This is what I have so far:
- (void)executeQuery:(HKQuery *)query {
NSAssert([query isKindOfClass:HKSampleQuery.class], #"Mock executeQuery: not implemented yet for other query types than HKSampleQuery");
HKSampleQuery *sampleQuery = (HKSampleQuery *)query;
NSMutableArray<HKObject *> *queryResults = [NSMutableArray new];
for (HKObject *o in self.storedObjects) {
if ([sampleQuery.predicate evaluateWithObject:o]) {
[queryResults addObject:o];
}
}
SEL selector = NSSelectorFromString(#"resultHandler");
Method m = class_getInstanceMethod(HKSampleQuery.class, selector);
IMP imp = method_getImplementation(m);
typedef void(*resultHandler_t)(id, SEL, void(^)(HKQuery*, NSArray*, NSError*));
resultHandler_t f = (resultHandler_t)imp;
// here, I need to invoke the result handler block with sampleQuery, queryResults and nil as arguments
}
Note the selector name is "resultHandler" even though the parameter of the initializer of HKSampleQuery is called "resultsHandler".
Is there any way to invoke the block with appropriate arguments?
You're not doing what you think you are doing. You are getting the implementation of the method resultHandler (the getter method of the property resultHandler). The block you want is the value of the property resultHandler, which is the return value of running the getter method. In other words, you need to run the getter and get the result, not get the getter itself.
Simplest way to call the method and get the return value (since in this case the return value is a regular object pointer type) would be
typedef void (^resultHandler_t)(HKSampleQuery *query, NSArray *results, NSError *error);
resultHandler_t f = [sampleQuery performSelector:#selector(resultHandler)];
f(sampleQuery, queryResults, nil);
Alternately, if you declare (but not implement) the instance method or property resultHandler in a dummy category of HKSampleQuery, you can then access the property directly like resultHandler_t f = sampleQuery.resultHandler;

Copy a method IMP for multiple method swizzles

I have a class set up that ideally will read the methods of any class passed in and then map all of them to on single selector at runtime before forwarding them off to their original selector.
This does work right now, but I can only do it for one method at a time. The issue seems to be that once I swizzle the first method, my IMP to catch and forward the method has now been swapped with that other methods IMP. Any further attempts at this screw up because they use newly swapped IMP to replace the others.
1)So I have MethodA, MethodB, and CustomCatchAllMethod.
2)I swap MethodA with CustomCatchAllMEthod. MethodA->CustomCatchAllMethod, CustomCatchAllMethod->MethodA
3)Now I try to swap to MethodB with CustomCatchAllMethod as well, but since CustomCatchAllMethod now = MethodA, MethodB becomes MethodA and MethodA->MethodB.
So how do I get/copy a new instance of my IMP for each new selector I want to intercept?
Here's a rough mockup of the above flow:
void swizzle(Class classImCopying, SEL orig){
SEL new = #selector(catchAll:);
Method origMethod = class_getInstanceMethod(classImCopying, orig);
Method newMethod = class_getInstanceMethod(catchAllClass,new);
method_exchangeImplementations(origMethod, newMethod);
}
//In some method elsewhere
//I want this to make both methodA and methodB point to catchAll:
swizzle(someClass, #selector(methodA:));
swizzle(someClass, #selector(methodB:));
That common method-swizzling pattern only works when you want to intercept one method with one other. In your case you are basically moving the implementation for catchAll: around instead of inserting it everywhere.
To properly to this you'd have to use:
IMP imp = method_getImplementation(newMethod);
method_setImplementation(origMethod, imp);
This leaves you with one problem though: how to forward to the original implementation?
That is what the original pattern used exchangeImplementations for.
In your case you could:
keep a table of the original IMPs around or
rename the original methods with some common prefix, so you can build a call to them from catchAll:
Note that you can only handle methods of the same arity when you want to forward everything through the same method.
You can capture original IMP with block, get block's IMP and set it as implementation of method.
Method method = class_getInstanceMethod(class, setterSelector);
SEL selector = method_getName(method);
IMP originalImp = method_getImplementation(method);
id(^block)(id self, id arg) = ^id(id self, id arg) {
return ((id(*)(id, SEL, id))originalImp)(self, selector, arg);
};
IMP newImp = imp_implementationWithBlock(block);
method_setImplementation(method, newImp);

Is my understanding of 'self' correct?

I'll provide a simple method and then explain how I see it, if this is incorrect, please let me know and correct me. I feel like I understand 'self' but still doubt my self.
-(NSString *)giveBack {
NSString *string = [NSString stringWithFormat:#"Hi there!"];
return string;
}
-(IBAction)displayIt {
NSString *object = [self giveBack];
[myView setText:object];
}
the "myView" is a UITextView object.
Now as for the 'self'..
I'm basically saying in my -displayIt method that I'm creating a NSString object called 'object' and storing within it a method that returns a string which says "Hi there".
And this method (named 'giveBack') is performed ON the name of my class (whatever I named the project). Is this correct?
No, you are not creating an object called object and then storing a method within it etc. You are creating a variable which can hold a reference to an object and storing within it a reference to an object obtained by calling a method.
[Note: The following assumes you are using automatic memory management (ARC or garbage collection), no mention will be made of reference counts. If you are using manual memoery there is more to consider...]
Adding line numbers to your sample:
1. -(NSString *)giveBack
{
2. NSString *string = [NSString stringWithFormat:#"Hi there!"];
3. return string;
}
4. -(IBAction)displayIt
{
5. NSString *object = [self giveBack];
6. [myView setText:object];
}
Declares giveBack as an instance method of the class, to be invoked it must be called on a particular instance.
The RHS ([NSString stringWithFormat:#"Hi there!"]) calls a class method which creates an object of type NSString and returns a reference, of type NSString *, to that object. The LHS declares a variable (string) which can hold a reference to an NSString object. The assignment (=) stores the reference returned by the RHS into the variable declared by the LHS.
Return the value in string as the result of the method
Declare an instance method called displayIt
RHS: call an instance method (giveBack) on the object instance self - self is a reference to the current object instance when within an instance method (in this case displayIt). LHS: declare a variable, object of type NSString *. Assignment: store the reference to an NSString returned by the method call on the RHS into the variable declared on the LHS.
Call the instance method setText: on the object instance referenced by the variable myView passing it the reference to an NSString found in variable object.
I think, you are generally correct.
But in below mention:
And this method (named 'giveBack') is performed ON the name of my class (whatever I named the project)
I can't understand your meaning.
A class name is just a symbol (that is text for human readers).
Methods of an Objective-C class are indicated by - notation in the beginning of method declaration.
In other words, all method declarations start with - within #implementation CLASS_NAME ... #end block are instance method of CLASS_NAME class.
When we call another instance methods (within a instance method) we use self keyword. Because all Objective C method call must designate target object and, in this case, we are calling ourselves (current CLASS_NAME instance itself). So we use self keyword.
Sorry for my confusing words.. It's harder to explain I thought :-(
you're storing the string returned by 'giveBack', not the method itself. the method is part of the class. 'self' is the instance of the object that you're calling 'giveBack' (and 'displayIt' for that matter) on.

Calling methods on objects of type id

when I'm trying to call a method on an object of type id, i get a warning raised (method not found). Of course not, but isn't that the sense of an id object?
Is there a way to tell the compiler:
"You don't now the class of the object on which i am calling this method, but don't worry, i'm sure it does implement it!" ?
You could use performSelector?
And if you've got an object type id it's probably a good idea to use respondsToSelector as well :)
i.e.
if ([myObject respondsToSelector:#selector(dosomething:)])
myObject performSelector:#selector(doSomething:) withObject:#"hello"];
Yes, just use a variable of the proper class type. You can either perform an explicit cast when sending the message, or you can assign it to a variable of the correct type. In Objective-C, the type id can be implicitly cast to any Objective-C object pointer type:
id myId = ...;
// Option 1: Use a cast when sending the message
[(MyClass *)myId someClassMethod];
// Option 2: Assign to a variable
MyClass *myObj = miId; // Implicit cast in the assignment
[myObj someClassMethod];
You could cast your id object to the class you know that it is.
If you have an id instance named instanceA and you know that it is of ClassA you cast it accordingly
Class A *instanceACasted = (ClassA *)instanceA;
then call the method
[instanceACasted methodCall];

Why doesn’t my enum work as a method parameter?

I've used typedef enum in the past with method parameters and had no problem, but today I'm having problems...
h file
typedef enum
{ eGetVarious1,
eGetVarious2,
} eGetVarious;
- (double)getVarious:(eGetVarious)eVar:(NSDate*)pDate;
an m file
you're calling a class method, and declaring an instance method:
instance method:
- (double)getVarious:(eGetVarious)eVar:(NSDate*)pDate;
class method (may not use ivars or instance methods):
+ (double)getVarious:(eGetVarious)eVar:(NSDate*)pDate;
say you want this as an instance method, declare it like this:
- (double)getVarious:(eGetVarious)eVar forDate:(NSDate*)pDate;
and if you were in the scope of implementation of an instance method, then this should work:
double result = [self getVarious:eGetVarious1 forDate:[NSDate date]];
note the reason compiler is reporting an error:
if it has not seen a particular objc selector and you use it, it assumes the undeclared selector's arguments take id (anonymous objc object).
also, enum type should not be promoted to a pointer (although 0 is ok). since the compiler saw no way to match what you're calling: [objc_class* getVarious:eGetVarious<enum_type> :NSDate*] it is right, because you should be calling it as:
General * anInstanceOfGeneral = /* something here */;
NSDate * date = /* something here */;
double result = [anInstanceOfGeneral getVarious:eGetVarious1 forDate:date];