Is there a default method of NSObject to simulate something similar to Python's getattr / setattr?
I want to get and set the members of an instance having only the name of the member.
I want to accomplish something like this:
/* someUnknownInstance is defined elsewhere, and has an instance variable named "x", with an initial value of 5 */
...
id myInstance = someUnknownInstance; /* myInstance.x = 5 */
NSNumber myInstanceVariable = [myInstance getAttr:#"x"]; /* myInstanceVariable = 5 */
[myInstance setAttr:#"x" value:(myInstanceVariable + 1)]; /* myInstance.x = 6 */
The semi easy way..
Class _class_ = [myInstance class];
objc_property_t property = class_getProperty(_class_, "x");
// property is NULL when there is no such property
if (property) {
// because ObjC has internal members name scheme, leading '_' = "_x"
const char * publicname = property_getName(property);
// the public property name
NSString *publicKey = [NSString stringWithCString:publicname encoding:NSASCIIStringEncoding];
// getter
int i = [[myInstance valueForKey:publicKey] intValue];
// setter
[myInstance setValue:#(i+1) forKey:publicKey];
}
The crazy way.. lots of code for just adding + 1 to property "x".
#import <objc/runtime.h>
#import <string.h>
static void* getIvarPointer(id object, char const *propertyname) {
Ivar ivar = class_getInstanceVariable(object_getClass(object), propertyname);
if (!ivar) return 0;
return (uint8_t*)(__bridge void*)object + ivar_getOffset(ivar);
}
-(void)goCrazyWithObjcRuntime:(id)myInstance {
/// search class of id for specific property name and add 1
Class _class = [myInstance class];
objc_property_t property = class_getProperty(_class, "x");
if (property) {
Boolean isInt = false;
const char * attr = property_getAttributes(property);
// lets iterate the attributes of the property
char *attribs = strdup(attr);
char *pt = strtok(attribs,",");
while (pt != NULL) {
if (strcmp(pt, "N" ) == 0) printf("nonatomic ");
else if (strcmp(pt, "Ti") == 0) {
printf("integer ");
isInt = true;
}
else if (strcmp(pt, "R" ) == 0) printf("readonly ");
else if (strcmp(pt, "C" ) == 0) printf("copy ");
else printf("other property description:%s", pt);
pt = strtok(NULL, ",");
}
// the public propertyname
const char * name = property_getName(property);
NSLog(#"public propertyname:'%s' with attributes:'%s'",name, attr);
// transform public property name back to internal class member
char objcInternalPrefix[4] = "_";
char *backingPropertyName = strncat(objcInternalPrefix, name, strlen(name));
// getter, catch the Ivar
int *ptr = getIvarPointer(myInstance, backingPropertyName);
NSLog(#"x=%d",*ptr);
// setter, beware math without type check here
// Get a pointer to the instance variable. Returns NULL if not found.
int *intPtr = getIvarPointer(myInstance, backingPropertyName);
if (intPtr) {
// Now, add 1.
if (isInt) {
*intPtr = *intPtr + 1;
}
}
}
}
Related
I am trying to expose a C++ object by creating an Objective-C++ class to wrap it.
Ultimately, in Swift, I'm trying to write this:
print(JSApplication.eval("'TKTK'")?.toString() ?? "")
print(JSApplication.eval("x = {a: 42}; x")?.toString() ?? "")
print(JSApplication.eval("x")?.get("a")?.toString() ?? "nope");
print(JSApplication.eval("1 + 2")?.toInt32() ?? 0)
However, when I try to call JSApplication.eval("x")?.get("a")?.toString(), Objective-C calls dealloc on my class after .get("a") but before .toString().
Normally in dealloc I would call .reset() on the shared pointer that the class contains. But since the dealloc is firing too early, this would clear out my V8 result before .toString() could be called on it.
This raises a general question: How does Swift / Objective-C decide when to call dealloc on a temporary object? For something like foo()?.bar()?.baz(), where foo and bar return temporary objects, is it correct that both temp objects are receiving a dealloc message before baz is called? That's what I'm seeing.
If that's the correct behavior, then what's the proper way to extend the lifetime of a temporary object to the scope where the function is being called, like C++? Is that possible?
Here's my Objective-C++ binding. (I notice that no "Destroy 0x..." messages are being printed out at all, so C++ destructors don't seem to be firing. Am I supposed to call those manually?)
// Extracts a C string from a V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
}
#interface NJSValue (V8)
- (instancetype)init;
- (instancetype)initWithValue:(Local<Value>)value;
#end
struct NJSRef
{
std::shared_ptr<Nan::Persistent<Value>> _ref;
~NJSRef()
{
printf("Destroy 0x%08x\n", (unsigned int)(size_t)_ref.get());
}
};
#implementation NJSValue (V8)
NJSRef m;
- (instancetype)init
{
self = [super init];
return self;
}
- (instancetype)initWithValue:(Local<Value>)value
{
self = [super init];
Nan::HandleScope scope;
Nan::EscapableHandleScope escape;
m._ref.reset(new Nan::Persistent<Value>(escape.Escape(value)));
printf("Alloc 0x%08x\n", (unsigned int)(size_t)m._ref.get());
return self;
}
#end
#implementation NJSValue
- (void)dealloc
{
printf("Dealloc 0x%08x\n", (unsigned int)(size_t)m._ref.get());
//m_ref.reset();
}
- ( NSString * _Nonnull )toString
{
if (m._ref != nullptr) {
Nan::HandleScope scope;
Local<Value> value(Nan::New(*m._ref));
v8::String::Utf8Value str(JS_ISOLATE(), value);
const char* cstr = ToCString(str);
return NJSStringToNSString(JS_STR(cstr));
} else {
return #"undefined";
}
}
- (NSNumber *)toInt32
{
if (m._ref != nullptr) {
Nan::HandleScope scope;
Local<Value> value(Nan::New(*m._ref));
if (!value->IsInt32()) return nullptr;
return [NSNumber numberWithInt:TO_INT32(value)];
} else {
return nullptr;
}
}
- (NSNumber *)toNumber
{
if (m._ref != nullptr) {
Nan::HandleScope scope;
Local<Value> value(Nan::New(*m._ref));
if (!value->IsNumber()) return nullptr;
return [NSNumber numberWithDouble:TO_DOUBLE(value)];
} else {
return nullptr;
}
}
- (NJSValue * _Nullable __strong)get:(NSString * _Nonnull)key CF_RETURNS_RETAINED
{
if (m._ref == nullptr) return nullptr;
v8::HandleScope scope(JS_ISOLATE());
v8::EscapableHandleScope handle_scope(JS_ISOLATE());
Local<Value> value(Nan::New(*m._ref));
if (!value->IsObject()) return nullptr;
Local<Object> obj(JS_OBJ(value));
Local<Value> jsKey(JS_STR([key UTF8String]));
if (!obj->Has(JS_CONTEXT(), jsKey).FromJust()) return nullptr;
Local<Value> result(obj->Get(jsKey));
v8::String::Utf8Value str(JS_ISOLATE(), result);
const char* cstr = ToCString(str);
printf("got %s\n", cstr);
NJSValue* ret = [[NJSValue alloc] initWithValue:handle_scope.Escape(result)];
// [self associateValue:ret withKey:key];
return ret;
}
#end
#implementation JSApplication
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super init];
if (self) {
self.frame = frame;
}
return self;
}
// Executes a string within the current v8 context.
v8::Local<v8::Value>
ExecuteString(v8::Isolate* isolate, v8::Local<v8::String> source,
v8::Local<v8::Value> name, bool print_result,
bool report_exceptions) {
v8::EscapableHandleScope handle_scope(isolate);
v8::TryCatch try_catch(isolate);
v8::ScriptOrigin origin(name);
v8::Local<v8::Context> context(isolate->GetCurrentContext());
v8::Local<v8::Script> script;
if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) {
// Print errors that happened during compilation.
if (report_exceptions)
ReportException(isolate, &try_catch);
return handle_scope.Escape(v8::Undefined(isolate));
} else {
v8::Local<v8::Value> result;
if (!script->Run(context).ToLocal(&result)) {
assert(try_catch.HasCaught());
// Print errors that happened during execution.
if (report_exceptions)
ReportException(isolate, &try_catch);
return handle_scope.Escape(v8::Undefined(isolate));
} else {
assert(!try_catch.HasCaught());
if (print_result && !result->IsUndefined()) {
// If all went well and the result wasn't undefined then print
// the returned value.
v8::String::Utf8Value str(isolate, result);
const char* cstr = ToCString(str);
printf("eval result: %s\n", cstr);
}
return handle_scope.Escape(result);
}
}
}
void ReportException(v8::Isolate* isolate, v8::TryCatch* try_catch) {
v8::HandleScope handle_scope(isolate);
v8::String::Utf8Value exception(isolate, try_catch->Exception());
const char* exception_string = ToCString(exception);
v8::Local<v8::Message> message = try_catch->Message();
if (message.IsEmpty()) {
// V8 didn't provide any extra information about this error; just
// print the exception.
fprintf(stderr, "%s\n", exception_string);
} else {
// Print (filename):(line number): (message).
v8::String::Utf8Value filename(isolate,
message->GetScriptOrigin().ResourceName());
v8::Local<v8::Context> context(isolate->GetCurrentContext());
const char* filename_string = ToCString(filename);
int linenum = message->GetLineNumber(context).FromJust();
fprintf(stderr, "%s:%i: %s\n", filename_string, linenum, exception_string);
// Print line of source code.
v8::String::Utf8Value sourceline(
isolate, message->GetSourceLine(context).ToLocalChecked());
const char* sourceline_string = ToCString(sourceline);
fprintf(stderr, "%s\n", sourceline_string);
// Print wavy underline (GetUnderline is deprecated).
int start = message->GetStartColumn(context).FromJust();
for (int i = 0; i < start; i++) {
fprintf(stderr, " ");
}
int end = message->GetEndColumn(context).FromJust();
for (int i = start; i < end; i++) {
fprintf(stderr, "^");
}
fprintf(stderr, "\n");
v8::Local<v8::Value> stack_trace_string;
if (try_catch->StackTrace(context).ToLocal(&stack_trace_string) &&
stack_trace_string->IsString() &&
v8::Local<v8::String>::Cast(stack_trace_string)->Length() > 0) {
v8::String::Utf8Value stack_trace(isolate, stack_trace_string);
const char* stack_trace_string = ToCString(stack_trace);
fprintf(stderr, "%s\n", stack_trace_string);
}
}
}
+ (NJSValue *)Eval:(NSString *)string __attribute((ns_returns_retained))
{
Isolate* isolate = Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
v8::Context::Scope context_scope(context);
const char* str = [string UTF8String];
Local<Value> result = ExecuteString(
context->GetIsolate(),
v8::String::NewFromUtf8(context->GetIsolate(), str,
v8::NewStringType::kNormal).ToLocalChecked(),
JS_STR("JSApplication.Eval"), false, true);
return [[NJSValue alloc] initWithValue:result];
}
#end
Here's the output I get for the Swift code at the top of this question:
TKTK
Alloc 0x81d34230
Dealloc 0x81d34230
Alloc 0x81d34270
Dealloc 0x81d34270
TKTK
Alloc 0x81d38230
Dealloc 0x81d38230
[object Object]
Alloc 0x81d38250
got 42
Alloc 0x81d38240
Dealloc 0x81d38240
Dealloc 0x81d38240
42
Alloc 0x81d38250
Dealloc 0x81d38250
3
A big part of your problem is that your declaration of NJSRef m; is not declaring an instance variable, even though it's inside an #implementation. It's just a file-scope global. There's just one, and it's being shared (and clobbered) by all of your instances of NJSValue. You would have to enclose it in curly braces {...} to make it an instance variable.
That explains why it's never destroyed, at least. Probably a lot of the other symptoms, too, but it's hard to tell given the external types you're using that I'm not familiar with.
I want to know how the getter and setter for an Objective-C property are implemented as part of learning the concept of memory management.
I have not been able to find an actual representation other than "nonatomic" and "atomic".
What does the actual code look like for getters and setters with the different property attributes, such as strong/weak, copy/assign, and __unsafe_unretained?
You can check the source code at the objc4 github repo
getter: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-accessors.mm#L48
id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
setter: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-accessors.mm#L70
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
load weak variable: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/NSObject.mm#L444
id
objc_loadWeakRetained(id *location)
{
id result;
SideTable *table;
retry:
result = *location;
if (!result) return nil;
table = &SideTables()[result];
table->lock();
if (*location != result) {
table->unlock();
goto retry;
}
result = weak_read_no_lock(&table->weak_table, location);
table->unlock();
return result;
}
I don't think there is any code for unsafe_retained. Compiler can just simply assign the pointer without anything else.
I was wondering if it's possible to determine the class or primitive type of an Objects properties. Getting all properties names and values is pretty easy. SO answer
So is there any way to get the properties class type while the property hast no value or nil value?
Example Code
#interface MyObject : NSObject
#property (nonatomic, copy) NSString *aString;
#property (nonatomic, copy) NSDate *aDate;
#property NSInteger aPrimitive;
#end
#implementation MyObject
#synthesize aString;
#synthesize aDate;
#synthesize aPrimitive;
- (void)getTheTypesOfMyProperties {
unsigned int count;
objc_property_t* props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
// Here I can easy get the name or value
const char * name = property_getName(property);
// But is there any magic function that can tell me the type?
// the property can be nil at this time
Class cls = magicFunction(property);
}
free(props);
}
#end
After searching through Apples Documentation about objc Runtime and according to this SO answer I finally got it working. I just want to share my results.
unsigned int count;
objc_property_t* props = class_copyPropertyList([MyObject class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
const char * name = property_getName(property);
NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
const char * type = property_getAttributes(property);
NSString *attr = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
NSString * typeString = [NSString stringWithUTF8String:type];
NSArray * attributes = [typeString componentsSeparatedByString:#","];
NSString * typeAttribute = [attributes objectAtIndex:0];
NSString * propertyType = [typeAttribute substringFromIndex:1];
const char * rawPropertyType = [propertyType UTF8String];
if (strcmp(rawPropertyType, #encode(float)) == 0) {
//it's a float
} else if (strcmp(rawPropertyType, #encode(int)) == 0) {
//it's an int
} else if (strcmp(rawPropertyType, #encode(id)) == 0) {
//it's some sort of object
} else {
// According to Apples Documentation you can determine the corresponding encoding values
}
if ([typeAttribute hasPrefix:#"T#"]) {
NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)]; //turns #"NSDate" into NSDate
Class typeClass = NSClassFromString(typeClassName);
if (typeClass != nil) {
// Here is the corresponding class even for nil values
}
}
}
free(props);
Inspired by the ObjC answer by #arndt-bieberstein I have written a solution in Swift 3 (probably very similar - if not same - in earlier versions of Swift). You can find it on Github I am trying to make a pod of it but I am having issues getting pob lib lintto work with the Swift 3 code (CLI xcodebuild or Xcode 8 related problem probably.) Anyhow, the class method func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? can extract the name and types of any Swift class that inherits from NSObject.
The work horse of the project are these methods, but checkout the full code on Github:
func getTypesOfProperties(in clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}
func getTypeOf(property: objc_property_t) -> Any {
guard let attributesAsNSString: NSString = NSString(utf8String: property_getAttributes(property)) else { return Any.self }
let attributes = attributesAsNSString as String
let slices = attributes.components(separatedBy: "\"")
guard slices.count > 1 else { return getPrimitiveDataType(withAttributes: attributes) }
let objectClassName = slices[1]
let objectClass = NSClassFromString(objectClassName) as! NSObject.Type
return objectClass
}
func getPrimitiveDataType(withAttributes attributes: String) -> Any {
guard let letter = attributes.substring(from: 1, to: 2), let type = primitiveDataTypes[letter] else { return Any.self }
return type
}
func getNameOf(property: objc_property_t) -> String? {
guard let name: NSString = NSString(utf8String: property_getName(property)) else { return nil }
return name as String
}
It can extract the NSObject.Type of all properties which class type inherits from NSObject such as NSDate (Swift3: Date), NSString(Swift3: String?) and NSNumber, however it is store in the type Any (as you can see as the type of the value of the Dictionary returned by the method). This is due to the limitations of value types such as Int, Int32, Bool. Since those types do not inherit from NSObject, calling .self on e.g. an Int - Int.self does not return NSObject.Type, but rather the type Any. Thus the method returns Dictionary<String, Any>? and not Dictionary<String, NSObject.Type>?.
You can use this method like this:
class Book: NSObject {
let title: String
let author: String?
let numberOfPages: Int
let released: Date
let isPocket: Bool
init(title: String, author: String?, numberOfPages: Int, released: Date, isPocket: Bool) {
self.title = title
self.author = author
self.numberOfPages = numberOfPages
self.released = released
self.isPocket = isPocket
}
}
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
print("'\(name)' has type '\(type)'")
}
// Prints:
// 'title' has type 'NSString'
// 'numberOfPages' has type 'Int'
// 'author' has type 'NSString'
// 'released' has type 'NSDate'
// 'isPocket' has type 'Bool'
You can also try to cast the Any to NSObject.Type, which will succeed for all properties inheriting from NSObject, then you can check the type using standard == operator:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if let objectType = type as? NSObject.Type {
if objectType == NSDate.self {
print("Property named '\(name)' has type 'NSDate'")
} else if objectType == NSString.self {
print("Property named '\(name)' has type 'NSString'")
}
}
}
}
If you declare this custom == operator:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
You can then even check the type of value types like this:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if type == Int.self {
print("Property named '\(name)' has type 'Int'")
} else if type == Bool.self {
print("Property named '\(name)' has type 'Bool'")
}
}
}
LIMITATIONS
I have not yet been able to give this project support for when the value types are optionals. If you have declared a property in you NSObject subclass like this: var myOptionalInt: Int? my solution will not work, because the method class_copyPropertyList can't find those properties.
Does anyone have a solution for this?
I am trying to implement a tricky thing: all my model classes have an automatic NSCoding implementation of their properties. This let me add and remove properties to my classes and do not worry about missing stuff being not encoded/decoded. (Actually, it serves also other purposes, but that's the main idea).
When initializing my object from archive with initWithCoder, thanks to the Obj-C runtime, I go through the list of my properties, and try to assign directly the ivars to the values. I do not want to go through setters for various and imperious reasons, hence setValue:forKey: is forbidden.
Interesting problem, isn't it? I must say I am not full confident with C-pointers subtleties...
Here is the code:
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(class, &outCount);
for (unsigned int index = 0; index < outCount; index++) {
objc_property_t property = properties[index];
NSString *propertyNameString = [NSString stringWithUTF8String:property_getName(property)];
id value = [coder decodeObjectForKey:propertyNameString];
if (value == nil) {
continue;
}
const char *attributes = property_getAttributes(property);
NSString *typeAttribute = [[NSString stringWithUTF8String:attributes] substringWithRange:NSMakeRange(1, 1)];
const char *ivarName = [[#"_" stringByAppendingString:propertyNameString] UTF8String];
if ([typeAttribute isEqualToString:#"#"]) {
Ivar ivar = class_getInstanceVariable([self class], ivarName);
object_setIvar(self, ivar, value);
}
else if ([typeAttribute isEqualToString:#"d"]) {
double *doublePointer = getIvarPointer(self, ivarName);
*doublePointer = [value doubleValue];
}
else if ([typeAttribute isEqualToString:#"i"]) {
int *intPointer = getIvarPointer(self, ivarName);
*intPointer = [value intValue];
}
else if ([typeAttribute isEqualToString:#"c"]) {
char *charPointer = getIvarPointer(self, ivarName);
*charPointer = [value boolValue];
}
else if ([typeAttribute isEqualToString:#"Q"]) {
NSUInteger *uintegerPointer = getIvarPointer(self, ivarName);
*uintegerPointer = [value unsignedIntegerValue];
}
}
free(properties);
The 'getIvar' function looks like this (yes, I use class_getInstanceVariable on object class, since object_getInstanceVariable is not allowed with ARC...):
static void* getIvarPointer(id object, char const *name)
{
Ivar ivar = class_getInstanceVariable(object_getClass(object), name);
if (!ivar) return 0;
return (unsigned char *)(__bridge void *)object + ivar_getOffset(ivar);
}
When running Xcode's static analyzer, I get a warning saying "Dereference of null pointer (loaded from variable 'doublePointer')". Interestingly enough, a similar message appeared for int and NSUInteger, but seems to have disappear right now... It never appeared for 'char'.
Any idea, suggestion or insightful criticism would be very much appreciated.
I must say that, the code actually works. I do get automatic decoding of double and int in my object classes. But I want to understand why the static analyzer tells me such thing.
As your getIvarPointer() function can legibly return 0, you must handle this eventuality in each *ptr affectation I think.
For example:
if ([typeAttribute isEqualToString:#"d"]) {
double *doublePointer = getIvarPointer(self, ivarName);
if (doublePointer) {
*doublePointer = [value doubleValue];
} else {
// Handle this as you can ;)
}
}
Objective-C offers runtime reflections feature. I'm trying to find getter/setter selector name of a declared property. I know the basic rule like field/setField:. Anyway I think runtime reflection should offer a feature to resolve the name for complete abstraction, but I couldn't find the function.
How can I resolve the getter/setter method selector (not implementation) of a declared property with runtime reflection in Objective-C (actually Apple's Cocoa)
Or reverse query. (method selector → declared property)
I think you can get the selector names only if the property is declared with explicit (setter = XXX and/or getter = XXX)
So to get the getter and setter selector names for some property 'furType' of the class 'Cat':
objc_property_t prop = class_getProperty([Cat class], "furType");
char *setterName = property_copyAttributeValue(prop, "S");
if (setterName == NULL) { /*Assume standard setter*/ }
char *getterName = property_copyAttributeValue(prop, "G");
if (getterName == NULL) { /*Assume standard getter */ }
I don't know of a reverse query, other than iterating through all the properties and looking for matches. Hope that helps.
A little update from my NSObject category. Hope this'll help some one:
+(SEL)getterForPropertyWithName:(NSString*)name {
const char* propertyName = [name cStringUsingEncoding:NSASCIIStringEncoding];
objc_property_t prop = class_getProperty(self, propertyName);
const char *selectorName = property_copyAttributeValue(prop, "G");
if (selectorName == NULL) {
selectorName = [name cStringUsingEncoding:NSASCIIStringEncoding];
}
NSString* selectorString = [NSString stringWithCString:selectorName encoding:NSASCIIStringEncoding];
return NSSelectorFromString(selectorString);
}
+(SEL)setterForPropertyWithName:(NSString*)name {
const char* propertyName = [name cStringUsingEncoding:NSASCIIStringEncoding];
objc_property_t prop = class_getProperty(self, propertyName);
char *selectorName = property_copyAttributeValue(prop, "S");
NSString* selectorString;
if (selectorName == NULL) {
char firstChar = (char)toupper(propertyName[0]);
NSString* capitalLetter = [NSString stringWithFormat:#"%c", firstChar];
NSString* reminder = [NSString stringWithCString: propertyName+1
encoding: NSASCIIStringEncoding];
selectorString = [#[#"set", capitalLetter, reminder, #":"] componentsJoinedByString:#""];
} else {
selectorString = [NSString stringWithCString:selectorName encoding:NSASCIIStringEncoding];
}
return NSSelectorFromString(selectorString);
}