Let's say I have a UIViewController Subclass,
let's call it MyAppBaseViewController:
and
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSString *subclassName = ...;
NSLog(#"%# did appear", subclassName);
}
How would I be able to get the subclass name, not the name of the current class, from within the parent, without adding any properties to the implementation or anything like that?
You are confused.
An object is an instance of some subclass. It can, and often does, inherit from a long chain of parent classes.
The object is still a particular class.
If you use
NSString *myClassName = [NSString stringWithFormat: #"%s", class_getName([self class)];
It will give you the name of the current object's class. Not the parent class. The current object.
Edit:
As the other poster pointed out, the function NSStringFromClass is much easier. Use that instead:
NSString *myClassName = NSStringFromClass([self class]);
Related
i would like to add extra data to EKEvent, i tried NSDictionary (there is a lot of data to add) but it doesn't work..
sample code:
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc]init];
[eventStore setValue:dictionary forKey:MAIN_DICTIONARY];
any ideas?
You're using setValue:forKey: in a wrong way. That a look here. There are different options to achieve what you want: category, subclassing or create a class that contains the EKEvent and the NSMutableDictionary. It depends on how you need to use the EKEvent.
You cannot do it this way, because even with key-value coding you can only set (declared or non declared) properties known by the instance. Basically the accessors (setter, getter) are executed. But there is no property MAIN_THREAD,no setter setMAIN_THREAD: in EKEvent.
If you want to extend instances of a foreign class that are created by the system (the instances, not the class), there are to ways to add data:
You create an own class, let's say MyEvent and give them a reference to the system instance (EKEvent) as a property plus the properties you need. When you get an instance of EKEvent you look-up your list of MyEventss using the identifier. With that you have the full access to your data.
You use associated objects. But you have to take care that they are not handled by the instance, i. e. while copying.
The first solution is better by far. Simple sample code:
#interface MyEvent : NSObject
#property (readonly) EKEvent* systemEvent;
#property id customProperty;
- (instancetype)eventForSystemEvent:(EKEvent*)systemEvent;
#end
#implemenation MyEvent
// Look-Up
NSMutableDictionary *eventLookUp;
+ (void)initialize
{
if( self == [MyEvent class])
{
eventLookUp = [NSMutableDictionary new];
}
}
- (instancetype)eventForSystemEvent:(EKEvent*)systemEvent
{
return eventLookUp[systemEvent.calendarItemIdentifier];
}
// Instance creation
- (instancetype)initWithSystemEvent:(EKEvent*)systemEvent
{
// Usual initializer
…
eventLookUp[systemEvent.calendarItemIdentifier] = systemEvent;
return self;
}
+ (instancetype)newEventWithSystemEvent:(EKEvent*)systemEvent
{
return [[self alloc] initWithSystemEvent:systemEvent];
}
#end
Typped in Safari
I have a class named SPPanelManager, which has a property of another class, named SPPanelSettingsManager. SPPanelManager has the following in it's -init method:
self.settingsManager = [[SPPanelSettingsManager alloc] init];
The purpose of SPPanelManager is to be subclassed, and the subclasses are used throughout my app. For example, there's SPGreetingManager. In the .h file of SPGreetingManager, I have declared:
#property (nonatomic, strong) SPGreetingSettingsManager *settingsManager;
which makes the settingsManager be of the correct class. The problem is that when the SPGreetingManager subclass is initialized, it calls the init method above, and initializes the settingsManager as the SPPanelSettingsManager class, rather than SPGreetingSettingsManager.
How can I get it to initialize this as the correct class for that property without having to re-write the init code in every subclass?
The super class (SPPanelManager) somehow has to know which class the concrete panel manager wants to use as a settingsManager.
Apple uses the following approach to match CALayers to UIViews:
The base class declares a class method that returns the concrete SPPanelSettingsManager subclass:
// in SPPanelManager.h
+ (Class)settingsManagerClass;
... which subclasses override to return their custom class:
// in SPGreetingManager.m
+ (Class)settingsManagerClass
{
return [SPGreetingSettingsManager class];
}
Now the superclass can instantiate the settings manager as follows:
self.settingsManager = [[[[self class] settingsManagerClass] alloc] init];
Another common solution is to use a naming convention. Just match the names of the classes: SPGreetingManager has a SPGreetingSettingsManager.
By definition each ...Manager has to have a matching ...SettingsManager.
// in SPPanelManager.m init
NSString *className = NSStringFromClass([self class]);
className = [className stringByReplacingOccurrencesOfString:#"Manager"
withString:#"SettingsManager"];
Class settingsManagerClass = NSClassFromString(className);
NSAssert(settingsManagerClass != Nil, #"no settings manager class found");
self.settingsManager = [[[settingsManagerClass settingsManagerClass] alloc] init];
The advantage is that subclasses don't have to override a common method to declare the class type. On the other hand it might seem a bit obfuscated what's going on.
Also, above code forces a one to one relationship between the classes. No settings controller could be reused.
Is SPPanelManager a class you developed? Simply give it a init with a parameter for the settings instance.
I have a MKPolyline subblass which I want to implement NSCoding, i.e.
#interface RSRoutePolyline : MKPolyline <NSCoding>
I asked a question on the best way to encode the c-array and got an excellent answer. However, there is no init method defined on MKPolyline, i.e. there is no other way to give it data other than its class method polylineWithPoints:points.
Is this code where my comment is ok?
- (void)encodeWithCoder:(NSCoder *)aCoder
{
MKMapPoint *points = self.points;
NSUInteger pointCount = self.pointCount;
NSData *pointData = [NSData dataWithBytes:points length:pointCount * sizeof(MKMapPoint)];
[aCoder encodeObject:pointData forKey:#"points"];
[aCoder encodeInteger:pointCount forKey:#"pointCount"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
NSData* pointData = [aDecoder decodeObjectForKey:#"points"];
NSUInteger pointCount = [aDecoder decodeIntegerForKey:#"pointCount"];
// Edit here from #ughoavgfhw's comment
MKMapPoint* points = (MKMapPoint*)[pointData bytes];
// Is this line ok?
self = (RSRoutePolyline*)[MKPolyline polylineWithPoints:points count:pointCount];
return self;
}
You should call an init method on any subclass of NSObject. Since MKPolyline is an NSObject, you should init it.
But MKPolyline has no methods and no init. This is Objective C's was of telling you that you can't subclass it.
Instead, as WDUK suggested, define your own class. It keeps track of your list point points, and manages NSCoding to save and restore them as needed.
#interface RSPolyline: NSObject<NSCoding>
- (id) initWithPoints: (NSArray*) points;
- (id) initWithCoder:(NSCoder *)aDecoder;
- (void) encodeWithCoder:(NSCoder *)aCoder;
- (MKPolyline*) polyLine;
#end
Your class can generate a polyline on request, perhaps caching the result if performance is an issue.
As a rule, don't reach for inheritance first. When you want to extend and improve a class, think first of composition.
It's dirty not to call [super init], and it doesn't bode well with my idea of good programming. Without calling super yourself, it isn't a true subclass; just a bastardization of composition that relies on a side effect of calling a convenience constructor. Saying this, I believe your method described will work OK, but it goes against the grain of good Objective-C programming and its conventions.
What I would suggest is to use MKPolyLine as an MKPolyLine instance, and use a category to add the extra bells and whistles you need. As for adding extra instance variables and such, you can use associated objects. An introduction to this concept can be found here, and this SO question addresses the use of them with categories: How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?
While it is generally allowed to create and return a different object in an init method, there are three problems with that line (explained below). Instead of this, I would suggest overriding the points and pointCount properties so that you can return values stored in an instance variable, and call the super implementation there if the instance variable is empty. Then, your initializer just sets these instance variables so that they will be used.
- (MKMapPoint *)points {
if(myPointsIvar == NULL) return [super points];
else return myPointsIvar;
}
// similarly for pointCount
The first problem is that you are creating a new object, but not releasing the old one, which means you are leaking it. You should store the result in a different variable, then release self, then return the result (you don't need to store it in self).
Second, polylineWithPoints:count: returns an autoreleased object, but initWithCoder: should return a retained one. Unless there is another retain on it, it could be deallocated while you are still using it.
If these were the only problems, you could solve both like this:
MKPolyline *result = [MKPolyline polylineWithPoints:points count:pointCount];
[self release];
return [result retain];
However, there is a third problem which cannot be solved so easily. polylineWithPoints:count: does not return a RSRoutePolyline object, and the object it returns may not be compatible with your subclass's methods (e.g. it probably won't support NSCoding). There really isn't a way to fix this, so you can't use polylineWithPoints:count:.
Could someone share some knowledge on whats best practice / code convention on using #property iVars in init methods or designated initializers?
please see my example:
#interface MyClass ()
#property(nonatomic,strong) nsstring *tempString;
#property(nonatomic,strong) NSMutableArray *arrItems;
#end
#implementation ViewController
- (id)init
{
if (self = [super init]) {
//Is this best practice / correct
_tempString = #"";
_arrItems = [[NSMutableArray alloc] initWithCapacity:0];
...
...
//Or this
self.tempString = #"";
self.arrItems = [[NSMutableArray alloc] initWithCapacity:0];
}
return self;
}
#end
Any advice on why one or the other should be used?
Thanks...
Apple's guidance on this topic is included in the aptly named section Don’t Use Accessor Methods in Initializer Methods and dealloc.
Read this thread: Why shouldn't I use Objective C 2.0 accessors in init/dealloc?
In other words if you are not goiung to use KVO you can use second approach:
//Or this
self.tempString = #"";
self.arrItems = [[NSMutableArray alloc] initWithCapacity:0];
But be care full with alloc-init, don't forget about autorelease.
It's typically better to use property notation when you define it, partly(mostly?) for the reason Jeremy mentioned.
Debugging a particular variable is a whole lot easier when you can set a breakpoint in method setter override and have it apply to ALL code paths that modify the variable.
Another reason is to keep a consistent memory management model, although it is less important since you are using ARC. If you weren't however, and strong was retain, then you would make sure that the object you are setting to the property is autoreleased everywhere you set the property, and not have to deal with releasing the current value if you are directly setting the variable.
Consistency is important for maintenance/readability and debugging, no matter what practices you use.
I prefer the lazy instantiation method for properties.
After you #synthesize you can override your getter to lazily instantiate your property
For Example:
-(NSString *)tempString {
if(!tempString) {
_tempString = #"";
}
return _tempString;
}
and
-(NSMutableArray *)arrItems {
if(!_arrItems) {
_arrItems = [[NSMutableArray alloc] initWithCapacity:0];
}
return _arrItems;
}
If you do want to set your property in the init method, use dot notation self.myProperty so that it uses the defined setter for the property and not the private class method directly.
According to Apple, you should not use accessors in init... or dealloc methods:
You should always access the instance variables directly from within
an initialization method because at the time a property is set, the
rest of the object may not yet be completely initialized. Even if you
don’t provide custom accessor methods or know of any side effects from
within your own class, a future subclass may very well override the
behavior.
Taken from this doc: Encapsulating Data.
I am accessing a dispatched notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleUnpresent:) name:UNPRESENT_VIEW object:nil];
...
-(void)handleUnpresent:(NSNotification *)note;
{
NSLog(#"%#", note.object.footer);
//property 'footer' not found on object of type 'id'
}
Some of the incoming note.object objects have a "footer" and some don't. However, I don't want to go through to trouble of making a class that only has a property called footer just to make this work. I even tried ((NSObject *)note.object).footer) which works in some languages, but apparently not obj-c. What can I do?
Checking the isKindOfClass is certainly the more robust option. However, if you have multiple unrelated classes that return the property you need, there is another way: respondsToSelector. Just ask if the object has a footer method, and you can safely call it.
-(void)handleUnpresent:(NSNotification *)note;
{
id noteObject = [note object];
if ([note respondsToSelector:#selector(footer)])
{
NSLog(#"Footer = %#", [noteObject footer]);
}
}
That respondsToSelector method is powerful and handy in the right places, but don't go wild with it. Also, it can't tell you anything about the return type, so the footer you get may not be of the class you were expecting.
The syntax for noteObject.footer and [noteObject footer] are easy to treat as equivalent. However, when the class of noteObject is unknown, the compiler will accept the latter but not the former. If noteObject has a defined class that doesn't usually respond to footer, it will give a warning, but still compile and run. In these cases, it is your responsibility to guarantee that the method will indeed exist when needed, and therefore that the method call won't crash at run time.
If the object passed in the notification may be one of a number of classes and you don't want to cast the object to a specific class you can use performSelector: to call the footer method on the object. If you wrap this call with a respondsToSelector: you'll avoid an exception if the object turns out not to have a footer method.
-(void)handleUnpresent:(NSNotification *)note;
{
if ([[note object] respondsToSelector:#selector(footer)]) {
NSString *footer = [[note object] performSelector:#selector(footer)];
NSLog(#"%#", footer);
}
}
Using performSelector will stop the compiler complaining that the method "'footer' not found on object of type 'id'."
NSObject doesn't have any property named footer, which is why the compiler is complaining. Casting an id back to an NSObject doesn't help. If you know the object is always going to be some custom object you've created, you can cast back to that and then call footer and the compiler won't complain. It's best to actually check tho. See the example below (for the example, I named the class that has the footer property ViewWithFooter, so rename appropriately):
- (void)handleUnpresent:(NSNotification*)note
{
ViewWithFooter view = (ViewWithFooter*)[note object];
NSParameterAssert([view isKindOfClass:[ViewWithFooter class]]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}
If you have a bunch of unrelated classes (i.e., not in the same class hierarchy) that all present a footer property, you'd be best served creating a protocol with the required footer property and casting the object to the protocol in the code example above and asserting it responds to the -footer selector.
Here's an example using the protocol:
#protocol ViewWithFooter <NSObject>
- (UIView*)footer; // this could also be a readonly property, or whatever
#end
- (void)handleUnpresent:(NSNotification*)note
{
id<ViewWithFooter> view = (id<ViewWithFooter>)[note object];
NSParameterAssert([view respondsToSelector:#selector(footer)]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}