I am trying to create a method to quickly and easily create an NSArray from a va_list, however, when I run the method, I receive an exc_bad_access due to some bad memory management somewhere, although I cannot determine where this place is.
Please could you take a look at the code and tell me where and why this is occurring.
Thanks in advanced,
Max.
NSArray *arrayCreate(id firstObject, ...) {
NSMutableArray *objects = [NSMutableArray array];
[objects addObject:firstObject];
va_list args;
va_start(args, firstObject);
id arg;
while ((arg = va_arg(args, id))) {
[objects addObject:arg];
}
va_end(args);
return [objects copy];
}
Usage (just to test that it's working):
NSLog(#"%#", arrayCreate(#"1", #"2", #"3", #"4"));
You forgot to nil-terminate your arglist. In C, functions have no way of knowing how many variadic arguments you passed, so it's common to end a series of pointers with a null pointer (to indicate no more valid input.) Your code appears to be checking for this (arg = va_arg(args, id) will be false when it reaches nil) but your input is missing it.
Related
When registering to observe an object via KVO I write this code to avoid hardcoded strings:
[myObject addObserver:self
forKeyPath:NSStringFromSelector(#selector(myProperty))
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:NULL];
Note the NSStringFromSelector. It protects me at compile-time in the event that I change the name of myProperty and forget I had something observing it.
But the situation is more complicated when using nested properties, e.g., "myProperty1.myProperty2"
My naive solution is to use macros like the following:
#define KEYPATHSTRING1(a) NSStringFromSelector(#selector(a))
#define KEYPATHSTRING2(a, b) [NSString stringWithFormat:#"%#.%#", NSStringFromSelector(#selector(a)), NSStringFromSelector(#selector(b))]
#define KEYPATHSTRING3(a, b, c) [NSString stringWithFormat:#"%#.%#.%#", NSStringFromSelector(#selector(a)), NSStringFromSelector(#selector(b)), NSStringFromSelector(#selector(c))]
Any better or standardized solutions out there? Searches on Google and SO turned up nothing for me that addressed this particular question.
I haven't tried this (or even compiling it), but you could look at using a variadic helper method:
+ (NSString *)keyPathFromSelectors:(SEL)firstArg, ...
{
NSMutableArray *keys = [NSMutableArray array];
va_list args;
va_start(args, firstArg);
for (SEL arg = firstArg; arg != nil; arg = va_arg(args, SEL))
{
[keys addObject:NSStringFromSelector(arg)];
}
va_end(args);
return [keys componentsJoinedByString:#"."];
}
I use EXTKeyPathCoding.h from libextobjc. You can check how they do same at https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTKeyPathCoding.h
Swift 3 adds #keyPath() to generate a key path string from a "property chain".
let keyPathString = #keyPath(MyType.myProperty1.myProperty2)
The tokens inside the function are not Strings and will be checked by the compiler.
I'm pretty sure this is eactly the same problem as in componentsJoinedByString gives me EXC_BAD_ACCESS
basically, an array is populated using this code, with ARC turned on:
-(NSMutableArray *)getArrayOfCommaSeparatedSelectorStrings{
NSMutableArray *Array = [[NSMutableArray alloc] init];
for(NSMutableArray *e in [self getArrayOfSelectorArrays]) {
[Array addObject:[displayCSSInformation returnArrayAsCommaList:e]];
}
return Array;
}
and then displayCSSInformation tries to return a comma separated list with this method :
+(NSString *)returnArrayAsCommaList:(NSMutableArray *)ToBeConverted{
NSString *test = [ToBeConverted componentsJoinedByString:#", "];
return test;
}
Thanks for your help.
There's usually no need to use a separate method if all that method does is invoke another method. Remove your +returnArrayAsCommaList: method and just use componentsJoinedByString: on the array directly.
- (NSMutableArray *) getArrayOfCommaSeparatedSelectorStrings
{
NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSMutableArray *e in [self getArrayOfSelectorArrays])
[array addObject:[e componentsJoinedByString:#", "]];
return array;
}
The above should work (it works in my small test example), if you are still getting errors:
Make sure that getArrayOfSelectorArrays is actually returning an array of array of strings. Log the output to the console or step through with a debugger.
Use the “Build & Analyze” option to have the static analyser check for any issues. This is less of an issue with ARC but it will still pick up things such as using uninitialised values.
Make sure you have properly bridged ownership from any Core Foundation objects.
The way I understand it one of the things special about Objective C is that you can send messages to NULL and it will just ignore them instead of crashing.
Why is it that NSArray doesn't just return a NULL object if the index requested is out of bounds instead of causing a NSRangeException?
What I would expect from Objective C and NSArray is the following.
NSArray *array = [NSArray arrayWithObjects:#"Object 1", #"Object 2", #"Object 3", nil];
for (int i = 0; i < 5; i++) {
NSString *string = [array objectAtIndex:i];
if (string) {
NSLog(#"Object: %#",string);
}
}
Allowing me to access indexes of the array with don't contain objects and simply returning NULL. Then I can check if the object exists. I can do this in other places such as checking if the object has been instantiated.
NSArray *array;
if (!array) {
array = [NSArray array];
}
I realize this is a theory based question but I'm curious :)
Messages to nil is a language level feature, not a framework API feature.
That NSArray (and other classes) throw an exception to indicate programmer error is a conscious design decision that was made when the API was designed (~1993). As both Kevin and Richard indicate, nil eats message is not erroneous in anyway whereas out of bounds is very much a case of erroneous input.
Where would you draw the line? Should substringWithRange: accept any old input? How about setObject:atIndex:?
By making NSArray #throw on bounds exceptions for indexOfObject: it makes the API consistent; any index (or range) that is out of bound will #throw.
You could use an NSMutableDictionary to accomplish what you're trying to do. Set the keys to be NSNumber indexes. Invoking objectForKey on a nonexistent key (out of our imaginary bounds or removed) will return nil.
I have this question here (as well other quesrtions on SO), and the Apple docs about Objective-C collections and fast enumeration. What is not made clear is if an NSArray populated with different types, and a loop is created like:
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
What exactly happens here? Will the loop skip over anything that is not an NSString? For example, if (for the sake of argument) a UIView is in the array, what would happen when the loop encounters that item?
Why would you want to do that? I think that would cause buggy and unintended behavior. If your array is populated with different elements, use this instead:
for (id object in myArray) {
// Check what kind of class it is
if ([object isKindOfClass:[UIView class]]) {
// Do something
}
else {
// Handle accordingly
}
}
What you are doing in your example is effectively the same as,
for (id object in myArray) {
NSString *string = (NSString *)object;
NSLog(#"%#\n", string);
}
Just because you cast object as (NSString *) doesn't mean string will actually be pointing to an NSString object. Calling NSLog() in this way will call the - (NSString *)description method according to the NSObject protocol, which the class being referenced inside the array may or may not conform to. If it conforms, it will print that. Otherwise, it will crash.
You have to understand that a pointer in obj-c has no type information. Even if you write NSString*, it's only a compilation check. During runtime, everything is just an id.
Obj-c runtime never checks whether objects are of the given class. You can put NSNumbers into NSString pointers without problems. An error appears only when you try to call a method (send a message) which is not defined on the object.
How does fast enumeration work? It's exactly the same as:
for (NSUInteger i = 0; i < myArray.count; i++) {
NSString* string = [myArray objectAtIndex:i];
[...]
}
It's just faster because it operates on lower level.
I just tried a quick example... Here is my code.
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:1];
NSNumber *number = [NSNumber numberWithInteger:6];
[array addObject:number];
[array addObject:#"Second"];
Now if I simply log the object, no problem. The NSNumber instance is being cast as an NSString, but both methods respond to -description, so its not a problem.
for (NSString *string in array)
{
NSLog(#"%#", string);
}
However, if I attempt to log -length on NSString...
for (NSString *string in array)
{
NSLog(#"%i", string.length);
}
... it throws an NSInvalidArgumentException because NSNumber doesn't respond to the -length selector. Long story short, Objective-C gives you a lot of rope. Don't hang yourself with it.
Interesting question. The most generic syntax for fast enumeration is
for ( NSObject *obj in myArray )
NSLog( #"%#\n", obj );
I believe that by doing
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
instead, you are simply casting each object as an NSString. That is, I believe the above is equivalent to
for ( NSObject *obj in myArray ) {
NSString *string = obj;
NSLog( #"%#\n", string );
}
I could not find precise mention of this in Apple's documentation for Fast Enumeration, but you can check it on an example and see what happens.
Since all NSObject's respond to isKindOfClass, you could still keep the casting to a minimum:
for(NSString *string in myArray) {
if (![string isKindOfClass:[NSString class]])
continue;
// proceed, knowing you have a valid NSString *
// ...
}
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"Hello World!", [NSURL URLWithString:#"http://www.apple.com"], nil];
for (id *object in array) {
NSLog(#"Class name: %#", [object className]);
}
Given the above array of varying objects what is the proper way to fast enumerate thru them? Using the above code I do see my log statement properly, but Xcode does complain with the following message
Invalid receiver type 'id*' on my NSLog statement.
That should be:
for (id object in array) {
// ...
That is because id already is a pointer, see the section on id in Apples The Objective-C Programming Language for details on it.