I'm trying to get a Method at runtime and then use its data structure to call it's implementation. Just to clarify, this is for learning purposes, not for any practical reason. So here is my code.
#import <Foundation/Foundation.h>
#import <stdio.h>
#import <objc/runtime.h>
#interface Test : NSObject
-(void)method;
#end
#implementation Test
-(void)method {
puts("this is a method");
}
#end
int main(int argc, char *argv[]) {
struct objc_method *t = (struct objc_method*) class_getInstanceMethod([Test class], #selector(method));
Test *ztest = [Test new];
(t->method_imp)(ztest, t->method_name);
[ztest release];
return 0;
}
The definition of struct objc_method is as follows (defined in objc/runtime.h)
typedef struct objc_method *Method;
....
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
however when I try to compile my code, I get this error.
error: dereferencing pointer to incomplete type
But when I add this to my code (to explicitly declare an objc_method), it works just as expected.
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
typedef struct objc_method* Method;
Could someone explain to me why my code works when I explicitly declare this structure, and not when I import it from objc/runtime.h? Does it have anything to do with OBJC2_UNAVAILABLE? I can't find a definition for that, but it is defined in my environment.
EDIT:
I ran gcc -E code.m -o out.m to see what OBJC2_UNAVAILABLE was getting replaced with, it turns out that OBJC2_UNAVAILABLE was defined as __attribute__((unavailable)) in my environment. Can someone explain what that means and why Method still works if this structure is "unavailable"?
I just took a good look at the objc runtime header and found what the problem is, and how to fix it :)
So if you look at the area of the file that contains the transparent definition of the structure, you'll see that it's in the body of:
#if !__OBJC2__
...
#endif
Since this IS defined, it means that the structure that we refer to is in fact forward-declared and opaque (and thus incomplete).
What we must do instead is use the functions provided for accessing these members:
IMP method_getImplementation(Method method);
SEL method_getName(Method method);
void method_getReturnType(Method method, char *dst, size_t dst_len);
Etc.
You can see the full list of accessor methods and plenty of other goodies in the Runtime Reference Guide.
Hope it helps!
Its fields were previously defined, but it's an opaque type in ObjC-2. Use the runtime instead, and do not define the fields yourself:
int main(int argc, char *argv[]) {
Method t = class_getInstanceMethod([Test class], #selector(method));
Test * ztest = [Test new];
IMP imp = method_getImplementation(t);
typedef void (*fn)(id,SEL);
fn f = (fn)imp;
f(ztest,#selector(method));
[ztest release];
return 0;
}
Related
Below is rather basic objective-c code. It contains one instance variable i. The problem is whenever I attempt to assign to it, it leads to segfault almost immediately following that assignment operation. Note: It does not segfault at the point of assignment. Usually it segfaults after the assignment and I try to invoke a method. ALSO, notice that this is not your standard mac/apple objective-c code. I'm using cygwin (Linux) plain vanilla version of objective-c. So I have to generate my own alloc and dealloc methods.
#import <objc/runtime.h>
#import <objc/Object.h>
#interface Test
{
int i;
}
+(id)alloc;
- (id) init;
- (int) load;
#end
#implementation Test
+(id)alloc
{
self = class_createInstance(self,0);
return self;
}
-(id)init
{
i = 0; // <------ if I comment out this line, there is no segfault
return self;
}
-(int) load
{
return i;
}
#end
int main(int argc, const char * argv[])
{
Test * test = [[Test alloc] init];
int v = [test load]; //segfaults here (NOTE: if I comment out this line, it does not segfault)
return 0;
}
What is causing the segfault?
I expect that, for a root class like yours, you need to explicitly declare the isa instance variable. Without it, your i is being interpreted as though it were the isa, which is why you get a crash.
So:
#interface Test
{
Class isa;
int i;
}
If you didn't intend to create a root class, you should probably inherit from Object or the like.
#interface Soka : NSObject
#property (nonatomic, copy) NSString * name;
-(void)speak;
#end
#implementation Soka
-(void)speak{
NSLog(#"my name is: %#", self.name);
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString * asdfasdfasdfsdf = #"xxxxxxx4";
id cls = [Soka class];
void * obj = &cls;
[(__bridge id)obj speak];
}
return 0;
}
The output is :
[14402:5468392] my name is: xxxxxxx4
Can anyone explain this??
How was the asdfasdfasdfsdf values set to class property?
Thanks
Thanks for all your guys' replies:
Making An Update:
#Leo
Thanks for your reply.
1# What is the meaning of Soka Class object cls, what is its memory structure. It's pointer pointing to what address?
2# find memory near the ocObj object? what if the nearest memory is an integer value? It's a part of OBJC or whatelse theory?
Well it is a little tricky,not useful. It is about Objective C instance memory
Your code can be converted to this
NSString * asdfasdfasdfsdf = #"xxxxxxx4";
Class cls = [Soka class];
void * obj = &cls;
id ocObj = (__bridge id)obj;
NSLog(#"%#",[ocObj name]);
Lets make it clear one by one
cls is a Soka Class object
Class cls = [Soka class];
obj is a pointer point to Soka Class object
void * obj = &cls;
This convert obj to Object C object
id ocObj = (__bridge id)obj;
When call this [ocObj name],Objective C will find memory near the ocObj object.It is #"xxxxxxx4" in the stack.
Also,I test this to make sure I am right
#interface Soka : NSObject
#property (nonatomic, copy) NSString * name;
#property (nonatomic,copy) NSString * address;
-(void)speak;
#end
#implementation Soka
-(void)speak{
NSLog(#"my name is: %#", self.name);
}
#end
int main(int argc, char * argv[]) {
#autoreleasepool {
NSString * asdfasdfasdfsdf = #"xxxxxxx4";
NSString * address = #"address";
Class cls = [Soka class];
void * obj = &cls;
id ocObj = (__bridge id)obj;
NSLog(#"%#",[ocObj name]);
NSLog(#"%#",[ocObj address]);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
This will log
2015-07-01 23:26:56.857 OCTest[10910:347815] address
2015-07-01 23:26:56.859 OCTest[10910:347815] xxxxxxx4
Update:
+class function will return the class object of one class and type is Class.
So,what is Class type,it is something about objective c runtime
typedef struct objc_class *Class;
This is the define of objc_class
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
When you create an Objective C object,it will auto manage the memory. In your code,it is just a trick to make an Objective C object like memory.
Actually objective-c's classes is structures with properties.
For your's class Soka you have hidden fields in that structure: _name and class. They placed in same order, as placed your's variables asdfasdfasdfsdf and cls.
All works fine because at address &cls placed variable of type Class, that describes class. So after casting obj to id runtime find required field, that describes class and trust that that pointer points at Soka. Of course it'f false, but by circumstances that variables placed at same order and at right places this code work and doesn't crush.
You can't alloc objective-c objects in stack and should avoid this code if you think that you can alloc objective-c object in such way. Because it can lead to unpredicted errors.
The class method gives you the type of an object.
For example, if you want to know if myObject is an NSString, you could do this:
[myObject isKindOfClass:[NSString class]]
isKindOfClass takes a type as its argument, so we call class on NSString to get the type.
It's like typeof in the C languages.
I have a C (Objective-C) structure defined:
struct ResultadoVentaPUP{
NSString *autenticadoPorPin1;
NSString *autenticadoPorPin2;
NSString *tipoPago;
NSString *importe;
};
Then I declare a variable of this type globally (at top of the file):
ResultadoVentaPUP resven;
In a function I set values for this structure, for example:
resven.importe=#"12.45";
but when I try to view the content of "importe" in another function from the same file), ir returns (null).
NSLog(#"Result: %#",resven.importe);
What am I doing wrong? should I define the struct with 'static'?
Thank you!
Storing Obj-C objects in a C structure is a rather bad idea nowadays anyway, with ARC (Automatic Reference Counting), it is not even allowed any longer (the compiler will complain if you do that). Why not using an object instead? If you don't want to use assessor methods because you fear the overhead, just use an object with public ivars. Public ivars are bad IMHO, yet a struct is pretty much the same as an object with public ivars.
#interface ResultadoVentaPUP : NSObject
{
#public
NSString * autenticadoPorPin1;
NSString * autenticadoPorPin2;
NSString * tipoPago;
NSString * importe;
}
#end
#implementation ResultadoVentaPUP
#end
ResultadoVentaPUP * resven;
void someFunction () {
resven = [[ResultadoVentaPUP alloc] init];
resven->importe = #"12.45";
}
void someOtherFunction () {
NSLog(#"Result: %#",resven->importe);
}
This code will also work nicely if you use ARC and sooner or later every project should migrate to ARC in the near future (as soon as it can drop support for OSX/iOS versions without ARC support).
Maybe your declaration should be struct ResultadoVentaPUP resven;. This works for me:
#import <Foundation/Foundation.h>
struct ResultadoVentaPUP{
NSString *autenticadoPorPin1;
NSString *autenticadoPorPin2;
NSString *tipoPago;
NSString *importe;
};
struct ResultadoVentaPUP resven;
void func1() {
resven.importe = #"12.45";
}
void func2() {
NSLog(#"Result: %#", resven.importe);
}
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
func1();
func2();
[p release];
}
And I would be remiss if I didn't include the caveat that any time you are using global variables you should seriously be reconsidering your design.
I'm really confused with this problem. What I need to do is use some obj-c runtime feature in my project. Here is simple code in my .m file:
#import "Base.h"
#import <objc/runtime.h>
#implementation Base
- (void)someMethod {
NSUInteger numberMethods = 0;
Method *classMethods = class_copyMethodList([self class], &numberMethods);
for (int i = 0; i < numberMethods; i ++) {
classMethods[i]->method_name; //incomplete definition of type "struct objc_method"
}
#end
I got the following error: incomplete definition of type "struct objc_method". After some inspecting objc/runtime.h file I found something like this:
some code...
typedef struct objc_method *Method;
...some code...
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
Is this something like forward declaration problem, or something else?
In addition to Martin answer, you should use functions like method_getName to retrieve the name of the method classMethods[i].
This is much more portable (especially these fields in the structure no longer exists since Objective-C 2.0 as the macro suggests) and will avoid problems like the one you have when the Runtime evolves.
These members cannot be accessed!
OBJC2_UNAVAILABLE macro indicates that a member is not available and only provides some insight into the structure.
I am just learning how to use ScriptingBridges. I made a method that slowly fades the volume on iTunes, and would like to make it a category so I can do the following:
iTunesApplication* iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
[iTunes lowerVolume:50 speed:1];
I made another category for NSSpeechSynthesizer that works, but I can't get this one to. I keep getting the following build error:
"_OBJC_CLASS_$_iTunesApplication", referenced from:
l_OBJC_$_CATEGORY_iTunesApplication_$_iTunesApplicationAdditions in iTunesApplication.o
objc-class-ref-to-iTunesApplication in iTunesApplication.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
Is there something special I can do to make it work since I can't include the symbols?
Thanks,
Ryan Pendleton
UPDATE:
I only found one solution, which is below. It involves MethodSwizzling, so I'm open to better answers, but for now it's all I have.
The solution I found was to use the Objective-C runtime API. I'm sure there's a better way to organize this, but here's how I did it:
Here are my .h and .m files for creating the category. Notice how lowerVolume is not an actual method, but a C function with the arguments id self, and SEL _CMD. You'll also notice a setupCategories function. We'll call that later.
// iTunes+Volume.h
#import <objc/runtime.h>
#import "iTunes.h"
void lowerVolume(id self, SEL _cmd, int dest, float speed);
void setupCategories();
#interface iTunesApplication (Volume)
- (void)lowerVolume:(int)dest speed:(float)speed;
#end
// iTunes+Volume.m
#import "iTunes+Volume.h"
void lowerVolume(id self, SEL _cmd, int dest, float speed)
{
NSLog(#"Lower Volume: %i, %f", dest, speed);
}
void setupCategories()
{
id object = [[SBApplication alloc] initWithBundleIdentifier:#"com.apple.iTunes"];
Class class = [object class];
[object release];
class_addMethod(class, #selector(lowerVolume:speed:), (IMP)lowerVolume, "#:if");
}
Now that I've made the functions, I need to actually add them to the scripting bridge class using the Objective-C runtime API. I'll do this in main.m to make sure that the methods are ready to be used when the run loop starts.
// main.m
#import <Cocoa/Cocoa.h>
#import "iTunes+Volume.h"
int main(int argc, char *argv[])
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
setupCategories();
return NSApplicationMain(argc, (const char **) argv);
[pool drain];
}
Now, I can use my method wherever I want as long as I include the header files:
- (void)mute
{
iTunesApplication* iTunes = [[SBApplication alloc] initWithBundleIdentifier:#"com.apple.iTunes"];
[iTunes lowerVolume:0 speed:1];
[iTunes release];
}
If any of this doesn't make sense, just tell me and I'll try to explain it better.
I think you need to include -framework ScriptingBridge to your gcc arguments. That got it to compile for me!
As noted above, you can't easily do a category on iTunesApplication because it doesn't exist at compile time, and also because the runtime class name is ITunesApplication (capital "I").
The best solution I've found is to do your category on the class that DOES exist, SBApplication. Here's the code I tested that works and does what the original example was trying to do:
// SBApplication+Extensions.h
#import ScriptingBridge;
#interface SBApplication (Extensions)
- (void)lowerVolume:(int)dest speed:(float)speed;
#end
// SBApplication+Extensions.m
#import "iTunes.h"
#import "SBApplication+Extensions.h"
#implementation SBApplication (Extensions)
- (void)lowerVolume:(int)dest speed:(float)speed
{
NSLog(#"Lower Volume: %i, %f", dest, speed);
}
#end
// Caller, say in AppDelegate
#import "SBApplication+Extensions.h"
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
iTunesApplication *iTunesApp =
[SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
[iTunesApp lowerVolume:4 speed:3.3f];
}