class_copyPropertyList for all subclasses also - ios

Here is explanation
List of class properties in Objective-C
how to use class_copyPropertyList to get at runtime all properties of class.
I have tested this and it is working fine.
I have notice that it will only get properties from that class only, and not it subclass.
CODE:
#interface JustForThisUT : NSObject
#property NSUInteger public_access;
#end
#interface JustForThisUT ()
#property NSUInteger private_access;
#end
#implementation JustForThisUT
#end
#interface JustForThisUTParent : JustForThisUT
#property NSUInteger my_public_access;
#end
#interface JustForThisUTParent ()
#property NSUInteger my_private_access;
#end
#implementation JustForThisUTParent
#end
If I use it on JustForThisUT I will get 2 properties (public_access & private_access)
If I use it on JustForThisUTParent I will ALSO get 2 properties (my_public_access & my_private_access)
But for JustForThisUTParent I was expecting to get 4, 2 from JustForThisUT and 2 from JustForThisUTParent.
My question is
How to get properties from current class and all subclasses ?
Is it even possible ?

You have to first find all the subclasses, then use the class_copyPropertyList() to list the properties for each class, uniquing as needed.
However, this has a bad code smell. This level of dynamic, reflection heavy, programming is counter to what ObjC is really designed for. You'll end up with a fragile codebase that is difficult to maintain.
If you really want dynamism of this nature, then implement a class method on each class that returns the list of property names that you want to be able to dynamically access.

It's fairly simple to find all the superclasses (I presume this is what you meant, rather than subclasses); you simply loop until class.superclass == NSObject.class.
Do note #bbum's comments about this being a bad code smell though. Read his answer and consider alternative approaches, and really think about why you want to do this and if there's a better way.
That said, here's a full code example that retrieves all the BOOL properties from a class. It would be easy to extend it for other property types - see the linked questions.
/**
* Get a name for an Objective C property
*
*/
- (NSString *)propertyTypeStringOfProperty:(objc_property_t) property {
NSString *attributes = #(property_getAttributes(property));
if ([attributes characterAtIndex:0] != 'T')
return nil;
switch ([attributes characterAtIndex:1])
{
case 'B':
return #"BOOL";
}
assert(!"Unimplemented attribute type");
return nil;
}
/**
* Get a name->type mapping for an Objective C class
*
* Loosely based on http://stackoverflow.com/questions/754824/get-an-object-properties-list-in-objective-c
*
* See 'Objective-C Runtime Programming Guide', 'Declared Properties' and
* 'Type Encodings':
* https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101
* https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
*
* #returns (NSString) Dictionary of property name --> type
*/
- (NSDictionary *)propertyTypeDictionaryOfClass:(Class)klass {
NSMutableDictionary *propertyMap = [NSMutableDictionary dictionary];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(klass, &outCount);
for(i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName)
{
propertyMap[#(propName)] = [self propertyTypeStringOfProperty:property];
}
}
free(properties);
return propertyMap;
}
- (NSDictionary *)settingsProperties
{
if (!_settingsProperties)
{
Class class = _settings.class;
NSMutableDictionary *propertyMap = [[NSMutableDictionary alloc] init];
do
{
[propertyMap addEntriesFromDictionary:[self propertyTypeDictionaryOfClass:class]];
}
while ((class = class.superclass) != NSObject.class);
_settingsProperties = propertyMap;
}
return _settingsProperties;
}

Related

accessing NSDictionary property of class in init function gives error "use of undeclared identifier". Need help understanding why

Here is a class for a deck of cards that could have several different configurations that I define using a NSDictionary with string keys and array values of how the cards are to be added. I haven't completed the init function yet, but it gives me the error above on trying to access my NSDictionary property. Fairly new to objective-c sorry if this is trivial question.
Here is my .m class file:
#interface MarioCardDeck()
#property (strong, nonatomic)NSDictionary *cardConfigurations;
#end
#implementation MarioCardDeck
- (instancetype)init {
self = [super init];
if(self) {
unsigned index = arc4random() % [[cardConfigurations allKeys] count]; ** error line
}
return self;
}
- (NSDictionary *)cardConfigurations
{
if(!_cardConfigurations)
{
_cardConfigurations = #{
#"1" :
#[#"flower",#"coin20",#"mushroom",#"star",#"oneUp",#"flower",#"oneUp",#"flower",#"coin10",#"mushroom",#"coin20",#"star",#"mushroom",#"coin10",#"star",#"mushroom",#"flower",#"star"],
#"2" :
#[#"flower",#"coin10",#"oneUp",#"flower",#"oneUp",#"mushroom",#"star",#"mushroom",#"coin20",#"star",#"mushroom",#"coin10",#"star",#"flower",#"coin20",#"mushroom",#"flower",#"star"]
};
}
return _cardConfigurations;
}
#end
You need:
unsigned index = arc4random() % [[self.cardConfigurations allKeys] count];
You need to access the property by using self.
FYI - you should use:
unsigned index = arc4random_uniform([[self.cardConfigurations allKeys] count]);
You need to refer to it by self.cardConfigurations.
You need to change
unsigned index = arc4random() % [[cardConfigurations allKeys] count];
to
unsigned index = arc4random() % [[self.cardConfigurations allKeys] count];
Accessing properties in the init method is, however, dangerous thing to do in Objective-C. If the getter is overridden in a subclass you might get a nasty surprise. I would make another property for the index, assign the instance variable to NSNotFound, and do the calculation the first the time the getter method is called. Also, you should use NSUInteger as the type for storing the index.

Why can I not assign a value to my ivar?

it has been a while since I did anything in ObjC. I am sure the mistake is trivial but I can't seem to find it.
I created a class called Game with the following .h file:
#import <Foundation/Foundation.h>
#interface FSGame : NSObject
#property NSInteger numberOfGames;
#end
In the implementation file of one of my view controllers, a method then uses the user's selection from a segm. control to determine the number of games:
FSGame *game;
// Number of Games
NSInteger index = _segCtrlNumberGames.selectedSegmentIndex;
if (index == 0) {
game.numberOfGames = 1;
} else if (index == 1) {
game.numberOfGames = 3;
} else {
game.numberOfGames = 5;
}
NSLog(#"Games: %i; index: %i", game.numberOfGames, index);
The NSLog returns: Games: 0; index: 2
I have the same issue with two other classes I created. I grab text from a UITextField and the NSLog displays it correctly so I know I am actually able to get the user's input - it just wont store it as an ivar.
This must be something really obvious. Any pointers?
You create the Game object:
Game *game;
But you should create NSGame instead and you need to allocate and initialise it as well:
FSGame *game = [[FSGame alloc] init];

NSOrderedSet vs NSArray indexOfObjectPassingTest:

The NSOrderedSet Class Reference Overview says:
You can use ordered sets as an alternative to arrays when the order of elements is important and performance in testing whether an object is contained in the set is a consideration— testing for membership of an array is slower than testing for membership of a set.
Which methods are considered "testing for membership"? Just containsObject:? Or, will indexOfObjectPassingTest: also be faster?
I'm asking because if I just have the object's ID (from the server for example) and want to check if the ordered set contains an object with that ID, I'd use indexOfObjectPassingTest:. But, that method, since it tests every object in the collection, seems that it'd be just as slow as it is for an array. On the other hand, containsObject: seems that it'd be faster since it takes advantage of the NSObject methods hash & isEqual:. I could just create a probe object with the ID I have, and then use containsObject:. But then, if the ordered set already contains an object with that ID, I'll just discard of the probe object and update the properties on the object already in the ordered set. It seems like extra work to have to create a probe object first. In that case, is it even worth using an ordered set over an array?
Also, I would be sorting objects by their date, not their ID.
I'd use an NSMutableDictionary with object IDs mapping to objects, as St3fan suggested, but I also want to display the objects in a UITableView.
The best way to test this is by writing some small benchmarks. I don't know how many objects you are dealing with but if it is less then a few hundred then you probably won't notice much difference between containsObject:, indexOfObjectPassingText: or even just iterating over all objects manually.
It sounds like an NSMutableDictionary is more appropriate for your use case actually. Why don't you store your objects in a dictionary that is indexed by your object's ID? Then you can really quickly find them by ID and you can also iterate easily over them if needed.
You can override -isEqual: and -hash in you class. If you do, it'll work with NSOrderedSet's fast lookup. It can be as simple as:
- (BOOL)isEqual:(id)otherObject
{
return self.myID == otherObject.myID;
}
- (NSUInteger)hash
{
return self.myID;
}
Here's a full example:
#import <XCTest/XCTest.h>
#interface MyClass : NSObject
#property (nonatomic) NSInteger myID;
#property (nonatomic, strong) NSDate *date;
#end
#implementation MyClass
- (BOOL)isEqual:(MyClass*)otherObject
{
return self.myID == otherObject.myID;
}
- (NSUInteger)hash
{
return self.myID;
}
#end
#interface MyTests : XCTestCase
#end
#implementation MyTests
- (void)testExample
{
MyClass *obj1 = [[MyClass alloc] init];
obj1.myID = 1;
obj1.date = [NSDate dateWithTimeIntervalSince1970:20000];
MyClass *obj2 = [[MyClass alloc] init];
obj2.myID = 2;
obj2.date = [NSDate dateWithTimeIntervalSince1970:10000];
MyClass *obj3 = [[MyClass alloc] init];
obj3.myID = 1;
obj3.date = [NSDate dateWithTimeIntervalSince1970:30000];
MyClass *obj4 = [[MyClass alloc] init];
obj4.myID = 3;
obj4.date = [NSDate dateWithTimeIntervalSince1970:30000];
NSOrderedSet *set = [[NSOrderedSet alloc] initWithArray:#[obj1, obj2]];
XCTAssertEqualObjects(((MyClass *)[set firstObject]).date, obj1.date);
XCTAssertEqualObjects(((MyClass *)[set lastObject]).date, obj2.date);
XCTAssertTrue([set containsObject:obj1]);
XCTAssertTrue([set containsObject:obj3]);
XCTAssertFalse([set containsObject:obj4]);
}
#end

Trouble understanding static class in iOS

I am following the Big Nerd Ranch book on iOS programming.
There is a sample of a static class:
#import <Foundation/Foundation.h>
#interface BNRItemStore : NSObject
+ (BNRItemStore *) sharedStore;
#end
I have a problem undrstanding the bit below with question mark in the comments.
If I try to alloc this class, the overriden method will bring me to sharedStore, which in turn sets the static pointer sharedStore to nil. The conditional after will hit the first time because the pointer doesn't exist.
The idea is that the second time I am in the same place, it would not alloc a new instance and get the existing instance instead. However with static BNRItemStore *sharedStore = nil; I am setting the pointer to nil and destroy it, isn't it? Hence every time I am creating unintentionally a new instance, no?
#import "BNRItemStore.h"
#implementation BNRItemStore
+ (BNRItemStore*) sharedStore
{
static BNRItemStore *sharedStore = nil; // ???
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}
+(id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
#end
However with static BNRItemStore *sharedStore = nil; I am setting the pointer to nil and destroy it, isn't it?
No, you do not set it to nil: the expression in the static initializer is computed only once; the second time around the initialization has no effect. This looks very confusing, but this is the way the function-static facility works in C (and by extension, in Objective-C).
Try this as an example:
int next() {
static int current = 123;
return current++;
}
int main(int argc, char *argv[]) {
for (int i = 0 ; i != 10 ; i++) {
NSLog(#"%d", next());
}
return 0;
}
This would produce a sequence of increasing numbers starting at 123, even though the code makes it appear as if current is assigned 123 every time that you go through the function.

Storing an enum property inside CoreData and making it convenient

One of my model object has an enum property. To store it in CoreData, I've used an NSNumber object.
Nevertheless, I'd like to access it as an enum type in a convenient way. What is the best practice to achieve that?
So far, I've gone with the following code.
in MyObject.h
typedef enum _ABType {
ABTypeUnknown,
ABTypeValue1,
...
ABTypeValueN
} ABType;
#interface MyObject : NSManagedObject
#property (nonatomic, retain) NSNumber * myPersistentEnum; // Defined in my Core Data model
#property (nonatomic) ABType myConvenientEnum;
in MyObject.m
#dynamic myPersistentEnum;
- (BOOL)isValidEnumValue {
if (self.myPersistentEnum) {
int intValue = [self.type intValue];
if (intValue >= ABTypeValue1 && intValue <= ABTypeValueN) {
return YES;
}
}
ELog(#"Undefined enumValue %#", self.myPersistentEnum);
return NO;
}
- (ABType)myConvenientEnum {
if ([self isValidEnumValue]) {
return [self.type intValue];
}
return ABTypeUnknown;
}
- (void)setMyConvenientEnum:(ABType)enumValue {
NSNumber *oldValue = [self.myPersistentEnum retain];
self.myPersistentEnum = [NSNumber numberWithInt:enumValue];
if ([self isValidEnumValue]) {
[oldValue release];
} else {
self.myPersistentEnum = oldValue;
[oldValue release];
}
}
My questions are:
Is there something wrong in the code above?
Is int the right type to use when converting an enum to NSNumber? (NSNumber doesn't provide an -(enum)enumValue; method)
Would you leave the validation aspect to the runtime CoreData model validation?
[NEW] How can I make clear for other developers that the convenient property should be used and not the NSNumber property?
CoreData in iOS 5 supports native integer types, so you can use that instead of NSNumber if you want.
EDIT
FWIW, the compiler can make enum be as small as necessary for the possible values it holds. It is best to cast the enum -- (int)myEnumValue -- when assigning into a NSNumber, because it assumes the type is the exact size you tell it.

Resources