Objective-C. Fail to instantiate an object - objective-c

I have recently started my journey with Objective-C.
In my class KGApp, I have a method -(void)createArrayOfKGAppQuotes. In that method:
I take the long NSString of quotes and authors and split it into quotesArrayTemp.
Then I do the loop where,
I split each quote and its author and place it to the tempQuoteArray
Then I am trying to addObject to the property quotesArray that holds KGQuote objects by instantiating this object with the initializer that takes two NSStrings respectively.
-(void)createArrayOfKGAppQuotes {
NSArray<NSString *> *quotesArrayTemp = [self.stringOfQuotes componentsSeparatedByString:#"\n"];
for (NSString *quote in quotesArrayTemp) {
NSArray *tempQuoteArray = [quote componentsSeparatedByString:#"/"];
[self.quotesArray addObject:[[KGQuote alloc] initWithQuote:[tempQuoteArray objectAtIndex:0] andWithAuthor:[tempQuoteArray objectAtIndex:1]]];
}
}
I am sure that tempQuoteArray is being populated and it holds NSStrings. But quotesArray goes back as nil.
Many thanks for the help.

Related

NSArray of NSDictionary in Objective C

I am trying to develop a function to add Dictionaries into Arrays.
This function looks this way but doesn't work:
in the implementation of the CBLModel I have the property emailList. The property I want to be an NSArray of NSDictionary's.
-(void) addEmail: (NSDictionary*) c{
NSMutableArray* mutArrayEmail= [NSMutableArray arrayWithObject: self.emailList];
[mutArrayEmail addObject:c];
self.emailList = [NSArray arrayWithObject:mutArrayEmail];
}
And the implementation would be something like:
[persInf addEmail: email1];
[persInf addEmail: email2];
[persInf addEmail: email3];
Any help?
EDIT
Thanks to everyone for your reply.
I have inserted in the main code the following (in order to not test everything easier):
NSDictionary *inventory = #{
#"Mercedes-Benz SLK250" : [NSNumber numberWithInt:13],
#"Mercedes-Benz E350" : [NSNumber numberWithInt:22],
#"BMW M3 Coupe" : [NSNumber numberWithInt:19],
#"BMW X6" : [NSNumber numberWithInt:16],
};
NSMutableArray *nsmut= [[NSMutableArray alloc] init];
[nsmut addObject: inventory];
[nsmut addObject: inventory];
[nsmut addObject: inventory];
NSArray *nsarray= [NSArray arrayWithArray:nsmut];
[persInf setEmailList:nsarray];
After this I get an error. I post this code sample as may be easier to see where the error can be
You only want to create the array if it doesn't already exist:
- (void)addEmail:(NSDictionary *) c{
if (!self.emailList) {
self.emailList = [[NSMutableArray alloc] init];
}
[self.emailList addObject:c];
}
This is lazy initialization as the array is created on first use. This is fine, as long as you realise that the array can be nil at times in the object's lifetime (which is less of a problem in Objective-C than in other languages).
A more conventional approach is to override an init method and allocate the array there.
Declare your NSMutableArray outside of your addEmail: method. You can use addObject: as-is.
Since emailList is a property you should override the getter to lazily instantiate it when its needed, like so.
- (NSMutableArray *)emailList {
if(!_emailList) {
_emailList = [NSMutableArray array];
}
return _emailList
}
To be thorough you could write that code using the ternary and make it a bit shorter:
- (NSMutableArray *)emailList {
return _emailList = _emailList ?: [NSMutableArray array];
}
Finally once you've overrode the getter your addEmail method becomes:
- (void)addEmail: (NSDictionary*)c {
[self.emailList addObject:c];
}
If you have to store emailList as an NSArray, I guess you need to set it with
self.emailList = [NSArray arrayWithArray:mutArrayEmail];
I'm far from an expert, though, so there may be problems with this approach. I'd welcome comments to that effect!
You can declare additional property of NSMutableArray (it can be in your class extension):
#property(nonatomic, copy)NSMutableArray *tempArray;
Initialize it in the init
_tempArray = [[NSMutableArray alloc] init];
And change your setter method to:
-(void) addEmail: (NSDictionary*) c{
[self.tempArray addObject:c];
}
And change your getter to:
- (NSArray *)emailList {
return [NSArray arrayWithArray:[self.tempArray copy]];
}
I see you are new to SO, so a little (friendly!) advice: you need to provide more detail in your questions of exactly what you've tried and what has gone wrong; e.g. "After this I get an error." does not help others help you.
Now reading your question and comments two things pop out: assertions about CBLModel, and how to add items to an array.
A. First, CBLModel. You've stated a few times you need an NSArray as:
I can't declare it as Mutable array or it won't work in the CBLModel
What do you mean by this? If you are saying that the methods in CBLModel take NSArray type arguments then you can pass NSMutableArray's as an NSMutableArray is an NSArray (i.e. it is a subclass) - this is one of the central pillars of inheritance-based languages like Objective-C.
If instead you mean that CBLModel does not function correctly if you pass it an NSMutableArray, say because you yourself mutate the array after passing it and that effects CBLModel in an invalid way[*], then you can simply create an immutable copy ([NSArray arrayWithArray:someMutableArray]) or just another mutable copy ([someMutableArray copy]) which you pass only to CBLModel.
B. Adding items to an array. Let's look at your code, line by line, so you can see what it is doing:
NSMutableArray* mutArrayEmail= [NSMutableArray arrayWithObject: self.emailList];
This creates a new NSMutableArray initialised with a single element obtained from self.emailList, and stores a reference to it in mutArrayEmail. E.g. if before execution self.emailList references an array [1, 2, 3], then after execution mutArrayEmail references a (mutable) array [ [1, 2, 3] ] - a single element array whose element is itself an array.
[mutArrayEmail addObject:c];
You add the object referenced by c, so using the same example you now have [ [1, 2, 3], c ] - an array with two elements.
self.emailList = [NSArray arrayWithObject:mutArrayEmail];
And finally you create a new array with the single object referenced by mutArrayEmail. Keeping the same example you now have [ [ [1, 2, 3], c ] ]. That is probably not what you wanted, or what CBLModel accepts.
The class method arrayWithArray: creates an array with the same elements as another array. If you used that method the above example would produce [1, 2, 3, c], which is probably what you want.
HTH
[*] Any classes which accept objects of types with mutable subtypes [arrays, strings, etc.] would normally be written such that a mutation after the object is passed does not effect the behaviour in undesirable ways, i.e. the classes would copy objects if needed. This it is recommended that properties of such type are defined with the copy attribute.
It's a bad idea, generally, to expose a mutable array to the outside world if it's part of an object's state. It means the object's state can be amended without the object knowing about it.
Here's how I would do this:
Have a readonly property that lets people get the array.
Have a method to add objects to the array.
#interface MyClass : NSObject
#property (nonatomic, readonly, strong) NSArray* emailList;
-(void) addEmail: (NSDictionary*) newEmail;
#end
In the implementation, have an mutable instance variable to back the property, initialise it in your designated initialiser. Implement your add method.
// All code assumed to be using ARC
#implementation MyClass
{
NSMutableArray* _emailList;
}
#synthesize emailList = _emailList;
-(id) init
{
self = [super init];
if (self != nil)
{
_emailList = [[NSMutableArray alloc] init];
}
return self;
}
-(void) addEmail: (NSDictionary*) newEmail
{
[_emailList addObject: newEmail];
}
#end
Although, the actual object returned by the property is an NSMutableArray the rule is to program to the interface, which means that when an interface says it returns an immutable object, even if you know it's really a mutable subclass, you are not allowed to send it mutating methods.
If, however, you are paranoid, implement the getter for the property like this:
-(NSArray*) emailList
{
return [_emailList copy];
}

NSString blowing my mind away

I have three NSString properties declared like this:
#property(nonatomic,retain) NSString *currentPassword;
#property(nonatomic,retain) NSString *newPassword;
#property(nonatomic,retain) NSString *confirmPassword;
I initialize them in a viewDidLoad method:
currentPassword = [[NSString alloc]init];
newPassword = [[NSString alloc]init];
confirmPassword = [[NSString alloc]init];
The funny thing is that they are the same object after initialize them as different objects!
Is this some kind of compiler optimization?
Thank you
Is this some kind of compiler optimization?
Not quite. It's a special case value for a constant, and an optimization of a common concrete immutable type/value which has been implemented by the NSString class.
NSString is immutable. There's no reason multiple instances of the same empty string are needed. In such simple cases, -[NSString init] can take the form:
static NSString* const EmptyNSString = #"";
- (id)init
{
self = [super init];
[self release];
return EmptyNSString;
}
similarly, + [NSString string]:
+ (id)string
{
return EmptyNSString;
}
So there are a few static immutable objects which are used this way where it makes sense. Other obvious examples include + [NSArray array] and + [NSNumber numberWithBool:].
Each one of these constants can represent what would have been many many many thousands of unique allocations produced during your program's execution.
This happens to work because NSString as a class cluster: You are returned an object of one of many opaque types which implements the interface declared by NSString. Therefore, a NSMutableString type could then implement init appropriately:
- (id)init
{
self = [super init];
if (nil != self) { ... }
return self;
}
Finally, you should in almost all cases declare your NSString properties as copy.
As NSString objects are immutable (i.e. cannot be changed after they are created) and there's no sense in creating several different instances of the same immutable strings, system tries to reuse existing objects whenever possible.
Using constructor with no parameters may be one of examples. You can also check that +stringWithString: (and -initWithString:) also return the (retained) parameter string, and copy method in NSString is equivalent to retain.
Remember that optimization is only possible because we know NSString instance is not going to change and the same tests with NSMutableString most likely will to create new string instances.
P.S. About NSAssert usage:
NSAssert Generates an assertion if a given condition is false.
So your assert condition should be reversed:
NSAssert(currentPassword && newPassword && confirmPassword,#"nil field");
When you have NSString as a property you should specify the attribute 'copy' instead.
NSString is defined as an immutable type, so whenever the compiler can optimize things by combining identical strings, it should. if you use #"myString" in two separate places in your code, they will be referencing the very same object. #"" strings are of class NSConstantString

NSMutableDictionary error

I want to use NSMutableDictionary to cache some data i will use later. My custom object is following:
#interface MyData : NSObject {
NSRange range;
NSMutableArray *values;
}
#property (nonatomic, retain) NSMutableArray *values;
and implement:
- (id)init {
if (self = [super init]) {
values = [[NSMutableArray alloc] init];
}
return self;
}
and when i wanna cache it, i use it like this:
NSMutableDictionary *cache = [[NSMutableDictionary alloc] init];
NSString *key = #"KEY";
MyData *data = [[MyData alloc] init];
// save some data into data
[data.values addObject:"DATA1"];
[data.values addObject:"DATA2"];
//... ...
[cache setObject:data forKey:key];
My questions is the count of cache.values is zero when i retrieve this object later as follow:
[cache objectForKey:#"KEY"];
i can retrieve "data" and the object's memory address is the same as the address when i put it into cache.
what's wrong? i need some kind guys help, any info is helpful. thanks
As Carl Norum pointed out, you're passing C strings to addObject:. addObject:, as its name suggests, requires a pointer to a Cocoa object; a C string is a pointer to characters. You need to pass NSString objects there; for literal strings, this simply requires prefixing them with #: "Fred" is a constant C string, whereas #"Fred" is a constant NSString object.
Is cache an instance variable? It looks like it's not; it appears to be a local variable, which means you're creating a new dictionary object every time. That's why there's nothing you've added previously (to previous dictionaries) in the new one. It also means you're leaking those previous dictionaries, since you're not releasing them (not in the code you showed, anyway).
Make cache an instance variable and only create the dictionary when you don't already have one (i.e., when cache == nil). Creating the dictionary in your init method is one good way. And make sure you manage its lifetime appropriately, so you don't leak and/or crash.
First of all your objects your adding don't look right it should have an # before the string. Like #"DATA1"
Second when you add an object to a dictionary or an array it does not make an actual copy of it. It just creates a pointer to it so if those objects are destroyed or moved somewhere also they are also gone out of your dictionary. A better way to make a cache of your values would be to copy the objects like so:
MyData* cache = [[MyData alloc] init];
for (int i = 0; i < [data.values count]; i ++){{
[cache.values addObject:[NSString stringWithString:[data.values objectAtIndex:i]]];
}
Don't use a dictionary in this situation.

Replace array display method?

I am curious how I might override the description method that is used when you do the following (see below) for an object. I basically want to better format the output, but am unsure about how I might go about setting this up.
NSLog(#"ARRAY: %#", myArray);
many thanks
EDIT_001
Although subclassing NSArray would have worked I instead decided that I would add a category to NSArray (having not used one before) Here is what I added ...
// ------------------------------------------------------------------- **
// CATAGORY: NSArray
// ------------------------------------------------------------------- **
#interface NSArray (displayNSArray)
-(NSString*)display;
#end
#implementation NSArray (displayNSArray)
-(NSString*)display {
id eachIndex;
NSMutableString *outString = [[[NSMutableString alloc] init] autorelease];
[outString appendString:#"("];
for(eachIndex in self) {
[outString appendString:[eachIndex description]];
[outString appendString:#" "];
}
[outString insertString:#")" atIndex:[outString length]-1];
return(outString);
}
#end
gary
If you're doing this a lot, the easiest way to reformat the display of your array would be to add a new prettyPrint category to the NSArray class.
#interface NSArray ( PrettyPrintNSArray )
- (NSSTring *)prettyPrint;
#end
#implementation NSArray ( PrettyPrintNSArray )
- (NSString *)prettyPrint {
NSMutableString *outputString = [[NSMutableString alloc] init];
for( id item in self ) {
[outputString appendString:[item description]];
}
return outputString;
}
#end
Obviously you'd need to alter the for loop to get the formatting the way you want it.
I'm assuming that you myArray variable is an instance of the NSArray/NSMutableArray class.
When NSLog() encounters the # character in its format string, it calls the -description: method on the object. This is a method on the root class, NSObject from which all other Cocoa classes inherit. -description: returns an NSString allowing any object that implements this method to be passed into NSLog(#"#",anyObject) and have a nicely formatted output. The string returned can be anything you care to construct.
For your specific problem, you could subclass NSMutableArray and override the -description: method with your own implementation. Then utilise your subclass instead of NSMutableArray.
For more information on NSObject and -description: see Apple's docs.
From Formatting string objects:
NSString supports the format characters defined for the ANSI C functionprintf(), plus ‘#’ for any object. If the object responds to the descriptionWithLocale: message, NSString sends that message to retrieve the text representation, otherwise, it sends a description message.
So to customize array conversion to string you should change NSArray descriptionWithLocale: implementation. Here's an example of how you can replace object method in run-time.

Objective C /iPhone : Is it possible to re initialize an NSArray?

I read that non mutable data types can't be modified once created.(eg NSString or NSArray).
But can they be re-initialized to point to a different set of objects?
If so, do I use release to free any alloc from first time round in between uses? eg:
myArray declared as NSArray *myArray in interface, and as nonatomic/retain property.myArray set in initialization code to a point to an array of strings as follows.
self.myArray = [myString componentsSeparatedByString:#","];
But later I want to re-initialize myArray to point to a different set of strings
self.myArray = [myOtherString componentsSeparatedByString:#","];
Is it possible? Thanks...
It really depends what you mean with re-initialize. You can assign another immutable object to a pointer, because the pointers aren't constant.
Example:
#interface MyObj : NSObject {
NSString *name; // not needed in 64bit runtime AFAIK
}
#property(retain) NSString *name; // sane people use copy instead of retain
// whenever possible. Using retain can
// lead to some hard to find errors.
#end
/* ... another file ... */
MyObj *theObject = [[[MyObj alloc] init] autorelease];
theObject.name = #"Peter";
NSString *oldName = theObject.name;
NSLog(#"%#", theObject.name); // -> Peter
NSLog(#"%#", oldName); // -> Peter
theObject.name = #"Martin";
NSLog(#"%#", theObject.name) // -> Martin
NSLog(#"%#", oldName) // -> Peter
If the behavior above is what you want, that's fine.
If you want that last line to return Martin you're in trouble. Those are constant strings and are not meant to be modified. You could, if you really want, modify the memory of the object directly, but this is dangerous and not recommended. Use mutable objects if you need such behaviour.
Yes you can reinitialized the NSArray. Here is the sample code that i used to re-initialized the NSArray.
NSString *keywords = #"FirstName|LastName|Address|PhoneNumber";
NSArray *arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"First Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
arr = nil;
keywords = #"First_Name|Last_Name|_Address|_PhoneNumber";
arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"Second Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
Of course they can. Saying that an NSArray is immutable doesn't mean that an attribute of a class of that type cannot be changed. You can't change the content, but you can assign new content to it.
If you want to make also changing the reference impossible you should use const keyword.