Iphone: Replace functions using reflection - iphone

I have a small function which I want to rewrite, so that function is valid for every class.
At the moment I have 10 of the same functions which all work same but every function is for another class.
I know, that I have to do it with reflections, but I am not so sure how to do it.
I already read this link:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html
The functions I am talking about are:
-(NSCountedSet *)MissionGetReferecedNested:(id)modelObject
{
setOfObjects = [[NSCountedSet alloc]initWithArray:modelObject.MissionSectionList];
return setOfObjects;
}
-(NSCountedSet *)MissionGetSectionReferecedNested:(id)modelObject
{
setOfObjects = [[NSCountedSet alloc]initWithArray:modelObject.DamageAccountList];
return setOfObjects;
}
MissionSectionList and DamageAccountList are both NSMutableArrays from two different classes.
Is it possible to see if a class consists a NSMutableArray and if yes then it should call the .... modelObject.MyMutableArray?

You can use reflection like this:
- (NSCountedSet *)MissionGet:(id)modelObject
{
SEL propertySelector = NULL;
if ([modelObject respondsToSelector:#selector(MissionSectionList)]) {
propertySelector = #selector(MissionSectionList);
} else if ([modelObject respondsToSelector:#selector(DamageAccountList)]) {
propertySelector = #selector(DamageAccountList);
}
if (!propertySelector) {
[NSException raise:#"Invalid modelObject value" format:#"Model object %# does not contain any recognised selectors", modelObject];
}
return [[NSCountedSet alloc] initWithArray:[modelObject performSelector:propertySelector]];
}
But a more common technique among cocoa programmers would be:
- (NSCountedSet *)MissionGet:(id <MyCustomProtocol>)modelObject
{
return [[NSCountedSet alloc] initWithArray:[modelObject missionArray]];
}
Where you would accept any object which confirms to the protocol MyCustomProtocol. The protocol is defined in a header files somewhere, using:
#protocol MyCustomProtocol
#property (readonly) NSArray *missionArray;
#end
And then in each of your classes, declare it as implementing the protocol:
#interface MissionSectionListClass <MyCustomProtocol>
And add a method implementation:
#implementation MissionSectionListClass <MyCustomProtocol>
- (NSArray *)missionArray
{
return self.MissionSectionList;
}
#end
Using protocols is a bit more code, but it's the "right" way to go. It allows you to add support for new classes, without any change to your MissiongGet... method.
More info about protocols: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html

EDIT : Cleared all my answer to this :
I think it's not possible to check if a class has a member variable of specified type. You can only check if a class has a specified method.
So, in this case it will be best if you make all your NSMutableArray list the same name, and then create a declared property for this list, and then do a respondsToSelector in your ...GetReferencedNested method.
So, for example, in all of your class create this property :
#property (nonatomic, retain) NSMutableArray * list;
and then in the ..MissionGetReferencedNested method :
if ([modelObject respondsToSelector:#selector(list)])
...
Correct me if i'm wrong...

In terms of style I'd also follow Abhi's suggestion.
But if you really want to inspect a class that you are stuck with and, for example build a NSCountedSet with the first NSMutableArray variable you can find, you could do it like this:
#import "Utilities.h"
#import <Foundation/Foundation.h>
#import <objc/objc-runtime.h>
#implementation Utilities
+ (NSCountedSet*)initCountedSetWithFirstArrayinObject:(id)someObject {
unsigned int c;
Ivar *ivar_arr = class_copyIvarList([someObject class], &c);
for (unsigned int i = 0; i < c; i++) {
if ([#"#\"NSMutableArray\"" isEqualToString:
[NSString stringWithCString:ivar_getTypeEncoding(ivar_arr[i]) encoding:NSUTF8StringEncoding]
]) {
return [[NSCountedSet alloc] initWithArray:object_getIvar(someObject, ivar_arr[i])];
}
}
return nil;
}
#end
Of course this has very limited real world use because it depends on you knowing that the first array will be the one you're interested in.

I think I have to go with the runtime type editing.(http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html)
The idea with the protocols was good but there I have to change a lot of things in the classes.(which is not possible/allowed) for me. My intension was only to change the functions so that I have only one function for all classes.
I think with the runtime type editing I can check what classes and attributes I have (?) Am I right?
Did somebody already work with runtime type editing?

Related

Using NSMutableDictionary as backing store for properties

I am looking for a shorthand way of setting my properties directly to an NSMutableDictionary that is a instance variable. ie:
KVCModle.h:
#interface KVModel : NSObject {
NSMutableDictionary * data;
}
#property(nonatomic,assign)NSString * string1;
#property(nonatomic,assign)NSString * string2;
#end
KVCModel.m
#import "KVModel.h"
#implementation KVModel
-(id)init
{
self = [super init];
if(self)
{
data = [[NSMutableDictionary alloc] init];
}
return self;
}
-(NSString *)string1
{
return [data objectForKey:#"string1"];
}
-(NSString *)string2
{
return [data objectForKey:#"string2"];
}
-(void)setString1:(NSString *)_string1
{
[data setObject:_string1 forKey:#"string1"];
}
-(void)setString2:(NSString *)_string2
{
[data setObject:_string2 forKey:#"string2"];
}
-(void)dealloc
{
[data release];
[super dealloc];
}
#end
I have tried to override setValue:ForKey: and valueForKey:, but those aren't called, they allow you to directly set properties without using the property syntax.
I have made preprocessor macros to make this work in the past, but I am not interested in typing at all, and would like to avoid as much of it as I can in the future. Is there a way to make this work that I am not familiar with?
I have thought about using NSManagedObject, but I am not sure if I can get what I want out of that.
EDIT:
source
If you're trying to access the properties with code like foo = obj.foo and obj.foo = foo, that's why it doesn't work.
Property-access syntax is synonymous with message syntax; the former is exactly the same as foo = [obj foo], and the latter is exactly the same as [obj setFoo:foo]. There is no KVC code to intercept. Properties are at the language level; KVC is at the framework level.
You'll need to intercept the accessor messages instead. Consider implementing the resolveInstanceMethod: class method, in which you “resolve” the selector by adding a method implementation to the class using the Objective-C runtime API. You can add the same implementation(s) for many different selectors.
For your purpose, have a function or method that examines the selector (using NSStringForSelector and regular NSString-examining techniques) and returns two facts: (1) the property name, and (2) whether it's a getter (foo, isFoo) or setter (setFoo:). Then, have two more methods, one a dynamic getter and the other a dynamic setter. When the selector names a getter, add it with your dynamic-getter method; when the selector names a setter, add it with your dynamic-setter method.
So how do the dynamic-getter and -setter methods work? They'll need to know what property to dynamically get and set, but they also need to take no arguments (getter) or one argument (setter, which takes the value), in order to match the original property-access message. You might be wondering how these generic implementations can know what property to get or set. The answer is: It's in the selector! The selector used to send the message is passed to the implementation as the hidden argument _cmd, so examine that selector the same way as before to extract the name of the property you should dynamically get or set. Then, the dynamic getter should send [data objectForKey:keyExtractedFromSelector] and the dynamic setter should send [data setObject:newValue forKey:keyExtractedFromSelector].
Two caveats:
You may still get complaints from the compiler when you use the property-access syntax to access a “property” that you have not declared in the class's #interface. This is normal and intentional; you're really only supposed to use property-access syntax to access known formal properties. What you're doing, while I found it fun to solve, is technically an abuse of the property-access syntax.
This will only work for object values. KVC does the boxing and unboxing for primitive values, such as integers; since KVC is not involved, no free boxing and unboxing. If you have declared formal properties (see 1), you'll need to introspect them using the Objective-C runtime API, and do the boxing and unboxing yourself with your findings.
This piqued my curiosity, so I went ahead and used Peter Hosey's suggestion of overriding +resolveInstanceMethod: to generate the getters and setters. I posted the resulting object (DDDynamicStorageObject) to a github repository:
https://github.com/davedelong/Demos
What you basically want is your own implementation of the NSManagedObject machinery. I have done something similar. Look here: https://gist.github.com/954035 HTH
(Updated the code to remove the dependency on the non-existant NSString+Utilities.h)
(Added missing ReleaseAndZero() macro)
For the love of all that is sacred - do not use an NSDictionary as a place to stuff every conceivable property of a model object. Ivars are easier to debug, and much much clearer to other developers (including your future self).
If you want to use a dictionary, use a dictionary and some statically defined keys - but if you want a model object, use some ivars
I come to the same problem today just like you. So I find your question posted here.
The answers above used the +resolveInstanceMethod: is a little bit hard for me. :)
My understanding is that as long as we setup the property, we would have getter and setter method, so I use the setter method to implement it.
BDLink.h
#property (nonatomic, strong) NSString *type;
#property (nonatomic, strong) NSString *displayName;
#property (nonatomic, strong) NSString *linkURI;
BDLink.m
- (id)initWithLinkInfoDictionary:(NSDictionary *)linkInfoDict {
for (NSString *key in linkInfoDict) {
const char *rawName = [key UTF8String];
NSString *setMethodString = [NSString stringWithFormat:#"set%c%s:", toupper(rawName[0]), (rawName+1)];
SEL setMethod = NSSelectorFromString(setMethodString);
if ([self respondsToSelector:setMethod]) {
[self performSelector:setMethod withObject:linkInfoDict[key]];
}
}
return self;
}
Hope it would be helpful. My first answer, :)

Problem with singleton

I want to make a singleton containing information "title, comments, Two picture" and it saves all the information in an array
I want to do is these objects in my application I use it All The Time
#interface CarteManager : NSObject {
NSMutableArray *carteMan ;
}
#property(nonatomic,retain) NSMutableArray *carteMan;
+(CarteManager*)sharedInstance;
-(void)ajouttitre:(NSString*)txt;
-(void)ajoutcom:(NSString*)com;
-(void)ajoutimage1:(UIImage*)img;
-(void)ajoutimage2:(UIImage*)img;
#end
In order to create a Singleton you will need a static instance.
#implementation CarteManager
static CarteManager *_carteManager = nil;
+(CarteManager*)sharedInstance {
if (!_carteManager) {
_carteManager = [[CarteManager alloc] init];
}
return _carteManager;
}
// your other codes
#end
And before creating a Singleton, make sure that you really need a Singleton. Please pay special attention to Singleton: How should it be used.
You didn't state your problem. If it's how to make the object a singleton, you can find several possible implementations in the question What does your Objective-C singleton look like?.

Sharing variables amongst 2 ViewControllers

Here is a simple one for you guys . I'm defining a class to store variables in so I can reuse those variables in different ViewControllers .
Here is how I do it, it`s obviously not working ( that's why I'm asking a question ... ):
I declare a class :
VariableStore.h
#interface VariableStore : NSObject {
int evTe;
}
#property (nonatomic) int evTe;
+ (VariableStore *)shareInstance;
#end
VariableStore.m
#implementation VariableStore
#synthesize evTe;
+ (VariableStore *)sharedInstance {
static VariableStore *myInstance = nil;
return myInstance;
}
#end
Now in my FirstViewController I want to set the value for evTe :
[[VariableStore sharedInstance] setEvte:2];
NSLog(#"value testing, %i", evTe);
And this keeps on returning 0 unfortunately, Im obviously missing something important here but I can't figure out what it is .
Later on Id like to set the value for evTe here in the FirstViewController and then reuse it back in the SecondViewController ..
You are setting your shared instance to nil and then returning it:
static VariableStore *myInstance = nil;
return myInstance;
A nil instance won't hold your variable. It's nil.
First off you shouldn't be using a singleton to pass around variables. If you're going to do that then you might as well just use global variables instead (don't do that either by, the way). Second, if you insist on using a singleton, you need to read up on how to use them.
Finally, if you want to pass variables between view controllers, you either need another view controller that is a parent to the two to facilitate passing data between them, or one needs to call the other and take the first one or its data as a parameter.
Well, you're asking for the value of evTe without calling the object to which it belongs. Try this:
NSLog(#"value testing, %i", [[VariableStore sharedInstance] evTe]);
If you keep using the singleton for a number of times, you might want to do:
VariableStore *vStore = [VariableStore sharedInstance];
so you can do:
[vStore setEvTe:2];
NSLog(#"value testing, %i", [vStore evTe]);
And look out for what Matt said about nilling your singleton ;)
I think in nslog you should output not just evTe, but [[VariableStore sharedInstance] evTe].
First, you have to declare the static variable outside the function, in a way both controllers can access.
static VariableStore* myInstance = nil;
The singleton sharedInstance should be:
if(myInstance == nil)
{
myInstance = [[VariableStore] alloc] init];
}
return myInstance;

Obj-C: Creating an object with a String name

Hey all. I know this sounds simple, but I can't find a way to do it. I have a method in Obj-C that takes in a NSString and then should create a new class with the String as its title.
-(DataModel *)createDataModel:(NSString *)dataModel_name {
DataModel *[initWithString:dataModel_name] = [[DataModel alloc] init];
}
I know I have some problems in this. For starters, I don't know how to define a return on an object whose name could change. Second, I know this doesn't compile considering the initWithString method is wrong. I just don't know what to do or what method to use so that I can create this DataModel object with the specified name...
If your title is setup correctly, as a property:
-(DataModel *)createDataModel:(NSString *)dataModel_name {
DataModel *model = [[DataModel alloc] init];
model.title = dataModel_name;
return model;
}
That would require in your datamodel.h:
#interface DataModel {
NSString *title;
}
#property (nonatomic, retain) NSString *title;
#end
And in your .m:
#implementation DataModel
#synthesize title;
#end
But your question isn't clear if your real purpose is trying to instantiate different classes based on the dataModel_name or if you just have a single generic class with a title that should be set to dataModel_name.
Depending on what you want to do, there are different answers. If you really want different classes based on the name, then you should do things differently. You can use the Cocoa specific type: id, to return any object from a method. Then the method, NSClassFromString() to create the object:
- (id)createDataModel:(NSString *)dataModel_name {
id model = [[NSClassFromString(dataModel_name) alloc] init];
[model setTitle:dataModel_name];
return model;
}
Or you can define a Protocol (Interface in java parlance) that declares the features of your data model. Your method would return that instead.
NSClassFromString() will do what you want. Also, initially declaring variables as type id allows you to set their explicit type later on. So:
id dataModel = [[NSClassFromString(dataModel_name) alloc] init];
To locate or create a new class:
Class arbitraryClass = NSClassFromString(dataModel_name);
if ( nil == arbitraryClass ) arbitraryClass = objc_allocateClassPair( [DataModel class] , [dataModel_name UTF8String] , 0 );
To create a new instance of an object with your newly created class:
DataModel *modelWithArbitratyClassName = [[arbitraryClass alloc] init];
Creating new classes at runtime is not usually a good idea.
So, it seems you want to dynamically add an Instance variable to an object at runtime. You don't get this for free. CALayer and CAAnimation can do something similar to this, you can read about it here
You could add similar functionality to your own objects using Key-value-coding, and more specifically the method valueForUndefinedKey. There will be some KVC specific caveats so you should really make sure you are familiar with and understand KVC. Take a look at this, it might be just want you want.
A dictionary is used to store the value and key, and to retrieve the value when you try to access it.

Inheritance Issues in Objective C

I created an "SDMutableGrid" class so that I could use a grid. It's just a child of NSMutableArray that contains a number for arrays equal to the number of rows in the grid.
Currently, the program quits before it really starts and it appears that it is because the methods defined for NSMutableArray somehow do not apply to SDMutableGrid, anyone know why?
Here is the .h :
#import <Foundation/Foundation.h>
#import "SDDimensions.h"
#interface SDMutableGrid : NSMutableArray {
SDDimensions dimensions;
}
#property (nonatomic) SDDimensions dimensions;
- (id)initWithDimensions:(SDDimensions)newDimensions;
- (void)addObject:(id)anObject toRow:(NSUInteger)row;
#end
Here is the .m :
#import "SDMutableGrid.h"
#implementation SDMutableGrid
#synthesize dimensions;
- (void)setDimensions:(SDDimensions)newDimensions {
if (newDimensions.width < dimensions.width) {
NSMutableArray *anArray;
NSRange aRange = NSMakeRange(newDimensions.width, dimensions.width - newDimensions.width);
for (NSUInteger i = 0; i < MIN(dimensions.height,newDimensions.height); i++) {
anArray = [self objectAtIndex:i];
[anArray removeObjectsInRange:aRange];
}
}
dimensions.width = newDimensions.width;
if (newDimensions.height > dimensions.height) {
for (NSUInteger i = dimensions.height; i < newDimensions.height; i++) {
[self addObject:[[NSMutableArray alloc] initWithCapacity:dimensions.width]];
}
} else if (newDimensions.height < dimensions.height) {
[self removeObjectsInRange:NSMakeRange(newDimensions.height, dimensions.height - newDimensions.height)];
}
dimensions.height = newDimensions.height;
}
- (id)initWithDimensions:(SDDimensions)newDimensions {
if (self = [super initWithCapacity:newDimensions.height]) {
NSMutableArray *anArray;
for (NSUInteger i = 0; i < newDimensions.height; i++) {
anArray = [[NSMutableArray alloc] initWithCapacity:newDimensions.width];
NSLog(#"Got this far");
[self addObject:anArray];
NSLog(#"woot");
[anArray release];
}
NSLog(#"Finished Initializing grid");
}
return self;
}
- (void)addObject:(id)anObject toRow:(NSUInteger)row {
[[self objectAtIndex:row] addObject:anObject];
}
#end
And here is what is appearing on the console:
2009-08-12 15:27:02.076 Flipswitch[1756:20b] Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: ' -[NSMutableArray initWithCapacity:]: method only defined for abstract class. Define -[SDMutableGrid initWithCapacity:]!'
2009-08-12 15:27:02.080 Flipswitch[1756:20b] Stack: (
807902715,
2536648251,
808283725,
808264737,
13690,
11018,
10185,
814713539,
814750709,
814739251,
814722434,
814748641,
839148405,
807687520,
807683624,
814715661,
814752238,
10052,
9906
)
The short, easy answer: Don't make a subclass of NSArray. It's better to make a category on NSArray or make an NSObject subclass that has an NSArray ivar that you talk to.
The long, technical answer: NSArray is a class cluster. This means that it isn't actually one class, but many classes operating under the NSArray abstract class interface that are each implemented in a different way (say, one implementation for small arrays, another for big arrays, etc.). To create a subclass of a class cluster, you have to implement all the primitive methods of the abstract class you are inheriting from, manage your own storage and basically reimplement all the stuff you were hoping to get for free by subclassing.
More simply, you could just create a category if you don't require additional ivars. If you want an object that behaves like an array with additional state, you can create a class that has an NSArray and use Objective-C message forwarding to forward everything except your custom behavior to that class.
This is due to the nature of 'Class Clusters' used for collection classes in Foundation.
See:
Class Clusters
Basically, NSMutableArray defines a public interface to 'mutable arrays', but is not the actual class you use when initialized. So 'initWithCapacity:' is defined, but not implemented in NSMutableArray. If you run:
NSMutableArary *foo = [[NSMutableArray alloc] init];
NSLog(#"%#", [foo className]);
you will print "_NSCFArray", which is a concrete subclass of NSMutableArray (and NSArray). To work around this, I would have a instance variable that is an NSMutableArray, or implement 'initWithCapacity:' with a suitable meaning (such as a capaciy of '3' means a 3x3 grid).
Ok, I found the answer from this question
Although the questions are different, the answer is the same and that is that due to the setup of NSArray (and therefore NSMutableArray), you cannot subclass it without implementing the methods yourself.
So I guess I'll just make SDMutableGrid have an NSMutableArray variable instead of actually being an NSMutableArray.
You problem is that you are not implementing abstract methods of NSMutableArray super class that need to be implemented, it says
-[NSMutableArray initWithCapacity:]: method only defined for abstract class. Define -[SDMutableGrid initWithCapacity:]!' 2009-08-12 15:27:02.080 Flipswitch[1756:20b]
So you need do define initWithCapacity in your subclass, I would recommend to not extend NSMutableArray, there is no need, just make a class that has a mutable array in it.