If I create a custom initWith for an object do I essentially include the code I would add should I want to override init?
-(id) init {
self = [super init];
if (self) {
NSLog(#"_init: %#", self);
}
return(self);
}
e.g.
-(id) initWithX:(int) inPosX andY:(int) inPosY {
self = [super init];
if(self) {
NSLog(#"_init: %#", self);
posX = inPosX;
posY = inPosY;
}
return(self);
}
gary
You can create one designated initializer that accepts all parameters that you want to make available in initialization.
Then you call from your other -(id)init your designated initializer with proper parameters.
Only the designated initializer will initialize super class [super init].
Example:
- (id)init
{
return [self initWithX:defaultX andY:defaultY];
}
- (id)initWithPosition:(NSPoint)position
{
return [self initWithX:position.x andY:position.y];
}
- (id)initWithX:(int)inPosX andY:(int)inPosY
{
self = [super init];
if(self) {
NSLog(#"_init: %#", self);
posX = inPosX;
posY = inPosY;
}
return self;
}
The designated initializer is -(id)initWithX:andY: and you call it from other initializers.
In case you want to extend this class you call your designated initializer from subclass.
I'd suggest creating one main initializer that handles most of the work. You can then create any number of other initializers that all call this main one. The advantage of this is if you want to change the initialization process, you'll only have to change one spot. It might look like this:
-(id) initWithX:(float)x {
if (self = [super init]) {
/* do most of initialization */
self.xVal = x;
}
return self;
}
-(id) init {
return [self initWithX:0.0f];
}
In this example initWithX: is our main initializer. The other initializer (init) simply calls initWithX: with a default value (in this case 0).
Yes, that's exactly how I do it. One slight change will cut out a line of code:
if (self = [super init]) {
As opposed to:
self = [super init];
if(self) {
For modern Objective-C ...
UDFile.h
#import <Foundation/Foundation.h>
#interface UDFile : NSObject
#property (nonatomic, strong) NSString *name;
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
#end
UDFile.m
#import "UDFile.h"
#implementation UDFile
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = [name copy];
}
return self;
}
- (instancetype)init {
return [self initWithPathname:#""];
}
Sometimes, you want to reuse some initialisation code and modify the behaviour only slightly for specific initialisers. In this case, I do the following:
- (id) init
{
self = [super init];
if (!self) return nil;
// These values are always initialised this way
ivar1 = 10;
ivar2 = #"HellO";
ivar3 = [[NSMutableArray alloc] initWithCapacity:10];
ivar4 = 22;
return self;
}
- (id) initWithIvar4:(int) aValue
{
// call -init on self, which will call -init on super for us, and set
// up ivar1, ivar2, ivar3, and ivar4.
self = [self init];
if (!self) return nil;
// Change ivar4 from the default 22 to whatever aValue is.
ivar4 = aValue;
return self;
}
Related
I have a question about initializers that overlaps with memory management. I'm confident that this is a perfectly functional initializer (even tho it calls setters in the init method which is discouraged) . . .
#synthesize age = _age, name = _name, delegate = _delegate;
- (id)initWithName:(NSString *)name Age:(int)age delegate:(MyDelegateClass *)delegate
{
if (self = [super init]) {
[self setName:name];
[self setAge:age];
[self setDelegate:delegate];
}
return self;
}
But what about this initializer? Do i need to allocate memory for those ivars or does it just work out of the box like this?
- (id)initWithName:(NSString *)name Age:(int)age delegate:(MyDelegateClass *)delegate
{
if (self = [super init]) {
_name = name;
_age = age;
_delegate = delegate;
}
return self;
}
A few things:
(void) return type won't work. Make it (id)
compiler will warn on the assignment in the conditional, use two lines
it's common practice to set ivars directly in init, not referring to self*
neither of your forms you suggest allocate memory. that's done when the object is allocated.
so ...
- (id)initWithName:(NSString *)name age:(int)age delegate:(MyDelegateClass *)delegate {
self = [super init];
if (self) {
_name = name;
_age = age;
_delegate = delegate;
}
return self;
}
Normally, you'll want to copy strings rather than assign them -- even though you've specified that name is an NSString*, the object it points to might actually be a NSMutableString. So do this:
_name = [name copy];
It's stepping into the ViewDidLoad of the main view controller, and hitting the line calling get all tweets, but I put a breakpoint in the getAllTweets of both the base and derived to see if it just wasn't hitting the derived like I expected.
#implementation WWMainViewControllerTests {
// system under test
WWMainViewController *viewController;
// dependencies
UITableView *tableViewForTests;
WWTweetServiceMock *tweetServiceMock;
}
- (void)setUp {
tweetServiceMock = [[WWTweetServiceMock alloc] init];
viewController = [[WWMainViewController alloc] init];
viewController.tweetService = tweetServiceMock;
tableViewForTests = [[UITableView alloc] init];
viewController.mainTableView = tableViewForTests;
tableViewForTests.dataSource = viewController;
tableViewForTests.delegate = viewController;
}
- (void)test_ViewLoadedShouldCallServiceLayer_GetAllTweets {
[viewController loadView];
STAssertTrue(tweetServiceMock.getAllTweetsCalled, #"Should call getAllTweets on tweetService dependency");
}
- (void)tearDown {
tableViewForTests = nil;
viewController = nil;
tweetServiceMock = nil;
}
The base tweet service:
#implementation WWTweetService {
NSMutableArray *tweetsToReturn;
}
- (id)init {
if (self = [super init]) {
tweetsToReturn = [[NSMutableArray alloc] init];
}
return self;
}
- (NSArray *)getAllTweets {
NSLog(#"here in the base of get all tweets");
return tweetsToReturn;
}
#end
The Mock tweet service:
#interface WWTweetServiceMock : WWTweetService
#property BOOL getAllTweetsCalled;
#end
#implementation WWTweetServiceMock
#synthesize getAllTweetsCalled;
- (id)init {
if (self = [super init]) {
getAllTweetsCalled = NO;
}
return self;
}
- (NSArray *)getAllTweets {
NSLog(#"here in the mock class.");
getAllTweetsCalled = YES;
return [NSArray array];
}
The main view controller under test:
#implementation WWMainViewController
#synthesize mainTableView = _mainTableView;
#synthesize tweetService;
NSArray *allTweets;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
allTweets = [tweetService getAllTweets];
NSLog(#"was here in view controller");
}
- (void)viewDidUnload
{
[self setMainTableView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
Since you're able to break in the debugger in viewDidLoad, what's the value of the tweetService ivar? If it's nil, the getAllTweets message will just be a no op. Maybe the ivar isn't being set properly or overridden somewhere else.
You should probably use the property to access the tweetService (call self.tweetService) rather than its underlying ivar. You should only ever access the ivar directly in getters, setters, and init (also dealloc if aren't using ARC for some crazy reason).
You also should not call loadView yourself, rather just access the view property of the view controller. That will kick off the loading process and call viewDidLoad.
Also, if you're doing a lot of mocking, I highly recommend OCMock.
i have a question about initializing a custom delegate.
Within MyScrollView initWithFrame method, there is the first position where i need to send my delegate. But it´s still unknown there, because i set the delegate within MyCustomView after the initializer.
How can i fix that, so the delegate gets called even within init?
Thanks for your help..
MyCustomView.m
self.photoView = [[MyScrollView alloc] initWithFrame:frame withDictionary:mediaContentDict];
self.photoView.delegate = self;
//....
MyScrollView.h
#protocol MyScrollViewDelegate
-(void) methodName:(NSString*)text;
#end
#interface MyScrollView : UIView{
//...
__unsafe_unretained id <MyScrollViewDelegate> delegate;
}
#property(unsafe_unretained) id <MyScrollViewDelegate> delegate;
MyScrollView.m
-(id) initWithFrame:(CGRect)frame withDictionary:(NSDictionary*)dictionary{
self.content = [[Content alloc] initWithDictionary:dictionary];
self = [super initWithFrame:frame];
if (self) {
//.... other stuff
// currently don´t get called
[self.delegate methodName:#"Test delegate"];
}
return self;
}
I am sure you have defined a:
- (id)initWithFrame:(CGRect)frame withDictionary:(NSDictionary *)dictionary;
Then, just pass the delegate, too:
- (id)initWithFrame:(CGRect)frame withDictionary:(NSDictionary *)dictionary withDelegate:(id<MyScrollViewDelegate>)del;
In the Implementation File:
- (id)initWithFrame:(CGRect)frame withDictionary:(NSDictionary *)dictionary withDelegate:(id<MyScrollViewDelegate>)del {
// your stuff...
self.delegate = del;
[self.delegate methodName:#"Test delegate"];
}
Use it:
self.photoView = [[MyScrollView alloc] initWithFrame:frame withDictionary:mediaContentDict withDelegate:self];
One option might be to pass in your delegate in your custom class's initializer:
-(id)initWithFrame:(CGRect)frame withDictionary:(NSDictionary*)dictionary delegate:(id)delegate
{
self = [super initWithFrame:frame];
if (self == nil )
{
return nil;
}
self.content = [[Content alloc] initWithDictionary:dictionary];
self.delegate = delegate;
//.... other stuff
// Delegate would exist now
[self.delegate methodName:#"Test delegate"];
return self;
}
My question is kind of weird, lets say I have a class inheriting from UITableViewCell called GenericTableViewCell and some more classes inheriting from GenericTableViewCell.
I want to be able to pass an argument to the GenericTableViewCell init method that will tell me which subclass of GenericTableViewCell should init this TableViewCell as.
Heres what I thought of but I know that it will fail because it has a recursive loop in it.
#implementation GenericTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier cellIdentifier: (CellIdentifier *) identifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
if ([identifier class] == [FirstIdentifier class]){
self = [[FirstTableViewCell alloc] initWithStyle:style reuseIdentifier:reuseIdentifier];
}
/// more else statements to check for other identifier cases
}
return self;
}
#end
Is there any way to do this? Or should I just check the identifier outside of the init function and by that decide which cell do I declare?
Yes, you can do this. It's a pattern you sometimes come across called a class cluster. If you are not using ARC, you must release the original value of self to stop a memory leak.
However, I would not do this. I would create a factory method in GenericTableViewCell.
+(GenericTableViewCell*) cellWithStyle: (UITableViewCellStyle)style
reuseIdentifier: (NSString *)reuseIdentifier
cellIdentifier: (CellIdentifier *) identifier
{
GenericTableViewCell* ret = nil;
if ([identifier class] == [FirstIdentifier class])
{
ret = [[FirstTableViewCell alloc] initWithStyle:style reuseIdentifier:reuseIdentifier];
}
else
{
// ....
}
return [ret autorelease];
}
You can eliminate the if statement by adding a method to CellIdentifier and overriding it in subclasseslike this:
// in CellIdentifier.m
-(id) classForCell
{
return [GenericTableViewCell class];
}
// in FirstIdentifier.m
-(id) classForCell
{
return [FirstTableViewCell class];
}
Then your factory method becomes
+(GenericTableViewCell*) cellWithStyle: (UITableViewCellStyle)style
reuseIdentifier: (NSString *)reuseIdentifier
cellIdentifier: (CellIdentifier *) identifier
{
return [[[[identifier classForCell] alloc] initWithStyle:style
reuseIdentifier:reuseIdentifier] autorelease];
}
The thing you should implement is called class cluster pattern. In your situation you shouldn't call initWithStyle:reuseIdentifier: on a subclass rather than in subclass' initializer:
In GenericTableViewCell:
- (id)initWithCustomIdentifier:(NSString *identifier) {
Class cellClass = NSClassFromString(identifier);
if (!cellClass) {
cellClass = [MyStandardTableViewCell class];
}
self = [[cellClass alloc] init];
return self;
}
In MyStandardTableViewCell (or any other subclass of GenericTableViewCell:
- (id)init {
self = [super initWithStyle:someStyle reuseIdentifier:NSStringFromClass([self class])];
if (!self) return nil;
// do extra setup here
return self;
}
In an NSObject subclass declaration is an array:
#interface theClass : NSObject {
NSMutableArray *myArray;
}
...
#end
In the implementation's initializer:
- (id)init
{
self = [super init];
if (self) {
[myArray initWithCapacity:50];
}
return self;
}
And in a method:
- (NSMutableArray *)theMethod:(NSArray *)someArray {
...
...
[myArray addObject:anObject];
...
return myArray;
}
Yet despite the class being instantiated in my controller, any number of messages to the method leave myArray without contents.
You haven't allocated the array yet. Replace your init code with the following:
- (id)init
{
self = [super init];
if (self) {
myArray = [[NSMutableArray alloc] initWithCapacity:50];
}
return self;
}