I want to create an Objective-C base class that performs an operation on all properties (of varying types) at runtime. Since the names and types of the properties will not always be known, how can I do something like this?
#implementation SomeBaseClass
- (NSString *)checkAllProperties
{
for (property in properties) {
// Perform a check on the property
}
}
EDIT: This would be particularly useful in a custom - (NSString *)description: override.
To expand on mvds' answer (started writing this before I saw his), here's a little sample program that uses the Objective-C runtime API to loop through and print information about each property in a class:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface TestClass : NSObject
#property (nonatomic, retain) NSString *firstName;
#property (nonatomic, retain) NSString *lastName;
#property (nonatomic) NSInteger *age;
#end
#implementation TestClass
#synthesize firstName;
#synthesize lastName;
#synthesize age;
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
unsigned int numberOfProperties = 0;
objc_property_t *propertyArray = class_copyPropertyList([TestClass class], &numberOfProperties);
for (NSUInteger i = 0; i < numberOfProperties; i++)
{
objc_property_t property = propertyArray[i];
NSString *name = [[NSString alloc] initWithUTF8String:property_getName(property)];
NSString *attributesString = [[NSString alloc] initWithUTF8String:property_getAttributes(property)];
NSLog(#"Property %# attributes: %#", name, attributesString);
}
free(propertyArray);
}
}
Output:
Property age attributes: T^q,Vage
Property lastName attributes: T#"NSString",&,N,VlastName
Property firstName attributes: T#"NSString",&,N,VfirstName
Note that this program needs to be compiled with ARC turned on.
Use
objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
and read https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html on how to do this exactly.
Some code to get you going:
#import <objc/runtime.h>
unsigned int count=0;
objc_property_t *props = class_copyPropertyList([self class],&count);
for ( int i=0;i<count;i++ )
{
const char *name = property_getName(props[i]);
NSLog(#"property %d: %s",i,name);
}
Adding some detial to #mvds:
unsigned int count=0;
objc_property_t *props = class_copyPropertyList([self class],&count);
for ( int i=0;i<count;i++ )
{
const char *name = property_getName(props[i]);
NSString* dataToGet = [NSString swf:#"%s",name];
#try { // in case of this pair not key value coding-compliant
id value = [barButton valueForKey:dataToGet];
NSLog(#"prop %d: %s %#",i,name, value);
} #catch (NSException *exception) {
// NSLog(#"Exception:%#",exception);
}
#finally {
// Display Alternative
}
}
Please give #mvds the upvote.
Related
I want to override my object's description method to be able to print the values of all my declared properties. I know that I can do this by appending all the values to each other, one by one, but sometimes this is time consuming if you have a lot of properties.
I was wondering if there's an easy way to do this, by getting help from the runtime powers of Objective-C?
Did you try this solution ?
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface NSObject (PropertyListing)
// aps suffix to avoid namespace collsion
// ...for Andrew Paul Sardone
- (NSDictionary *)properties_aps;
#end
#implementation NSObject (PropertyListing)
- (NSDictionary *)properties_aps {
NSMutableDictionary *props = [NSMutableDictionary dictionary];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [[[NSString alloc] initWithCString:property_getName(property)] autorelease];
id propertyValue = [self valueForKey:(NSString *)propertyName];
if (propertyValue) [props setObject:propertyValue forKey:propertyName];
}
free(properties);
return props;
}
#end
I can't seem to get round this error; use of undeclared identifier 'ageBy'.
I dont understand why am getting it as i have the import Person.h in my code.
Thanks for your time and any help.
Person.h
#interface Person : NSObject
{
int _age;
int _years;
NSString *_name;
NSString *_job;
}
-(void)setAge:(int)age;
-(int)age;
-(void)setName:(NSString *)name;
-(NSString *)name;
-(void)setJob:(NSString *)job;
-(NSString *)job;
-(NSString *)summaryString;
-(void)ageBy:(int)years;
#end
Person.m
#import "Person.h"
#implementation Person
-(void)setAge:(int)age{
_age = age;
}
-(int)age{
return _age;
}
-(void)setName:(NSString *)name{
_name = name;
}
-(NSString *)name{
return _name; }
-(void)setJob:(NSString *)job{
_job = job;
}
-(NSString *)job{
return _job;
}
-(NSString *)summaryString{
return [NSString stringWithFormat:#"The Person %# is %d years old and is a %#",_name,_age,_job];
-(void)ageBy:(int)years{
_years = years;
_age = years + _age;
}
}
#end
Your ageBy: is defined inside summaryString. You probably want to move the curly bracket just before #end so that it is above -(void)ageBy:(int)years. So:
-(NSString *)summaryString{
return [NSString stringWithFormat:#"The Person %# is %d years old and is a %#",_name,_age,_job];
}
-(void)ageBy:(int)years{
_years = years;
_age = years + _age;
}
Also as a style note, if summaryString is merely for debugging then you'd possibly be better off declaring it as description. The latter is the standard form for getting an implementation-dependand string description of an Objective-C object, with the net effect that collection objects like NSArray know to call description on all their child objects in order to create the correct output.
As was stated, the problem is caused by the ageBy: method being embedded in the summaryString method.
I wanted to demonstrate how this class could be written using modern Objective-C:
// Person.h
#interface Person : NSObject
#property (nonatomic, assign) int age;
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *job;
- (void)ageBy:(int)years;
#end
// Person.m
#implementation Person
- (NSString *)description {
return [NSString stringWithFormat:#"The Person %# is %d years old and is a %#", self.name, self.age, self.job];
}
- (void)ageBy:(int)years {
self.age = self.age + years;
}
#end
Getting this error:
'NSInvalidArgumentException', reason: '-[EngineerModel _isNaturallyRTL]: unrecognized selector sent to instance
I've read How to resolve 'unrecognized selector sent to instance'? and others.
Using Xcode 4.5.1 with arc (my first time with arc)
Here's my code, which is based on an earlier non-arc project which works great
database.m
// Models for data
#import "EngineerModel.h"
- (NSArray *)returnEngineers
{
NSMutableArray *retval = [[NSMutableArray alloc] init];
NSString *query = #"SELECT * FROM engineers";
stmt = nil;
if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &stmt, nil) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
char *engineerIDChr = (char *) sqlite3_column_text(stmt, 0);
char *engineerNameChr = (char *) sqlite3_column_text(stmt, 1);
char *engineerSigFileChr = (char *) sqlite3_column_text(stmt, 2);
char *engineerPhoneChr = (char *) sqlite3_column_text(stmt, 3);
char *engineerEmailChr = (char *) sqlite3_column_text(stmt, 4);
char *engineerRegNoChr = (char *) sqlite3_column_text(stmt, 5);
NSString *engineerID = [[NSString alloc] initWithUTF8String:engineerIDChr];
NSString *engineerName = [[NSString alloc] initWithUTF8String:engineerNameChr];
NSString *engineerSigFile = [[NSString alloc] initWithUTF8String:engineerSigFileChr];
NSString *engineerPhone = [[NSString alloc] initWithUTF8String:engineerPhoneChr];
NSString *engineerEmail = [[NSString alloc] initWithUTF8String:engineerEmailChr];
NSString *engineerRegNo = [[NSString alloc] initWithUTF8String:engineerRegNoChr];
EngineerModel *info = [[EngineerModel alloc] initWithUniqueId:engineerID
engineerName:engineerName
engineerSigFile:engineerSigFile
engineerPhone:engineerPhone
engineerEmail:engineerEmail
engineerRegNo:engineerRegNo];
[retval addObject:info];
}
sqlite3_finalize(stmt);
}
return retval;
}
Database has two entries
And the model
// EngineerModel.h
#import <Foundation/Foundation.h>
#interface EngineerModel : NSObject
{
NSString *_engineerID;
NSString *_engineerName;
NSString *_engineerSigFile;
NSString *_engineerPhone;
NSString *_engineerEmail;
NSString *_engineerRegNo;
}
#property (nonatomic, copy) NSString *engineerID;
#property (nonatomic, copy) NSString *engineerName;
#property (nonatomic, copy) NSString *engineerSigFile;
#property (nonatomic, copy) NSString *engineerPhone;
#property (nonatomic, copy) NSString *engineerEmail;
#property (nonatomic, copy) NSString *engineerRegNo;
- (id)initWithUniqueId:(NSString *)AengineerID
engineerName:(NSString *)AengineerName
engineerSigFile:(NSString *)AengineerSigFile
engineerPhone:(NSString *)AengineerPhone
engineerEmail:(NSString *)AengineerEmail
engineerRegNo:(NSString *)AengineerRegNo;
- (id) init;
#end
// EngineerModel.m
#import "EngineerModel.h"
#interface EngineerModel ()
#end
#implementation EngineerModel
#synthesize engineerID, engineerName, engineerSigFile, engineerPhone, engineerEmail, engineerRegNo;
- (id)initWithUniqueId:(NSString *)AengineerID
engineerName:(NSString *)AengineerName
engineerSigFile:(NSString *)AengineerSigFile
engineerPhone:(NSString *)AengineerPhone
engineerEmail:(NSString *)AengineerEmail
engineerRegNo:(NSString *)AengineerRegNo
{
if ((self = [super init]))
{
self.engineerID = AengineerID;
self.engineerName = AengineerName;
self.engineerSigFile = AengineerSigFile;
self.engineerPhone = AengineerPhone;
self.engineerEmail = AengineerEmail;
self.engineerRegNo = AengineerRegNo;
}
return self;
}
- (id) init {
self = [super init];
return self;
}
#end
Lastly
I've added -ObjC and -all_load to other linker flags
Added #synthesize (I didn't think I had to for arc?)
If I simplify it down to
- (id)initWithId:(NSString *)AengineerID
{
NSLog(#"AengineerID %#",AengineerID);
if ((self = [super init]))
{
self.engineerID = AengineerID;
}
return self;
}
It traces AengineerID then crashes
Any ideas?
_isNaturallyRTL is a private method on NSString. So somehow you've got an EngineerModel instance where some other code is expecting an NSString.
What do you do with that array of EngineerModel objects you return from the returnEngineers method?
And just as a recommendation... using the sqlite3 C API yourself like that is a lesson in frustration. I highly recommend using something like FMDB instead.
I have a form with information first name and last name and some other information. I use a person class to store this information. on submit click I archiving it in a file person.txt using NSCoding implemented in person class. if I add multiple persons in the file person.txt, how can I get all the person objects stored in the file. decoding the person class just gives me the last added person.
If you want all of the person objects serialized, then you need the NSArray or whatever other collection class in which they are stored to be the root object for the NSKeyedArchiver. e.g.: (assumes ARC)
#import <Foundation/Foundation.h>
#interface Person:NSObject <NSCoding>
#property (nonatomic, copy) NSString *lastName;
#property (nonatomic, copy) NSString *firstName;
// etc.
#end
#implementation Person
#synthesize lastName = _lastName;
#synthesize firstName = _firstName;
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.lastName forKey:#"ln"];
[aCoder encodeObject:self.firstName forKey:#"fn"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if( !self ) { return nil; }
_lastName = [aDecoder decodeObjectForKey:#"ln"];
_firstName = [aDecoder decodeObjectForKey:#"fn"];
return self;
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
Person *me = [Person new];
me.lastName = #"Kitten";
me.firstName = #"Mittens";
Person *you = [Person new];
you.lastName = #"Youe";
you.firstName = #"JoJo";
NSArray *people = [NSArray arrayWithObjects:me,you,nil];
NSData *serializedData = [NSKeyedArchiver archivedDataWithRootObject:people];
// write your serializedData to file, etc.
[p release];
}
Why the .txt extension on your archive, though? It just binary data, right?
I've built a singleton object to manage some data in my app
#interface MyCommon : NSObject {
NSArray *quizz;
int iCurrentQuestion;
};
+ (MyCommon *)singleton;
#property (retain) NSArray *quizz;
#property (assign) int iCurrentQuestion;
#end
MyCommon.m
#import "MyCommon.h"
// MyCommon.m:
#implementation MyCommon
static MyCommon * MyCommon_Singleton = nil;
#synthesize iCurrentQuestion;
+ (MyCommon *)singleton
{
if (nil == MyCommon_Singleton)
{
MyCommon_Singleton = [[MyCommon alloc] init];
NSLog(#"allocating MyCommon_Singleton at %#",MyCommon_Singleton);
}
else {
NSLog(#"accessing singleton : %#", MyCommon_Singleton);
}
return MyCommon_Singleton;
}
- (NSArray*) getQuizz{
return quizz;
}
- (void) setQuizz:(NSArray *)array {
quizz = [NSArray arrayWithArray:array];
NSLog(#"setQuizz : %#",quizz);
}
There is no problem for writing the quizz object (setQuizz), however when I try to access it for reading, I get a crash : the quizz looks invalid and Xcode notify me an invalid CFArrayRef
I don't know what's wrong with my code.
You provide a custom setter for quizz but it doesn't comply with how the property is declared.
You're not retaining quizz when you're setting a new value. It's likely to be released just after, leading to a crash when you access it.
You should write
- (void)setQuizz:(NSArray *)array {
if (quizz != array) {
NSArray *tmp = quizz;
quizz = [array retain]; // retain the new value
[tmp release]; // release the old one
}
NSLog(#"setQuizz : %#",quizz);
}
this is way more code than it needs to be. First if you are going to be providing your own method you should declare so in the #property declaration which you didn't. Also your not properly retaining your variables. Additionally you should be using dispatch_once() for a thread safe & fast way to guarantee the singleton is only created once.
#interface MyCommon : NSObject {}
#property(nonatomic, retain) NSArray *quiz;
#property (assign) int iCurrentQuestion;
+ (MyCommon *)singleton;
#end
#implementation MyCommon
#synthesize quiz;
#synthesize iCurrentQuestion;
-(id)init {
self = [super init];
if(self) {
quiz = [[NSMutableArray alloc init];
iCurrentQuestion = 0;
}
return self;
}
+ (MyCommon *)singleton {
static MyCommon *singleton = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
singleton = [[MyCommon alloc] init];
});
return singleton;
}
#end
then you just do
[MyCommon singleton].quiz = //some array