I have 3 classes of objects. All 3 classes share some properties in common, as color, text, etc.
For example, I can have this
Class1 *objectA = [[Class1 alloc] init];
objectA.myColor = [UIColor redColor];
Class2 *objectB = [[Class2 alloc] init];
objectA.myColor = [UIColor redColor];
Class3 *objectC = [[Class3 alloc] init];
objectA.myColor = [UIColor redColor];
... etc.
Now I need, for example, to create a method that can change the color of a given object, whatever class it represents.
A typical method would be
- (void) changeColor:(Class1*) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor;
}
when in fact I need this
- (void) changeColor:(???) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor;
}
// what to put on ??? to make it generic? Is this a "whatever" kind?
thanks
EDIT
the problem of using this approach
- (void) changeColor:(id)myOBJ toColor:(UIColor*)myColor {
if ([myOBJ respondsToSelector:#selector(setColor:)]) {
myOBJ.color = myColor;
}
}
is this. Imagine I want to set the frame of the object.
Then I will have to have this:
- (void) changeColor:(id)myOBJ newFrame:(CGRect)myFrame {
if ([umID isKindOfClass:[Class1 class]]) {
Class1 *oneObj = (Class1 *)myObj;
oneObj.frame = myFrame;
}
if ([umID isKindOfClass:[Class2 class]])
Class2 *oneObj = (Class2 *)myObj;
oneObj.frame = myFrame;
}
if ([umID isKindOfClass:[Class3 class]])
Class3 *oneObj = (Class3 *)myObj;
oneObj.frame = myFrame;
}
}
in other words, I will have to repeat the same stuff 3 times... right?
in other words, the problem is not solved as this is the same of having 3 methods, one for each class.
Maybe you can use protocols? Make Class1, Class2 and Class3 conform to a protocol with a property myColor. Then you could have a method like this (assuming your classes are of type UIView and your protocol is called ColorProtocol):
- (void) changeColor:(UIView<ColorProtocol>*) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor;
myOBJ.frame = ...;
}
Here is what your protocol definition could look like:
#protocol ColorProtocol
#property (nonatomic, retain) UIColor *myColor;
#end
Change your class definition files (.h) as follows to specify that you will conform to the protocol:
interface Class1 : UIView <ColorProtocol> {...}
In the implementation files (.m) you must simply synthesize the myColor property to conform to the ColorProtocol:
#synthesize myColor;
If your classes are very similar, using inheritance might be even simpler though. Check out Philip Regan's answer.
You have a couple options. The simplest, and "most dangerous" approach is to use a type id. This will let you pass in any object, but you'll want to test that it actually has a color property before you try and set it.
- (void) changeColor:(id)myOBJ toColor:(UIColor*)myColor {
if ([myOBJ respondsToSelector:#selector(setColor:)]) {
myOBJ.color = myColor;
}
}
(That said, with the responds to selector check, this approach isn't all that dangerous, and it's much more flexible than the next idea.)
Another approach is to have all your objects inherit from a shared base class that has a color property. Then your parameter type would be the base class. This approach could be considered "safer" as the compiler would check that you're passing in the correct type of object. This approach also requires considerably more code.
If you want to use the first approach, but set something other than color, adjust the respondsToSelector: call appropriately.
- (void) changeFrame:(id)myOBJ newFrame:(CGRect)myFrame {
if ([myOBJ respondsToSelector:#selector(setFrame:)]) {
myOBJ.frame = myFrame;
}
}
In general, if you want to know if an object supports propertyX, use [myOBJ respondsToSelector:#selector(setPropertyX:)]. If the passed in object is declared as id, you can then call [myOBJ setPropertyX:newPropertyValue] or myObj.propertyX = newPropertyValue.
If you have multiple classes that share characteristics, then, if at all possible, I suggest refactoring the class structure so that those characteristics are contained in an umbrella parent class, we'll call it ClassZ. ClassZ's subclasses can override things as needed. Otherwise, let the method in the parent class handle it for you. Then, your method turns back into this...
- (void) changeColor:(ClassZ *) myOBJ toColor:(UIColor*)myColor {
myOBJ.color = myColor; // note, myObj is ClassZ, not the subclasses.
}
Otherwise, you are stuck with id and testing the individual classes.
use [object setFrame:newFrame]; instead of object.frame = newFrame;
and instead of oldFrame = object.frame; use oldFrame = [object frame];
??? will be 'id'.
Related
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?
I have two classes A and B.
Class A contains a UIView named myView and also a method MyMethod to set the position of the myView.
-(void)MyMethod:(NSString *)justCheck
{
[self.view addSubview:myView];
[self.view bringSubviewToFront:myView];
CGRect mframe = [myView frame];
NSLog(#"------------> MyMethod Called = %#",justCheck);
// EDIT: the following NSLogs are added later--------------------
NSLog(#"------------> MyMethod Called:mframe:x = %g",mframe.origin.x); //0
NSLog(#"------------> MyMethod Called:mframe:y = %g",mframe.origin.y); //42
NSLog(#"------------> MyMethod Called:mframe:H = %g",mframe.size.height); //317
NSLog(#"------------> MyMethod Called:mframe:W = %g",mframe.size.width); //320
//---------------------------------------------------------------
mframe.origin.y = 42;
[myView setFrame:mframe];
}
When a button in the class A named buttonOfA calls this MyMethod, it works perfectly and I can see the myView in position 42.
code is as below,
-(IBAction)buttonOfA:(id)sender
{
[self MyMethod:#"I am A"];
}
But, when the button of class B named buttonOfB tries to call this method, NSLog works but I cannot see the myView in position 42. Code as below,
-(IBAction)buttonOfB:(id)sender
{
[objOfA MyMethod:#"I am B"]; //objOfA is the object of class A
}
What is happening here??
I have been trying hard to figure out the problem, But I couldn't. Plz help me.
Thanx :)
EDIT: four NSLogs are added in myMethod()
Make sure the myView instance variable in the class A object is initialized and added to a super view when class B calls MyMethod: on it. Otherwise setting the frame won't have any effect.
Hold a reference to the object of type A, but it should be the same object which created the UIView. Hence it is better to return the singleton instance of the object.
Using the returned singleton for the Class A object call your method.
because myView is not a property just make it property of class A and do this work
-(void)MyMethod:(NSString *)justCheck
{
CGRect mframe = [self.myView frame];
NSLog(#"------------> MyMethod Called = %#",justCheck);
mframe.origin.y = 42;
[self.myView setFrame:mframe];
}
To make it a property instead of declaring it as
UIView* myView;
Do it as like this in .h file
#property(nonatomic,retain) UIView* myView;
and in .m of class A do it like this
#synthesize myView;
and you don't have to change anything in code just replace all myView with self.myView
It must be one of those things where there's a tiny mistake i've missed, or something, but i can't seem to figure it out.
Viewcontroller.h
#import "RGBEditView.h"
#interface ColorPickerView : UIViewController {
RGBEditView *rgbEditView;
}
-(void)showRGBEditor;
.m
-(void)showRGBEditor {
rgbEditView = [[RGBEditView alloc] initWithFrame:CGRectMake(0, 0, 280, 46) H:h S:s B:b];
}
It's this line above, the initwithframe line, that gives the error 'Incompatible Objective-C types assigning '*', expected '*'
RGBEditView.h
#interface RGBEditView : UIView {
}
-(RGBEditView *)initWithFrame:(CGRect)frame H:(float)hue S:(float)saturation B:(float)brightness;
RGBEditView.m
-(RGBEditView *)initWithFrame:(CGRect)frame H:(float)hue S:(float)saturation B:(float)brightness {
[super initWithFrame:frame];
return self;
}
Can anybody see my problem? I'm very confused about this.
EDIT:
The problem lies in that I have another class which also uses initWithFrame:H:S:B:, so the only way to fix this is to change on of them to something a bit different, but this seems like an awkward work around. Any other solutions?
the methods init and methods that start with initWith should return type id.
what typically happens is that you have 2 classes with the same method name (initializer in this case), but differ in their return types:
RGBEditView
-(RGBEditView *)initWithFrame:(CGRect)frame H:(float)h S:(float)s B:(float)b;
HSBEditView
-(HSBEditView *)initWithFrame:(CGRect)frame H:(float)h S:(float)s B:(float)b;
alloc returns id - the compiler warns you because it sees an expression which resembles type assignment used in the following example:
RGBEditView * rgb = /* ... */;
HSBEditView * hsb = nil;
hsb = rgb // << compiler: "hey - you don't want to do that unless
// RGBEditView were a subclass of
// HSBEditView... but it's not!"
you correct this by returning id from your initializers, like this:
-(id)initWithFrame:(CGRect)frame H:(float)h S:(float)s B:(float)b;
you return id to avoid clashes like this, and because the compiler doesn't know what type is returned via alloc, so every subclass declaration would have to return a different type - which would only lead to more problems.
the exception to this is to use well qualified names - and is typically seen in convenience constructors:
+ (HSBEditView *)newHSBEditViewWithFrame:(CGRect)frame
H:(float)h S:(float)s B:(float)b;
in RGBEditView.m try
self = [super initWithFrame:frame];
return self;
initWithFrame should be
-(RGBEditView *)initWithFrame:(CGRect)frame H:(float)hue S:(float)saturation B:(float)brightness {
self = [super initWithFrame:frame];
return self;
}
code structure
#protocal A_Delegate
{
-(void)doIt:(BOOL)isDone;
}
Super Class A // has properties of set delegate
-(void) setDelegate:(id<A_Delegate>)_delegate
{
/*self.delegate = _delegate*/ error, compiler stops right there and doesn't assigns the value from '_delegate'
self.delegate = _delegate.
//should be
delegate = _delegate;
}
Sub Class B : A // want to call and define the delegation for the super class of B which is A
-(void) acquireDelegation:(id<A_Delegate>)_delegate
{
[[super delegate] setDelegate];
}
Now, the Class C want to use the Class B and want to know its state,
Class C : NSObject <A_Delegation>
-(void) doSomething
{
B *b = [[B alloc]init];
[b aquireDelegation:self];
}
-(void)doIt:(BOOL)isDone
{
if(isDone)
// Do Something
}
Does any body know What I have done wrong and why super can't delegation?
Is it possible to fix?
resolved.
This would lead to infinite loop (until the stack overflow):
-(void) setDelegate:(id<A_Delegate>)_delegate
{
self.delegate = _delegate; // error, compiler stops right there and doesn't assigns the value from '_delegate'
}
cause self.delegate = _delegate; is calling setDelegate:. You have to assign to the ivar itself.
And I think this is not compiler. It is everything in runtime...
There are a couple issues. First, that protocol definition, well, isn't one. The syntax is all wrong. Secondly, what is "Super Class A" is that meant to be a comment?
Anyhoo, the major problem is in the setDelegate: method. If you have defined the delegate property then the line:
self.delegate = _delegate;
is equal to the line:
[self setDelegate:_delegate];
So, in the method -setDelegate you are calling -setDelegate.
I have two objects of class WidgetClass in my stored model. They are saved each time the app exits and reloaded each time it starts. I want to update my model to make one of them a WidgetSubclass object. WidgetSubclass will be a subclass of WidgetClass.
WidgetClass has quite a lot of ivars. WidgetSubclass will add few or none.
What is the most efficient way to accomplish the update? I am not using core data.
Couple of things.
If the subclass does not add any ivars to the superclass, you can actually get away with the following:
WidgetSubclass* widget = (WidgetSubclass*)[[WidgetClass alloc]initWithCoder: someCoder];
Class object_setClass(widget, [WidgetSubclass class]);
There is some risk that changes in the runtime could break the above code. So here is a safer way:
Foo.m:
-(void) copyIvarsTo: (Foo*) foo {
[super copyIvarsTo: foo];
foo.ivar1 = [self.objectIvar1 copy];
foo.ivar2 = [self.objectIvar2 copy];
foo.floatIvar = self.floatIvar;
// etc. Method works fine if foo is actually a member of a subclass.
}
-(Foo*) copy {
Foo* clone = [[self class]alloc];
[self copyIvarsTo: clone];
return clone;
}
Now I can have the following NSObject category method:
-(NSObject*) wj_copyWithSubclass: (Class) subclass {
if (![self respondsToSelector: #selector(copyIvarsTo:)])
return nil;
NSAssert([subclass isSubclassOfClass: [self class]], #"call copyWithSubclass only on subclasses");
NSObject* clone = [subclass alloc];
[self copyIvarsTo: clone];
return clone; // at this point, clone has copied all the ivars that are members of the receiver's class. Other ivars have their default values. Calling code needs to handle that.
}