Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
Imagine my class has following ivars/properties:
#property (nonatomic, copy) NSString *itemName;
#property (nonatomic, copy) NSString *serialNumber;
#property (nonatomic) int valueInDollars;
#property NSDate *dateCreated;
1) One way to initialize ivars of this class is like this:
// Designated initializer of this class
-(id) initWithItemName: (NSString*) name
valueInDollars:(int)value
serialNumber:(NSString *)sNumber
{
// Call the superclass's designated initializer
self = [super init];
if(self)
{
// Init properties
self.itemName = name;
self.serialNumber = sNumber;
self.valueInDollars = value;
dateCreated = [[NSDate alloc] init];
}
// Return the address of the newly initialized object
return self;
}
2) Another way I am thinking go initialize this class is for example is to write:
-(id) init
{
self = [super init];
if(self)
{
// basically do nothing
}
return self;
}
And then leave it up to the user who will be using the class to do initialization as he needs it, e.g.,
MyClass *object = [[MyClass alloc] init];
object.dateCreated = [[NSDate alloc] init];
[object.dateCreated someMethod];
object.itemName = "Hello";
object.someProperty = [[SomeClass alloc] init];
The thing with above I think is that some properties (as above) must be called a alloc/init before they can be used isn't it? And if user forgets to do so, then at most the app won't work as expected right? (It won't crash as we can send message to nil). What I wrote here seems to be the only problem with this way of initialization. What is your opinion?
ps. it is permitted as here too: http://developer.apple.com/library/ios/#documentation/general/conceptual/CocoaEncyclopedia/Initialization/Initialization.html
pps. Assuming ARC is used.
thanks for many replies, but basically I was interested in what are the possible problems with solution 2?
I think you'll find the answer in the document that you linked to:
Overriding init is fine for subclasses that require no additional data to initialize their objects. But often initialization depends on external data to set an object to a reasonable initial state.
So if your class is not in a reasonable state if the variables are not initialised to a proper value, you should use
- (id)initWithItemName:(NSString*)name valueInDollars:(int)value serialNumber:(NSString *)sNumber
Then in init you could either call your designated initialiser with default values, or if there are no reasonable default values disallow the use of init as described here on SO
I'd advise you to create a factory method that calls to the init method in order to combine allocation and initialization in the same step and also hiding the details of the initialization.
#interface CCAttachment()
#property (readwrite, strong, nonatomic) NSString *urlString;
#property (readwrite, strong, nonatomic) NSString *baseURLString;
#property (readwrite, strong, nonatomic) NSData *data;
#property (readwrite, strong, nonatomic) id object;
#property (readwrite, strong, nonatomic) AFHTTPClient *client;
#end
#implementation CCAttachment
//Init method
- (id)initWithURLString:(NSString *)aURLString baseURLString:(NSString *)aBaseURLString
{
self = [super init];
if (self)
{
self.urlString = aURLString;
self.baseURLString = aBaseURLString;
}
return self;
}
//Factory method
+ (instancetype)attachmentWithURLString:(NSString *)aURLString baseURLString:(NSString *)aBaseURLString
{
return [[self alloc] initWithURLString:aURLString baseURLString:aBaseURLString];
}
#end
They will provide a more uniform interface for creating instances. For example, if you later want to convert the above object to an nsmanagedobject, you would keep the same factory method and only change its implementation
+ (instancetype)attachmentWithURLString:(NSString *)aURLString baseURLString:(NSString *)aBaseURLString
{
CCAttachment *result = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(self.class) inManagedObjectContext:MOC];
result.urlString = aURLString;
result.baseURLString = aBaseURLString;
return result;
}
http://developer.apple.com/library/ios/#documentation/general/conceptual/CocoaEncyclopedia/ClassFactoryMethods/ClassFactoryMethods.html
As #Kreiri said - Depedns... But I think that if you want to prevent some stupid errors/mistakes you should give a user a tool called designated initializer. The less possibilities to crash the better your code is!
You should try to initialize ivars by yourself to prevent any type of crash. 1st approach bounds the user to input all parameters, and is safe. 2nd approach can cause crash a you mentioned yourself, but you can be on safe side if you initialize variables yourself in 2nd approach.
-(id) init
{
self = [super init];
if(self)
{
itemName = #"";
serialNumber = #"";
valueindollars = -1;
dateCreated = nil;//Better than garbage
}
return self;
}
As a good programming practice, you should initialize objects to nil to avoid access to any garbage value.
You should make yourself familiar with the basics of Object Oriented Design.
Generally, an instance of a class SHALL fulfill a crisp defined invariant after it has been created. That means, your instance (that includes all ivars) SHALL be in a particular, logical correct state after it has been initialized.
It's also required that after responding to any message the instance MUST still fulfill its invariants. This state may be different than before responding to the message, but it MUST still in a reasonable state.
Form your second design, a user can set and read any ivar at any time through the properties. Now suppose, your instance also responds to other messages which you have defined as methods in your class.
And now answer that question: is it guaranteed that at any time an instance of your class is in a state which fulfills the invariant conditions and thus can always respond to any messages in a clear and defined manner?
You will realize, that in the second approach, this is only the case when your class just serves as a container for the four ivars, and has effectively no other responsibilities. So, one should ask what's then the purpose of the class at all? ;)
As a general principle, it is bad style to create an object with an invalid state, because the user may forget that he has to set a valid state for some variable before using it.
Sometimes there are too many possible arguments, which would make the classical pattern (sometimes called “telescoping constructor”) cumbersome. Think of pizza ingredients for example initPizzaWithPepperoni:tomate:mozzarella:...etc. For this cases, you can use a builder pattern.
This example from “Effective Java” illustrates three different ways to initialize an object
builder: useful when there are too many possible arguments or their combinations are complex.
bean
“telescoping constructor”
main.m
int main(int argc, char *argv[]) {
#autoreleasepool {
NutritionFacts *facts = [NutritionFacts new];
// builder
facts = [[[[facts builder] calories:100] sodium:35] build];
// bean constructor pattern
facts.calories=100;
facts.sodium=35;
// telescoping constructor pattern
// [[NutritionFacts alloc] initWithCalories:100];
// [[NutritionFacts alloc] initWithCalories:100 sodium:35];
}
}
NutritionFacts+Builder.m
#interface NutritionFacts(Builder)
-(NutritionFactsBuilder*)builder;
#end
#implementation NutritionFacts(Builder)
-(NutritionFactsBuilder*)builder {
return [[NutritionFactsBuilder alloc]initWithNutritionFacts:self];
}
#end
NutritionFacts.m
#interface NutritionFacts : NSObject
#property (nonatomic, assign) NSUInteger calories, carbohydrate,
cholesterol, fat, fiber, protein, saturatedFat, sodium;
#end
#implementation NutritionFacts
#end
NutritionFactsBuilder.m
#interface NutritionFactsBuilder : NSObject
-(id)initWithNutritionFacts:(NutritionFacts*)facts;
#end
#implementation NutritionFactsBuilder {
NutritionFacts *_facts;
}
-(id)initWithNutritionFacts:(NutritionFacts*)facts {
self = [super init];
if (self){
_facts = facts;
}
return self;
}
-(BOOL)isValid {
// ... check valid ivar combos
NSAssert(YES,#"Always valid");
}
-(NutritionFacts*)build {
[self isValid];
return _facts;
}
-(instancetype)calories:(NSUInteger)calories {
_facts.calories = calories;
return self;
}
-(instancetype)sodium:(NSUInteger)sodium {
_facts.sodium = sodium;
return self;
}
// ...
#end
Related
I have 3 properties id_1, id_2, id_3
id_2 and id_3 are derived from id_1
id_1 can have public getter/setter
id_2 and id_3 only have readonly access.
So I need to override the setter for id_1 to set id_2 and id_3 for valid id_1
id_1 could come from NSUserDefaults which means in init, I need to set id_2 and id_3
So, I wanted to call setter of id_1 from init as if I was calling from outside of the class using ivar _id_1
That would give me a single implementation to set all the ids both during init phase or if called externally
My question is on following two lines that I have in my code as I am calling the setter for id_1 with argument as ivar _id_1
_id_1 = id_from_ns_user_defaults
[self setid_1:_id_1];
In few other SO articles I saw concerns around recursive loops
Custom Getter & Setter iOS 5
.h file
#interface UserCredentials : NSObject
#property (strong, nonatomic) NSString *id_1;
#property (readonly) NSString *id_2;
#property (readonly) NSString *id_3;
#end
.m file
#interface UserCredentials ()
#property (readwrite) NSString *id_2;
#property (readwrite) NSString *id_3;
#end
#implementation UserCredentials
- (id)init
{
self = [super init];
if (self) {
/* Is this valid in Objective-C */
_id_1 = id_from_ns_user_defaults
[self setid_1:_id_1];
}
return self;
}
- (void)setid_1:(NSString *)id
{
if (id && ![id isEqualToString:#""]) {
_id_1 = id;
_id_2 = convert2(_id_1);
_id_3 = convert3(_id_1);
}
}
#end
Your highlighted concern is around creating an assignment cycle. Because you are assigning to the ivar itself, you will not be creating a cycle. Remember that manipulating the ivar will not cause your getter/setter to be called -- it's just a pointer like any other pointer.
Setting an ivar to itself is not an issue unless you have done something in your setter implementation to make it an issue. In non-ARC systems, you could easily create a bad access error by implementing your setter with the wrong order:
- (void)setVal:(NSObject *)val {
[_val release];
_val = [val retain];
}
This is countered by using autorelease instead (or assigning to a temporary variable and releasing after the retain).
Most of the time, though, your setter won't be doing anything destructive when passed a new (or same) value. Your implementation does not do this.
I am creating a custom table-view cell generator and I want to pass in a reference to an Object created in a base class which will be mutated in a separate VC while the originating object class will reflect these changes:
//MutableTableViewObjects.h
#interface MutableTableViewObjects : NSObject
#property (retain, atomic) NSDate *startDate;
//MutableTableViewObjects.m
- (id) init {
self = [super init];
if (self) {
self.startDate = [[NSDate alloc] init];
}
return self;
}
then I want a call in a separate view controller to change the value of startDate
self.objectClass = [[MutableTableViewObjects alloc] init];
DateOptionCellInput *startDateCell = [[DateOptionCellInput alloc] initDateInputForObject:self.objectClass.startDate withDefault:[NSDate date] withTitle:#"start date" inSection:#"Dates"];
From here I want startDateCell to modify the value of startDate and have self.objectClass.startDate reflect the changes happening to its value in other classes/threads
Is this even possible or should I seriously reconsider the architecture?
Currently the value changes in the VC but it is never updated in the base object class. Is there some property parameters I can pass in to allow this?
edit: here is how date cell will
#interface BaseOptionCellInput : NSObject
#property (atomic) NSObject* observedObject;
- (id) initType:(NSString*) optionType withTitle:(NSString*) titleString inSection:(NSString*) sectionHeaderString {
self = [super init];
if (self) {
self.title = titleString;
self.sectionHeader = sectionHeaderString;
self.identifier = optionType;
}
return self;
}
- (void) setManagedObject:(NSObject*) managedObject withDefaultValue:(NSObject*) defaultvalue {
self.observedObject = managedObject;
self.defaultValue = defaultvalue;
}
- (void) updateContextWithValue:(NSObject*) newValue {
if ([self.observedObject class] == [newValue class]) {
self.observedObject = newValue;
}
}
and then DateCell is a subclass of BaseCell and calls updateContextWithValue from a delegate method triggered in the VC.
I know I can accomplish this if I used a dictionary to pass the values of objects back and forth but I was hoping it was possible just passing a pointer to the object?
I imagine in initDateInputForObject function you are just using the value from the passed in object and assigning it to startDateCell's member variables.
What you want to do is probably keep a pointer to the MutableTableViewObjects object so you can directly modify the date value in there.
If you can add the code for MutableTableViewObjects I can elaborate with better clarity rather than shoot arrows in the dark :)
But hopefully you get the idea of what I am saying. Point being that no where in your code the orig MutableTableViewObjects object values are being updated.. Just because you used it to initialize another object doesn't mean it will stay updated when you change second object.
EDIT:
Lets say I have an class A with NSDate*
A.h
#interface A : NSObject
#property NSDate *startDate;
}
with A.m
- (id) init {
self = [super init];
if (self) {
self.startDate = [[NSDate alloc] init];
}
return self;
}
Now there is a class B as follows that contains an object of A.
B.h
#interface B : UITableViewController
#property (strong, nonatomic) A *aObject;
#end
and B.m
- (void) some function {
self.aObject = [[A alloc] init];
}
Now if you change self.aObject.startDate, you should be able to change the value of data in A directly.
If its not working as expected, tell me exactly what it is and where it is happening
thx
I have a subclass of NSObject. In this class I have an NSUInteger declared like this:
#interface Deck : NSObject
- (id)initDecks:(NSUInteger)decks;
- (Card *)drawCard;
#property (nonatomic, assign) NSUInteger drawnCards;
In my implementation file, I have the following code:
#import "Deck.h"
#implementation Deck
- (id)initDecks:(NSUInteger)decks {
self = [super init];
if (self) {
self.drawnCards = 1;
}
return self;
}
- (BJCard *)drawCard {
self.drawnCards++;
return nil;
}
The number assigned to the NSUInteger (drawnCards) in the init method is set correctly, however, I am not able to change it later. I get no warnings or crashes in Xcode, but the number remains unchangeable. I have tried to do self.drawnCards++ and self.drawnCards = 10 etc, but nothing works. Any idea what might be wrong with my code? I am checking the value with:
NSLog(#"Value: %tu", self.drawnCards);
I believe the problem here is the objectiveC dot syntax. Because you are doing . with an object this is the equivalent to doing [self drawnCards]++ method call. The result of the method is incremented, not the drawnCards ivar. To do what you want you'll either need to do _drawnCards++ (access the iVar directly), or you'll need to do [self setDrawnCards:self.drawnCards++].
There are many questions concerning the category-properties problem.
I know some possibilities to address this:
use a singleton registry
objc_setAssociatedObject and objc_getAssociatedObject
From my point of view both is not clean since the memory allocated is never cleared when the object that created such properties is deallocated.
Categories are a good way to keep code clean and dynamically add functionality to already existing classes. They help to group functionality and to distributed implementation work among more developers.
The bad about categories is the missing storage.
I came across this problem several times now and I'm wondering whether the following would address this problem in an clean way that also takes care about the memory and if there are any problems that I can't see right now.
There is one restriction, that I can ignore since I'm working as a framework developer: I'm able to create my own root class that all my other classes can inherit from.
First of all declare the new root object:
#interface RootObject : NSObject
- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key;
- (id)runtimePropertyForKey:(id)key;
#end
With the corresponding implementation:
#import "RootObject.h"
#interface RootObject ()
#property (readwrite) NSMutableDictionary *runtimeProperties;
#end
#implementation RootObject
#synthesize runtimeProperties = _runtimeProperties;
- (id)init {
self = [super init];
if (self)
{
_runtimeProperties = [[NSMutableDictionary alloc] initWithCapacity:1];
}
return self;
}
- (void)dealloc {
[_runtimeProperties release];
_runtimeProperties = nil;
[super dealloc];
}
- (id)runtimePropertyForKey:(id)key {
return [self.runtimeProperties objectForKey:key];
}
- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key {
if (key)
{
if (runtimeProperty)
{
[self.runtimeProperties setObject:runtimeProperty forKey:key];
}
else
{
[self.runtimeProperties removeObjectForKey:key];
}
}
}
#end
By using this RootObject instead of NSObject it should be very easy to add a "property" to a category on a class. Consider having some class MyClass
#interface MyClass : RootObject
// some interface here
#end
When implementing a special behavior on top of this class you are now able to add a property like this:
#interface MyClass (specialBehavior)
#property (nonatomic, retain) NSString *name;
#property (nonatomic, copy) NSDate *birthday;
#end
With corresponding implementation:
#implementation MyClass (specialBehavior)
#dynamic name;
- (NSString *)name {
return [self runtimePropertyForKey:#"name"];
}
- (void)setName:(NSString *)name {
[self setRuntimeProperty:name forKey:#"name"];
}
#dynamic birthday;
- (NSDate *)birthday {
return [self runtimePropertyForKey:#"birthday"];
}
- (void)setBirthday:(NSDate *)birthday {
[self setRuntimeProperty:[birthday copy] forKey:#"birthday"];
}
#end
Such an implementation could KVO compatible as well by just adding the necessary calls in the setter method.
Very straight forward, but I'm wondering whether I missed something important? (E.g. very very bad runtime performance having many such declared properties or using many of these objects)
This is effectively the same as objc_setAssociatedObject and objc_getAssociatedObject, which do release memory when the object is deallocated (depending on the association type). I would guess they also have much lower overhead than your suggested code.
I'm having trouble wrapping my thoughts about class inheritance. I'm suppsed to create a dashboard like interface in a app, and I'll have maybe 10 widgets/dashlets on that dashboard view. All those dashlets/widgets will have basically same look, with a title on the top, borders, row of buttons on the top and a graph.
Let's say I create a subclass of UI View called 'Dashlet' with properties and outlets, and create XIB file with proper layout and connected outlets etc.
Now I want to create several subclasses of that 'Dashlet' view that will only process data differently, and draw different graphs. My current code looks something like this:
Dashlet.h
#interface Dashlet : UIView{
#private
UILabel *title;
UIView *controls;
UIView *graph;
}
#property (weak, nonatomic) IBOutlet UILabel *title;
#property (weak, nonatomic) IBOutlet UIView *controls;
#property (weak, nonatomic) IBOutlet UIView *graph;
-(Dashlet*)initWithParams:(NSMutableDictionary *)params;
-(void)someDummyMethod;
#end
And in Dashlet.m
- (id) init {
self = [super init];
//Basic empty init...
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(id)initWithParams:(NSMutableDictionary *)params
{
self = [super init];
if (self) {
self = [[[NSBundle mainBundle] loadNibNamed:#"Dashlet" owner:nil options:nil] lastObject];
//some init code
}
return self;
}
Now let's say that I create a subclass called CustomDashlet.h:
#interface CustomDashlet : Dashlet
#property (nonatomic, strong) NSString* test;
-(void)testMethod;
-(void)someDummyMethod;
#end
and CustomDashlet.m
-(id)init{
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(id)initWithParams:(NSMutableDictionary *)parameters
{
self = [super initWithParams:parameters];
if (self) {
//do some stuff
}
return self;
}
This, kind of works, but I need to override some of the methods declared in the superclass or even add some of my own. Whenever i try to do something like this in CustomDashlet.m
[self someDummyMethod] or even [self testMethod] I get an exception error like this:
NSInvalidArgumentException', reason: '-[Dashlet testMethod]: unrecognized selector sent to instance
Am I even doing this right? Did I miss something? Am I supposed to make this work in some other way? If anyone has suggestions, please feel free to share your thoughts, thank you for all the help.
The problem is that
SalesDashlet *sales = [[SalesDashlet alloc] initWithParams:nil];
does not return a SalesDashlet instance, as expected, but a Dashlet instance.
Here is what happens:
[SalesDashlet alloc] allocates an instance of SalesDashlet.
The subclass implementation of initWithParams: is called with this instance,
and calls self = [super initWithParams:parameters].
The superclass implementation of initWithParams discards self and
overwrites it with a new instance loaded from the Nib file. This is an instance
of Dashlet.
This new instance is returned.
Therefore SalesDashlet *sales is "only" a Dashlet, and calling any subclass
method on it throws an "unknown selector" exception.
You cannot change the type of objects loaded in the Nib file. You could create a second
Nib file containing a SalesDashlet object. If the main purpose of the subclass is
to add additional methods, then the easiest solution would be to add these methods
in a Category of the Dashlet class.
If the problem is with the
- (Dashlet *)initWithParams:
method it is because the base class declares it with a Dashlet return value, whereas the subclass is redeclaring it with a SalesDashlet return instance.
Always use instancetype as the return type for any init method.
I believe you simply need to change following line in your Dashlet.h file:
-(Dashlet*)initWithParams:(NSMutableDictionary *)params;
to following:
-(id)initWithParams:(NSMutableDictionary *)params;
or better:
-(instancetype)initWithParams:(NSMutableDictionary *)params;
You need to change your init methods.
-(Dashlet*)initWithParams:(NSMutableDictionary *)params
-(SalesDashlet*)initWithParams:(NSMutableDictionary *)parameters
The return type on both of these should be id.
The problem you're running into is similar to trying to do this:
NSMutableArray *someArray = [[NSArray alloc] init];
Despite declaring someArray as an NSMutableArray, you've initialized it as an NSArray, and as such, someArray will actually be an immutable NSArray.
So because your SalesDashlet init method calls its super init method and the super explicitly returns an object of type Dashlet, then the SalesDashlet will also return an object of type Dashlet, so you're trying to call testMethod (a method that only exists in SalesDashlet) on an object of type Dashlet (which doesn't know about the testMethod method).
Changing your return type to id will make the methods return an object of the right type.
As a note, you've done your init, and initWithFrame methods correctly.
SalesDashlet *mySalesDashlet = [[SalesDashlet alloc] initWithFrame:someFrame];
Creating a SalesDashlet in this way will allow you to call [mySalesDashlet testMethod].
Your initWithFrame has return type of id in both super and sub classes.