i have known runtime gives the message a chance to be executed when If the implementation of the method is not found in the class or in its superclasses. It will start by sending the message + (BOOL)resolveInstanceMethod:(SEL)name to the class of the object: this method allows you to add the method at runtime to the class: if this message returns YES, it means it can redispatch the message.i try to return NO from + (BOOL)resolveInstanceMethod:(SEL)name, i thought that will make -(id)forwardingTargetForSelector:(SEL)aSelector be invoked(and dynamicMethodIMP will not be invoked), but still dynamicMethodIMP is invoked as same as the return value is YES. the Apple Doc said
If the method returns NO, the Objective-C runtime will pass control to the method forwarding mechanism(https://developer.apple.com/library/mac/releasenotes/Foundation/RN-FoundationOlderNotes/)
what is the different between return YES and return No from
+ (BOOL)resolveInstanceMethod:(SEL)name.
a piece of sample code like this :
`void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(#"%# has added", NSStringFromSelector(_cmd));
}
+(BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == #selector(mustHas)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v#:");
return NO;
}
return [super resolveInstanceMethod:sel];
}`
[obj mustHas];
The docs you're reading are from the 10.5 release notes, but that specific statement is not part of the current documentation.
The implementation of class_resolveInstanceMethod() actually ignores your return value. It just checks if you've implemented +resolveInstanceMethod, calls it if you have, and then it looks up the original selector (just to cache the result). The only time your return value matters is when debugging the runtime.
There's no need to guess at how message forwarding works. It's all open source. Here's the function that calls +resolveInstanceMethod (runtime/objc-class.mm):
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
BOOL resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
Related
#import <objc/runtime.h>
#implementation Foo
+ (void)test
{
[Foo performSelector:#selector(foo)];
[Foo performSelector:#selector(foo) withObject:#"works"];
[Foo performSelector:#selector(foo1)];
[Foo performSelector:#selector(foo2)];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(#"resolveClassMethod: %#", NSStringFromSelector(sel));
if (sel == #selector(foo)) {
class_addMethod(objc_getMetaClass("Foo"), sel, (IMP)fooIMP_withoutArgument, "s#####");
return YES;
} else if (sel == #selector(foo1)) {
class_addMethod(objc_getMetaClass("Foo"), sel, (IMP)fooIMP_with1Argument, "Works");
return YES;
} else if (sel == #selector(foo2)) {
class_addMethod(objc_getMetaClass("Foo"), sel, (IMP)fooIMP_with2Arguments, "Not Works");
return YES;
}
return [super resolveClassMethod:sel];
}
void fooIMP_withoutArgument(id self, SEL _cmd)
{
NSLog(#"fooIMP_withoutArgument");
}
void fooIMP_with1Argument(id self, SEL _cmd, id arg)
{
NSLog(#"fooIMP_with1Argument");
}
void fooIMP_with2Arguments(id self, SEL _cmd, id arg1, id arg2)
{
NSLog(#"fooIMP_with2Argument");
}
#end
Output is:
2018-02-27 16:41:31.638693+0800 Test[74840:2194481] resolveInstanceMethod: foo
2018-02-27 16:41:31.639201+0800 Test[74840:2194481] fooIMP_withoutArgument
2018-02-27 16:41:31.639228+0800 Test[74840:2194481] fooIMP_withoutArgument
2018-02-27 16:41:31.639490+0800 Test[74840:2194481] resolveInstanceMethod: foo1
2018-02-27 16:41:31.639519+0800 Test[74840:2194481] fooIMP_with1Argument
2018-02-27 16:41:31.639548+0800 Test[74840:2194481] resolveInstanceMethod: foo2
(lldb)
class_addMethod function does not check argument types?
Is the selector just a key point to the implementation without other role?
Why IMP with 1 argument work, but 2 arguments not?
How it works when call a method with params (or not) but IMP without params (or has)
Can someone help me explain the reason?
class_addMethod function does not check argument types?
It does not. Being a dynamic-typed language, Objective-C are all essentially just id at runtime—the type checking for objects occurs at compile-time, and that information is not preserved in the binary. class_addMethod is a low-level runtime function, and as such it does not include any type checking. This is okay, since these runtime functions are meant to be very rarely used, and only in cases where you know exactly what you're doing, and therefore, what types to expect.
SEL is just a key point to IMP without other role?
SEL is a selector, which in the typical implementations of Objective-C is a string, under the hood. objc_msgSend resolves this selector to an IMP, which is just a function whose body contains the method's implementation.
Why IMP with 1 argument work, but 2 arguments not?
Since you don't show us the signature of foo2, nor describe what's exactly going wrong, it's difficult to say. But since it appears that you don't try to call foo2 with any arguments, it's not too surprising that the effect would end up being something other than what you want.
Also, you're not returning YES after you add the methods, but calling super's implementation of resolveInstanceMethod:. If that method happens to return NO, the runtime is going to think that adding the method didn't work. You should return YES instead after successfully adding a method. (And in the case where you don't, you should call super's implementation of resolveClassMethod: rather than resolveInstanceMethod:.)
No, there is generally no argument type checking during run-time.
A selector is just the name for a method. It is represented by just a constant C string. If you add a method to a class the selector acts like a key in a dictionary with implementations as values.
Adding methods with arbitrary number of arguments work fine. Your example might fail, because the types parameter is wrong, and the argument number in the selector doesn't match. Fix these issues first.
I have a very specific issue where, if I have a swift class which inherits from an objective-c class, and it has a dynamic property.
Now, on the +initialize, I am injecting getter and setters into the swift(:NSObject) class, and these work no problem, except for a slight issue when setting values from an init overload.
So, my swift class looks like this:
class TestClass: BaseClass {
dynamic var testStringProperty: String?
init(initialValue: String) {
super.init()
self.testStringProperty = initialValue;
// does not trigger the get/set methods
// a po self.testStringProperty will output 'nil'
let xyz = self.testStringProperty;
// xyz is actually set to the value of initialValue, but it does not trigger the getter.
}
}
And the objective-c class that bases the swift is as follows:
static id storedVar;
#implementation BaseClass
+ (void)initialize {
// we are going to exchange getter/setter methods for a property on a SwiftClass, crude but demonstrates the point
if(![[self description] isEqualToString:#"BaseClass"]) {
IMP reader = (IMP)getPropertyIMP;
IMP writer = (IMP)setPropertyIMP;
const char* type = "#";
NSString* propertyName = #"testStringProperty";
IMP oldMethod = class_replaceMethod([self class], NSSelectorFromString(propertyName), reader, [[NSString stringWithFormat:#"%s#:", type] UTF8String]);
NSString* setMethod = [NSString stringWithFormat:#"set%#%#:", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]];
oldMethod = class_replaceMethod([self class], NSSelectorFromString(setMethod), writer, [[NSString stringWithFormat:#"v#:%s",type] UTF8String]);
}
}
static id getPropertyIMP(id self, SEL _cmd) {
return storedVar;
}
static void setPropertyIMP(id self, SEL _cmd, id aValue) {
storedVar = aValue;
}
#end
Long story short, whilst in the call to init(initialValue: String), the getters and setters are not triggered, but immediately after the call to init has completed they work.
This is despite the call to initialize completing successfully and the methods being replaced.
But outside of the init function, the get/set behave as expected.
Here is where it get's called.
let test = TestClass(initialValue: "hello");
test.testStringProperty = "hello"
A po test.testStringProperty after the creation of the object, will output nil. But the subsequent assignment, triggers all the correct methods.
It only fails when assigning within the init. Everywhere else it works like a charm.
I would like to get this to work within the initializer if possible, i'm not sure if there is another way to work around it.
Here is a link to the sample app that replicates the issue:
https://www.dropbox.com/s/5jymj581yps799d/swiftTest.zip?dl=0
I am going around blocks and try to discover the ways that they can be used.
So I am wondering is it possible to pass block to block like parameter?
Here is some sample code:
//declaration
static id (^someBlock)(id) = ^(id someClass) {
// do some stuff to obtain class some class instance
// check if class instance respond to #selector
// if yes - perform selector
}
//usage
+ (instancetype)someMethod {
someBlock(SomeClass.class);
// do additional work and return some instance type
}
This works fine, but is not good enough, because we obligate caller to respond to selector if caller want to do some additional stuff when someBlock is completed.
So my question is how I can invoke someBlock block with parameter block which I want to be executed when someBlock is completed.
Some like:
//declaration
static id (^someBlock)(id, <b>^otherBlock</b>) = ^(id someClass, <b>????</b>) {
// do some stuff to obtain class some class instance
otherBlock();
}
Any advice?
PS: Please note that the question is not about passing block to method as parameter.
Thanks,
Venelin
Is this what you are looking for?
static id (^someBlock)(id, void (^otherBlock)()) = ^id (id someClass, void (^otherBlock)()) {
otherBlock();
return nil; // just because you declares a `id` return type
};
And call it like
someBlock(someClass, ^() {
NSLog(#"other stuff");
});
My question is probably very simple to answer.
I have a custom written designated initializer that gets in a BOOL parameter.
In it, I want to check whether there was a BOOL passed or something else.
If something else, I want to raise an exception.
I also want to override the default init and point it to my designated intializer instead of calling super, and pass a nil in there so that the user gets the proper exception when he is not using the designated initializer.
-(id)init
{
return [self initWithFlag:nil];
}
-(id)initWithFlag:(BOOL)flag
{
//get the super self bla bla
if (flag IS-NOT-A-BOOL)
{
//raising exception here
}
//store the flag
return self;
}
What should be in place of IS-NOT-A-BOOL?
BOOL in objective c can result in either YES or NO, and everything will be casted to one of those values. How about using an NSNumber containing a bool value? Like:
-(id)initWithFlag:(NSNumber *)flag
{
//get the super self bla bla
if (!flag) // Check whether not nil
{
//raising exception here
[NSException raise:#"You must pass a flag" format:#"flag is invalid"];
}
//store the flag
BOOL flagValue = [flag boolValue];
return self;
}
In this case you could call the method like this
[self initWithFlag:#YES]; // or #NO, anyway, it won't throw an exception
or this
[self initWithFlag:nil]; // it will throw an exception
I using Xcode 4.6 for calculator application and I am getting three errors, can you shed a light what might causing them?
Here are the 3 errors
property 'pushElement' not find on object type "CalculatorBrain",
property 'enter pressed' not found on object type CalculatorviewController
property 'perform operation' not find on object type "CalculatorBrain"
This is part of the code that i am getting error in it which CalculatorviewController.m
- (IBAction)enterPressed:(UIButton*)sender {
[_Brain.pushElement :self.display.text]; ......first error ....
userIntheMiddleOfEnteringText= NO;
}
- (IBAction)operationPressed:(UIButton *)sender {
if (userIntheMiddleOfEnteringText)
self.enterPressed; ........second error.....
else {
double result = [_Brain.performOperation]; ... third error...
self.display.text=[NSString stringWithFormat:#"%g",result];
}
}
and the CalculatorBrain.m code is
#import "CalculatorBrain.h"
#implementation CalculatorBrain
-(void)pushElement:(double)operand {
NSNumber *operandObject=[NSNumber numberWithDouble:operand];
[self.stack addObject:operandObject];
}
-(double) popElement {
NSNumber *popedNumber=[self.stack lastObject];
if (popedNumber)
{
[_stack removeLastObject];
}
return [popedNumber doubleValue];
}
-(double)performOperation:(NSString*)operation {
double result = 0;
if( [operation isEqualToString: #"+"]){
result =self. popElement + self.popElement;
}
else if ([operation isEqualToString: #"*"]){
result=self.popElement*self.popElement;
}
else if ([operation isEqualToString:#"-" ]) {
double operand=self.popElement;
result=self.popElement - operand ;
}
else if ([operation isEqualToString:#"/"]) {
double divisor =self.popElement;
result= self.popElement/ divisor;
}
return result;
}
#end
All three errors are simple errors that indicate that you don't yet understand the basics of Objective-C. You need to learn the language first. Learn how to call methods and pass parameters.
The first error:
[_Brain.pushElement :self.display.text];
This should be:
[_Brain pushElement:self.display.text];
Here you want to call the pushElement: method on the _Brain object.
The second error:
self.enterPressed;
That indicates that you are trying to accessing a property named enterPressed on self. But there is no such property. There is a method named enterPressed: and this takes a UIButton as an argument. So you need to call it like this:
[self enterPress:sender];
The third error:
double result = [_Brain.performOperation];
Here you want to call the performOperation method but there isn't one. There is a method named performOperation:. This takes a parameter of type NSString. It must be called like this:
double result = [_Brain performOperation:#"some operation"];
The missing piece here is the operation you wish to pass. There is no obvious indication of where that comes from. Perhaps the button title.