e.g. a Person class has a property #property (strong, nonatomic) NSString *name; and two methods: an initialisation method - (instancetype)initWithName:(NSString *)name; and another method - (void)doSomethingWithName:(NSString *)name; as follows:
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
self.name = name;
[self doSomethingWithName:name];
}
return self;
}
- (void)doSomethingWithName:(NSString *)name
{
NSLog(#"My name is: %#", name)
}
While in this case we're passing the local variable name from - initWithName: to - doSomethingWithName:, we could achieve the same result by passing self.name to - doSomethingWithName:. In fact, we could even rewrite the - doSomethingWithName: method as such:
- (void)doSomethingWithName
{
NSLog(#"My name is: %#", self.name)
}
Which of these approaches is considered best practice and why?
The two approaches do different things.
The form
- (void)doSomethingWithName:(NSString *)name
Is more like a global function. It takes a parameter and does something with it.
The form that doesn't take a parameter is an instance method. You ask an object to do something with its name.
Neither is better than the other. It's a question of need/intent.
It's like asking a chef "What's better, a whisk or a spatula?" Neither is better than the other. They are different tools suited to different tasks.
Both approaches could be problematic: you should not use accessors or message self in your init method.
Think about what can happen if some subclass overrides name accessor or doSomethingWithName. A subclass would expect a fully initialised object, while the reality is that its init method is yet to be executed...
Related
I have trouble implementing a Key-Value Observer at my attempt to follow the MVC pattern. I have a controller class, a model class and a view class. I update my model from the controller class and I want to put a key value observer in my view class to monitor when a NSMutableArray changes in model (like through addObject) and then redraw itself automatically. I used answer in this thread to guide me: How to add observer on NSMutableArray?
Code so far:
From my Scene (using sprite kit if it matters). Setting of letters will be done from Ctrl class, this is just to test.
BarCtrl *barCtrl = [[BarCtrl alloc] init];
BarModel *barModel = [[BarModel alloc] init];
BarView *barView = [[BarView alloc] init];
barCtrl.barModel = barModel;
barCtrl.barView = barView;
barView.barModel = barModel;
ScrabbleDeck *sd = [[ScrabbleDeck alloc] init];
if([barModel addLetter:[sd getLetter] onSide:BarModelSideRight])
NSLog(#"Added letter");
BarModel.h
#import <Foundation/Foundation.h>
#import "Letter.h"
typedef NS_ENUM(int, BarModelSide) {
BarModelSideLeft,
BarModelSideRight
};
#interface BarModel : NSObject
#property (nonatomic, strong) NSMutableArray *addedLetters;
- (instancetype)init;
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side;
#end
BarModel.m
#import "BarModel.h"
#interface BarModel ()
#property (nonatomic) int capacity;
#end
#implementation BarModel
- (instancetype)init
{
self = [super init];
if (self) {
self.capacity = letterCapacity;
_addedLetters = [[NSMutableArray alloc] init];
}
return self;
}
// We'll use automatic notifications for this example
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"arrayLetter"]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side{
if([_addedLetters count] > _capacity){
return FALSE;
}
switch (side) {
case BarModelSideLeft:
[_addedLetters insertObject:letter atIndex:0];
return TRUE;
break;
case BarModelSideRight:
[_addedLetters addObject:letter];
return TRUE;
break;
default:
return FALSE;
break;
}
}
// These methods enable KVC compliance
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index
{
self.addedLetters[index] = object;
}
- (void)removeObjectFromDataAtIndex:(NSUInteger)index
{
[self.addedLetters removeObjectAtIndex:index];
}
- (id)objectInDataAtIndex:(NSUInteger)index
{
return self.addedLetters[index];
}
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes
{
return [self.addedLetters objectsAtIndexes:indexes];
}
- (NSUInteger)countOfData
{
return [self.addedLetters count];
}
#end
BarView.h
#import <SpriteKit/SpriteKit.h>
#import "BarModel.h"
#interface BarView : SKSpriteNode
#property (nonatomic, strong) BarModel *barModel;
#end
BarView.m
#import "BarView.h"
#implementation BarView
static char MyObservationContext;
- (instancetype)init
{
self = [super init];
if (self) {
//_barModel = [[BarModel alloc] init];
}
return self;
}
-(void)setBarModel:(BarModel *)barModel{
if(_barModel != barModel)
_barModel = barModel;
[_barModel addObserver:self
forKeyPath:#"arrayLetter"
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:&MyObservationContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Check if our class, rather than superclass or someone else, added as observer
if (context == &MyObservationContext) {
// Check that the key path is what we want
if ([keyPath isEqualToString:#"arrayLetter"]) {
// Verify we're observing the correct object
if (object == self.barModel) {
[self draw:change];
}
}
}
else {
// Otherwise, call up to superclass implementation
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void) draw: (NSDictionary*) change{
NSLog(#"KVO for our container property, change dictionary is %#", change);
}
#end
When I ru this I get this "error":
2014-08-31 00:23:02.828 Testing[329:60b] Added letter
2014-08-31 00:23:02.830 Testing[329:60b] An instance 0x17803d340 of class BarModel was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x17804eb50> (
<NSKeyValueObservance 0x1780cf180: Observer: 0x178111670, Key path: arrayLetter, Options: <New: YES, Old: YES, Prior: NO> Context: 0x100101428, Property: 0x17804eb80>
I tried to follow the instructions in error but can not find where to set break point. Please help me figure this out!
The error is pretty descriptive. You add self as an observer of a BarModel object. At some point that object gets deallocated. But you never remove self as an observer by calling removeObserver:forKeyPath:context:. You need to do that.
First, in setBarModel, make sure to remove self as an observer of the previous value of _barModel.
Next, you probably need to add a dealloc method that does the same thing.
There are multiple problems with the code. In addition to what Tom Harrington said with respect to the specific error that was logged about failing to remove the observation:
You implemented the indexed collection accessors for a (non-existent) property named "data". That is, they have "Data" in their name where the property name should be.
The indexed collection property is addedLetters. So, the indexed collection mutating accessors should be:
- (void)insertObject:(id)object inAddedLettersAtIndex:(NSUInteger)index;
- (void)removeObjectFromAddedLettersAtIndex:(NSUInteger)index;
You don't really need the non-mutating accessors, since you have an array-type public property with a normal getter (i.e. -addedLetters).
By the way, that property is of type NSMutableArray which it should not be. The property should be of type NSArray, backed by an instance variable of type NSMutableArray. That is, the mutability of the type (as opposed to the property) should not be exposed through the public interface. When you do this, you have to manually declare the instance variable (since it should differ from the type of the property and auto-synthesis will get it wrong), make the property copy instead of strong, and implement the setter yourself to do a mutable copy of the passed-in immutable array:
- (void) setAddedLetters:(NSArray*)addedLetters
{
if (addedLetters != _addedLetters)
_addedLetters = [addedLetters mutableCopy];
}
Once you have implemented the indexed collection mutating accessors with the correct names, you must use only those methods to mutate the collection (after initialization). In particular, your -addLetter:onSide: method must not directly operate on the _addedLetters instance variable. This is the part that makes the class KVO-compliant for that property. The mere presence of the indexed collection mutating accessors does not help. They must be used for all actual mutations.
Your implementation of +automaticallyNotifiesObserversForKey: is redundant. Automatic notification is the default.
The BarView class is key-value observing a key path "arrayLetter" on its _barModel object, but that's not the name of the property on BarModel. I suppose you meant to use the key path "addedLetters".
Finally, for proper adherence to MVC design, your view should not have a reference to your model. It should have a reference to the controller. The controller can reflect the model to the view (or, in theory, adapt a model of a different internal design to what the view expects). Or, in a more traditional non-KVO approach, the controller would actually tell the view when something has changed and give it the updated data it should show.
So, your controller could expose its own addedLetters property:
#property (readonly, copy, nonatomic) NSArray* addedLetters;
It could be implemented as a derived property, forwarded through to the _barModel object:
+ (NSSet*)keyPathsForValuesAffectingAddedLetters
{
return [NSSet setWithObject:#"barModel.addedLetters"];
}
- (NSArray*)addedLetters
{
return self.barModel.addedLetters;
}
Then, the view would have a reference to the controller and not the model, and it would key-value observe the "addedLetters" key path on the controller.
I'm still fairly new to Objective C so please bear with me:
My app has a Delegate, a Navigation Controller, and a View.
I also use a Singleton for "global" variables.
I know I can do this but it seems ungainly:
#import "GlobalData.h"
#synthesize ...
NSInteger junk;
NSInteger moreJunk;
-(void)myMethod{
GlobalData *globDat=[GlobalData getSingleton];
junk=globDat.someValue;
}
-(void)myOtherMethod{
GlobalData *globDat=[GlobalData getSingleton];
moreJunk=globDat.someOtherValue;
}
I'd like to this but it can't:
#import "GlobalData.h"
#synthesize ...
NSInteger junk;
NSInteger moreJunk;
GlobalData *globDat=[GlobalData getSingleton]; //Compiler won't allow this line
-(void)myMethod{
junk=globDat.someValue;
}
-(void)myOtherMethod{
moreJunk=globDat.someOtherValue;
}
However I can do this:
#import "GlobalData.h"
#synthesize ...
NSInteger junk;
NSInteger moreJunk;
GlobalData *globDat;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
globDat=[GlobalData getSingleton];
}
return self;
}
-(void)myMethod{
junk=globDat.someValue;
}
-(void)myOtherMethod{
moreJunk=globDat.someOtherValue;
}
So, is there a standard/common/proper "init" method to use for all Classes?
Should I just do this in every Class?
-(id)init{
if(self=[super init]){
globDat=[GlobalData getSingleton];
}
return self;
}
I know I can do this but it seems ungainly...
You seem to be asking how to do away with the part where you retrieve the value you want from your singleton. The best way to do that is to eliminate the singleton in the first place.
You say that you have an app delegate, a navigation controller, and a view. You probably also have a view controller. If those are the main objects in your application, you might consider storing your data in your view controller. Or, convert your singleton into a legitimate data model, and let your view controller keep a reference to it in a property. Then you can say something like:
-(void)myMethod{
junk = self.model.someValue;
}
which is pretty close to what you seem to be asking for.
It depends on how much you will use GlobalData.
extensive use in many methods
If you plan to make an extensive use of GlobalData in your class (in many methods), then, you should better add
#propertu (nonatomic, readwrite, weak)GlobalData *globalData in your .h
(or, better, in the class extension). In your main init... method, you set the value self.globalData = [GlobalData getSingleton];. You can also do as you did, with an instance variable, GlobalData* myGlobalData in your .h or in the class extension.
Make sure your init... method starts with init and make sure there is a "main" init... method (we call it the "designated initializer"). You can simply override the init method of NSObject. But, if you need, you can define
- (id)initWithString:(NSString*)str
{
self = [super init] ; // or another one ; here you should use the designated init. of the parent class
if (self){
self.string = str ;
self.globalData = [GlobalData getSingleton] ;
}
return self;
}
extensive use in few methods
If you intensively use GlobalData in one or two methods, you don't need to do so. You will just define a local variable GlobalData * globalData = [GlobalData getSingleton]; and use it.
The third code you propose is not objected-oriented.
I'm not 100% sure what you're question is really about (singletons as a common pattern vs common -init method?), but it is common to create what is called a designated initializer for your class. If you're not using a singleton pattern an init method signature that takes a value might look like this:
-(id)initWithValue:(NSString*)newValue;
As for using a singleton pattern to provide access to your variables I would recommend a more loosely coupled approach, especially if the number of values your singleton class manages begins to grow. I would put those variables into their own classes (separated logically) and use them as needed, by passing them to constructors, or instantiating them. That's an opinion/philosophy issue, though.
I ended up using this to resolve the question.
(Again I was primarily trying to clean out the redundancy of code)
In my Class modules I did the following:
#import "GlobalData.h"
GloablData *globDat;
-(id)init{
if(self=[super init]){
globDat=[GlobalData getSingleton];
}
return self;
}
...
-(void)someMethod{
int junk=globDat.junkvalue;
}
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.
My app crashed after I called addImageToQueue. I added initWithObjects: forKeys: count: but it doesn't helped me.
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[NSDictionary initWithObjects:forKeys:count:]:
method only defined for abstract class.
Define -[DictionaryWithTag initWithObjects:forKeys:count:]!'
my code
- (void)addImageToQueue:(NSDictionary *)dict
{
DictionaryWithTag *dictTag = [DictionaryWithTag dictionaryWithDictionary:dict];
}
#interface DictionaryWithTag : NSDictionary
#property (nonatomic, assign) int tag;
- (id)initWithObjects:(id *)objects forKeys:(id *)keys count:(NSUInteger)count;
#end
#implementation DictionaryWithTag
#synthesize tag;
- (id)initWithObjects:(id *)objects forKeys:(id *)keys count:(NSUInteger)count
{
return [super initWithObjects:objects forKeys:keys count:count];
}
#end
Are you subclassing NSDictionary? That's not a common thing to do in Cocoa-land, which might explain why you're not seeing the results you expect.
NSDictionary is a class cluster. That means that you never actually work with an instance of NSDictionary, but rather with one of its private subclasses. See Apple's description of a class cluster here. From that doc:
You create and interact with instances of the cluster just as you would any other class. Behind the scenes, though, when you create an instance of the public class, the class returns an object of the appropriate subclass based on the creation method that you invoke. (You don’t, and can’t, choose the actual class of the instance.)
What your error message is telling you is that if you want to subclass NSDictionary, you have to implement your own backend storage for it (for example by writing a hash table in C). It's not just asking you to declare that method, it's asking you to write it from scratch, handling the storage yourself. That's because subclassing a class cluster directly like that is the same as saying you want to provide a new implementation for how dictionaries work. As I'm sure you can tell, that's a significant task.
Assuming you definitely want to subclass NSDictionary, your best bet is to write your subclass to contain a normal NSMutableDictionary as a property, and use that to handle your storage. This tutorial shows you one way to do that. That's not actually that hard, you just need to pass the required methods through to your dictionary property.
You could also try using associative references, which "simulate the addition of object instance variables to an existing class". That way you could associate an NSNumber with your existing dictionary to represent the tag, and no subclassing is needed.
Of course, you could also just have tag as a key in the dictionary, and store the value inside it like any other dictionary key.
From https://stackoverflow.com/a/1191351/467588, this is what I did to make a subclass of NSDictionary works. I just declare an NSDictionary as an instance variable of my class and add some more required methods. It's called "Composite Object" - thanks #mahboudz.
#interface MyCustomNSDictionary : NSDictionary {
NSDictionary *_dict;
}
#end
#implementation MyCustomNSDictionary
- (id)initWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt {
_dict = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:cnt];
return self;
}
- (NSUInteger)count {
return [_dict count];
}
- (id)objectForKey:(id)aKey {
return [_dict objectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator {
return [_dict keyEnumerator];
}
#end
I just did a little trick.
I'm not sure that its the best solution (or even it is good to do it).
#interface MyDictionary : NSDictionary
#end
#implementation MyDictionary
+ (id) allocMyDictionary
{
return [[self alloc] init];
}
- (id) init
{
self = (MyDictionary *)[[NSDictionary alloc] init];
return self;
}
#end
This worked fine for me.