Can set to nil but not to a different thing - cocoa

i think i don't finish to understand all about memory and that stuff but this is my problem:
I have a variable defined idActual on a view that will be pushed (var defined in its header), i can read (NSLog(idActual)) and set it to nil without problems. BUT when i change its value i get an CFString error, that its supposed to be due to bad memory management, i've tried this:
i can do this: nextView.idActual = nil;
i cant do this:
a) nextView.idActual = #"1";
b) NSString *str = [NSString stringWithFormat:#"1"];
nextView.idActual = str;
c) NSString *str = [[NSString alloc] initWithFormat:#"1"];
nextView.idActual = str;
[str release];
a, b and c always give me the CFString error:
*** -[CFString isEqualToString:]: message sent to deallocated instance

It appears that the CFString (NSString) that is contained in nextView.idActual has already been released when you go to change the value. If you can post more of the related code, that would help.
I'm guessing that idActual is declared as #property(nonatomic,retain). When you try to set a new value into idActual, the setter method for that property is called (It's possible that the setter was automatically generated). The first thing that the setter method is doing is trying to compare the old value and the new value - and then it crashes.
When that setter method attempts to compare the new value to the old value, it runs into trouble because the old value is already deallocated.
Are you calling [nextView.idActual release] before you assign these new values ? If you are, comment out that line, and see if that fixes your problem. The auto-generated setter method will release the old value for you.

Related

KVO versus NSUndoManager

I have a property on my document class called passDescription, of type NSMutableDictionary. When I call setPassDescription:, the current value of the property is archived into an NSData instance using NSJSONSerialization. The property’s backing ivar, passDescription, is updated, and then an undo action is registered. The selector invoked by the action reconstitutes the NSData given to it and calls setPassDescription:.
Now, here’s the joker: passDescription is being observed using Key-Value Observing. Considerable experimentation and examination in Xcode’s debugger reveals that the old value and the new value are identical. (I know that this isn’t a pointer-aliasing issue, as that’s why I’m using an NSData instance. The NSData is created before I record the new value, making it independent of what it was created from.) Thus, when I press Command-Z to undo, nothing happens as the value that has just been restored is no different from the value that has been overwritten by the undo.
The only thing I can think of that may be causing this is that KVO is setting the passDescription ivar for me, before setPassDescription: gets called. Why would this be, and how could I prevent KVO from doing that? (I have confirmed that the setter isn’t being called twice. If it was, I would see double output in the debugger console.)
Here is the source for setPassDescription:.
- (void)setPassDescription:(NSDictionary *)param
{
NSLog(#"passDescription (before) = \n%#", passDescription);
NSError *error;
NSData *archivedOldValue = [NSJSONSerialization dataWithJSONObject:passDescription options:0 error:&error];
NSAssert(archivedOldValue != nil, #"Could not archive old pass description: %#", error);
NSData *blob = [NSJSONSerialization dataWithJSONObject:param options:NSJSONWritingPrettyPrinted error:&error];
if (blob == nil) #throw [NSException exceptionWithName:#"PBJSONException" reason:#"Could not serialize pass.json" userInfo:#{ #"error": error }];
[self.fileBrowserRoot fileWrapperWithName:#"pass.json"].fileContents = blob;
[passDescriptionLock lock];
[[self.undoManager prepareWithInvocationTarget:self] setPassDescriptionFromData:archivedOldValue];
passDescription = param;
[passDescriptionLock unlock];
NSLog(#"passDescription (after) = \n%#", passDescription);
// After the pass description has been set, refresh the list of build issues.
[self recheckForIssues];
}

Is alloc+initWithString: same as copy?

Basically, the question is - are the following essentially the same?
NSString *value1 = ...;
NSString *value2 = [[NSString alloc] initWithString:value1];
and
NSString *value1 = ...;
NSString *value2 = [value1 copy];
Conceptually, yes. However, there is one difference: alloc always creates a new string, whereas copy may return the same string.
In particular, immutable objects, such as immutable strings, are likely respond to copy by returning themselves rather than creating and returning a copy. (After all, if you can't change anything about the original, why would you really need a copy?) Mutable strings will respond to it by creating and returning a copy, as you'd expect.
initWithString: is in the middle: It may release the receiver and return the string you gave it, similar to how copy may return the receiver. However, if that happens, it means you wasted the creation of the string you created with alloc. With copy, you may not need to create any additional objects at all.
About the only reason to use alloc and initWithString: is if you have your own subclass of NSString and want to make an instance of it from an existing string. copy won't use your desired subclass. Since subclassing NSString is practically never warranted in Cocoa, the same is true of using initWithString: (or stringWithString:).
So the bottom line is, just use copy (or mutableCopy). It's shorter, clearer about your intent, and can be faster.
Non-mutable strings are treated a bit special, compared to ordinary objects, so in this case, yes, the two operations are the same.
To wit:
NSString *str1 = #"string";
NSString *str2 = [str1 copy];
NSString *str3 = [[NSString alloc] initWithString: str1];
NSLog(#"str1: %p, str2: %p, str3: %p", str1, str2, str3);
Which gives me the following output:
str1: 0x108a960b0, str2: 0x108a960b0, str3: 0x108a960b0
Since the pointer addresses are the same, we are talking about the same object.

What is the better way of handling temporary strings?

I have a situation where I need to use some strings temporarily but I've read so many conflicting things that I'm a bit confused as to what the best way to proceed is.
I need to assign some strings inside an if structure but use them outside the if structure so they need to be created outside the if, I was thinking something like:
NSString *arbString = [[NSString alloc] init];
if(whatever)
{
arbString = #"Whatever"
}
else
{
arbString = #"SomethingElse"
}
myLabel.text = arbString;
[arbString release];
I have seen examples where people just used:
NSString *arbString;
to create the string variable
Google's Objective C guide says it's preferred to autorelease at creation time:
"When creating new temporary objects, autorelease them on the same line as you create them rather than a separate release later in the same method":
// AVOID (unless you have a compelling performance reason)
MyController* controller = [[MyController alloc] init];
// ... code here that might return ...
[controller release];
// BETTER
MyController* controller = [[[MyController alloc] init] autorelease];
So I have no idea, which is the best practice?
In the example you posted, you actually lose the reference to the NSString you created when you assign it in arbString = #"Whatever". You then release the string constant (which is unreleasable, by the way).
So there's a memory leak, since you never release the NSString you created.
Remember that all these types are pointers, so = only reassigns them.
As for the question, in this example, you don't need the [[NSString alloc] init]. You don't need to copy the string into a local variable anyway, you can just set myLabel.text to the string constant #"Whatever".
(edit: that's not to say that you can't use your pointer arbString, arbString = #"Whatever"; myLabel.text = arbString is fine. But this is just pointer assignment, not copying)
If you needed to manipulate the string before you returned it, you would create an NSMutableString, and either release or auto-release it. Personally, create autoreleased objects using class methods, so in this example, I'd use [NSString string], or [NSString stringWithString:], which return autoreleased objects.

pathForResource doesn't work

I have problem with
NSString *filePaht = [[NSBundle mainBundle] pathForResource:(NSString *)name ofType:(NSString *)ext];
if I used
NSString *filePaht = [[NSBundle mainBundle] pathForResource:#"soundName" ofType:#"aiff"];
it's OK. but when I used
NSString *fileName = [[file.list objectAtIndex:index] objectForKey:#"soundName"];
NSString *filePaht = [[NSBundle mainBundle] pathForResource:fileName ofType:#"aiff"];
It's not work
have any idea !?
Thanks
I am going to guess that fileName from file.list includes the file extension. So you are searching for "soundName.aiff.aiff" which does not exist. Try passing #"" for type or stripping the extension from fileName:
fileName = [fileName stringByDeletingPathExtension];
Check your Debugger Console, as it may be telling what you're doing wrong.
[file.list objectAtIndex:index]
If you're getting an NSRangeException, it may be because index contains an index that is outside the bounds of the array. Remember that arrays in Cocoa are serial, not associative; if you remove an object, the indexes of all the objects that came after it will go down by 1, upholding the invariant that 0 ≤ (every valid index) < (count of objects in the array).
It could also be because you never declared a variable named index.
NSString *fileName = [[file.list objectAtIndex:index] objectForKey:#"soundName"];
NSString *filePaht = [[NSBundle mainBundle] pathForResource:fileName ofType:#"aiff"];
If nothing is happening or you get an NSInternalInconsistencyException, it could be one of:
fileList is nil.
The dictionary returned from [file.list objectAtIndex:index] does not have an object for the key soundName.
If you got a “does not respond to selector” message in the Console, it may be one of:
file.list is an object, but not an NSArray.
[file.list objectAtIndex:index] is not an NSDictionary.
fileName ([[file.list objectAtIndex:index] objectForKey:#"soundName"]) is not an NSString.
Remember that the class name you use when you declare the variable doesn't matter except to the compiler; at run time, it's just a variable holding a pointer to an object. The object can be of any class. It is perfectly valid to put something that isn't an NSString into an NSString * variable; it simply carries a very high (near certain) risk of wrong behavior and/or crashing shortly thereafter.
Such a crash will usually manifest in the form of a “does not respond to selector” exception (after something sends the object a message that NSString objects, for example, should respond to, but that the object doesn't respond to because it isn't an NSString).
Whichever problem you're having, you can use the Debugger to investigate.
Sorry with my fault.
I get data from XML file
and that data include "\n". yes I see "\n" so I replace with #""
but it not enough I must trim space value again.
Thanks for all advice ^_^

Attempt to insert nil

Seems like it should be easy to add a boolean to an NSMutableArray.
Assume toDoArray is intialized as an NSMutableArray. The following:
BOOL checkBoxState = NO;
[toDoArray addObject:checkBoxState];
Generates the error "attempt to insert nil."
What's the correct way to add a negative boolean to a mutable array?
As others have said, NSMutableArray can only contain Objective-C objects. They do not have to be subclasses of NSObject, but that is the most typical.
However, long before you ever see the attempt to insert nil. runtime error, you should have seen a compiler warning:
warning: passing argument 1 of 'addObject:' makes pointer from integer without a cast
It is [in a vague and roundabout way] telling you exactly what the problem is; you are trying to stick something into an array that is not a pointer [to an object].
Pay attention to warnings and fix them. Most of the time, the presence of a warning will indicate a runtime error or crash.
NSMutable arrays require an id, a weird part of Objective C. An id is any object, but not a primitive (For example, ints are primitives, while NSArrays are objects, and in extension, ids).
This question might help.
You need using NSNumber to wrap any primitive types (BOOL, int, NSInterger, etc.) before placing it inside collection object (NSArray, NSDictionary, etc.).
Add BOOL to array:
BOOL checkBoxState = NO;
NSNumber* n = [NSNumber numberWithBool:checkBoxState];
[toDoArray addObject:n];
Get BOOL from array:
NSNumber* n = [toDoArray objectAtIndex:0];
BOOL checkBoxState = [n boolValue];

Resources