Objective-C: Boxing VS Literal - objective-c

Which construct should I use, why? I'll make an example with NSArray. The same apply to other "boxed" objects, such as NSDictionary, NSNumber...
NSArray *arr1 = #[object1, object2, object3];
or
NSArray *arr2 = [NSArray arrayWithObjects:object1, object2, object3, nil];

The two constructs produce identical effect for NSArray and NSDictionary objects. The difference is that the first syntax is not available prior to the 2012 version of the compiler, while arrayWithObjects: works with all versions of the language.
If you are not planning for your code to compile with older versions of the complier, the new syntax is gives you better readability, especially for NSNumber objects. Compare
NSArray *arr = #[#1, #2, #3];
vs.
NSArray *arr = [NSArray arrayWithObjects:
[NSNumber numberWithInteger:1]
, [NSNumber numberWithInteger:2]
, [NSNumber numberWithInteger:3]
, nil
];

Neither NSArray nor NSDictionary literals are "boxed". Boxing refers to when you make objects out of scalars or non-object types. Cocoa arrays and dictionaries are always objects.
Boxing is the relevant terminology for NSNumber, though. For example, 1 is an int, a scalar type (not an object). #1 is an NSNumber, the result of boxing 1. However, technically, I'd say that [NSNumber numberWithInt:1] is also boxing. It's just that #1 is a more convenient syntax.
There is a difference between:
NSArray *arr1 = #[object1, object2, object3];
and
NSArray *arr2 = [NSArray arrayWithObjects:object1, object2, object3, nil];
In the first, if any of object1, object2, or object3 hold nil, you will get a runtime exception. In the second, any nil just terminates the argument list. So, if object2 is nil, arr2 will end up as a single-element array holding only object1. NSArray will never even get around to considering object3 because, as far as it's concerned, the argument list was terminated after the first element.
Rarely, you can make legitimate use of this behavior (in which case, I'd clearly make note of it in the comments), but most often this is unexpected and undesirable behavior. It's a particularly pernicious sort of bug.

Related

CFPropertyListCreateDeepCopy fails to process array / dictionary containing NSNull

For some reason this sample code works:
NSArray *immutable = #[ #"a", #"b", #"c" ];
NSMutableArray *mutable = (__bridge id)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFArrayRef)immutable, kCFPropertyListMutableContainers);
and this code produces nil as a result of the conversion:
NSArray *immutable = #[ #"a", [NSNull null], #"c" ];
NSMutableArray *mutable = (__bridge id)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFArrayRef)immutable, kCFPropertyListMutableContainers);
I tried to find any mention of NSNull not being allowed when using this function. I have a suspicion that it has something to do with the way method examines whether property is mutable or not, but I can't really back that up with facts.
Any ideas?
As kind people from apple developer forum pointed out the issue is that Property List Structure is rather strict about data types it can work with. NSNull is not one of allowed ones.
From apple docs:
Property lists are constructed from the basic Core Foundation types CFString, CFNumber, CFBoolean, CFDate, and CFData.

Retrieving NSArray From an NSDictionary

I am trying to get an array full of my data, I keep getting an BAD_ACCESS error when I run this though at the calling the array which I have not included here but I even commented that code out and tried just calling it to the log and still get the BAD_ACCESS error. The array is stored in a dictionary that contains a one key that is a number. I am not sure what I am doing wrong here.
ISData *is = [[ISData alloc] init];
NSDictionary *dic = [is getData:#"right" : isNumber];
NSArray *array = [[NSArray alloc] initWithArray:[dic valueForKey:#"2"]];
NSString *out = [array objectAtIndex:0];
How the dictionary is created:
NSNumber* key = [NSNumber numberWithInt:isNumber];
NSArray *values = [NSArray arrayWithObjects:[NSString stringWithUTF8String:name], [NSString stringWithUTF8String:desc], [NSString stringWithUTF8String:intent], nil];
[dic setObject:values forKey:key];
You don't say exactly where it crashes, and you don't have an obvious crashing bug here, so it's hard to diagnose your actual issue. This could be a memory management thing that's outside the code you've presented. But a couple things are going on here that are suspicious:
You should never have a bare [MyClass alloc] without the -init call. Your init should call super's init, which is responsible for setting up the new object.
Your -valueForKey: should be -objectForKey:. The difference is probably unimportant in this case, but the former is used for "KVC" coding, which you're not using. If you set it as object, get it as object.
Your #"2" as the key into the dictionary doesn't match your input, which is an NSNumber. NSNumbers are not string versions of numbers, so you're unlikely to find any value there. Instead, use the same [NSNumber numberWithInt:2] pattern.
It is most likely that your array is empty. You can try print NSLog(#"count = %d", array.count); to see if that's the case.
If your dic is set up in the second block of code, then what's that NSDictionary *dic = [is getData:...] thing in the first block?
And is there a reason you cannot set up your array directly? Is there a reason for you to use a dictionary when it has only one key?

How to fill NSArray in compile time?

In Objective-C, how to do something like is
int array[] = {1, 2, 3, 4};
in pure C?
I need to fill NSArray with NSStrings with the smallest overhead (code and/or runtime) as possible.
It's not possible to create an array like you're doing at compile time. That's because it's not a "compile time constant." Instead, you can do something like:
static NSArray *tArray = nil;
-(void)viewDidLoad {
[super viewDidLoad];
tArray = [NSArray arrayWithObjects:#"A", #"B", #"C", nil];
}
If it's truly important that you have this precompiled, then I guess you could create a test project, create the array (or whatever object) you need, fill it, then serialize it using NSKeyedArchiver (which will save it to a file), and then include that file in your app. You will then need to use NSKeyedUnarchiver to unarchive the object for use. I'm not sure what the performance difference is between these two approaches. One advantage to this method is that you don't have a big block of code if you need to initialize an array that includes a lot of objects.
use this
NSArray *array = [NSArray arrayWithObjects:str1,str2, nil];
As far as i understand you need a one-dimentional array
You can use class methods of NSArray.. For instance
NSString *yourString;
NSArray *yourArray = [[NSArray alloc] initWithObjects:yourString, nil];
If you need more, please give some more detail about your issue
Simple as that: NSArray<NSString*> *stringsArray = #[#"Str1", #"Str2", #"Str3", ...]; Modern ObjectiveC allows generics and literal arrays.
If you want shorter code, then NSArray *stringsArray = #[#"Str1", #"Str2", #"Str3", ...];, as the generics are optional and help only when accessing the array elements, thus you can later in the code cast back to the templatized array.

Can NSDictionary contain different types of objects as values?

Why does the following result in a BAD_ACCESS error?
NSDictionary *header=[[NSDictionary alloc]initWithObjectsAndKeys:#"fred",#"title",1,#"count", nil];
Can you have different types of objects as values in NSDictionary, including another NSDictionary?
You can put any type of object into an NSDictionary. So while #"fred" is OK, 1 is not, as an integer is not an object. If you want to put a number in a dictionary, wrap it in an NSNumber:
NSDictionary *header = { #"title": #"fred", #"count": #1 };
Not the way you have it. The number 1 is a primitive and the NSArray object can hold only objects. Create a NSNumber for the "1" and then it will store it.
An NSDictionary can only contain Objective-C objects in it (such as NSString and NSArray), it cannot contain primitive types like int, float, or char*. Given those constraints, heterogeneous dictionaries are perfectly legal.
If you want to include a number such as 1 as a key or value, you should wrap it with an NSNumber:
NSDictionary *header=[[NSDictionary alloc] initWithObjectsAndKeys:
#"fred", #"title",
[NSNumber numberWithInt:1], #"count",
nil];
The only requirement is that is be an object. It's up to you to handle the objects properly in your code, but presumably, you can keep track of their types based on the keys.
1 is not an object. If you want t o put a number into a dictionary you may want to convert it to an NSNumber.

Objective-C accessing / changing array elements in a multidimensional array (NSArray)

I'm trying to change a value in a multidimensional array but getting a compiler error:
warning: passing argument 2 of 'setValue:forKey:' makes pointer from integer without a cast
This is my content array:
NSArray *tableContent = [[NSArray alloc] initWithObjects:
[[NSArray alloc] initWithObjects:#"a",#"b",#"c",nil],
[[NSArray alloc] initWithObjects:#"d",#"e",#"f",nil],
[[NSArray alloc] initWithObjects:#"g",#"h",#"i",nil],
nil];
This is how I'm trying to change the value:
[[tableContent objectAtIndex:0] setValue:#"new value" forKey:1];
Solution:
[[tableContent objectAtIndex:0] setValue:#"new val" forKey:#"1"];
So the array key is a string type - kinda strange but good to know.
NSMutableArray *tableContent = [[NSMutableArray alloc] initWithObjects:
[NSMutableArray arrayWithObjects:#"a",#"b",#"c",nil],
[NSMutableArray arrayWithObjects:#"d",#"e",#"f",nil],
[NSMutableArray arrayWithObjects:#"g",#"h",#"i",nil],
nil];
[[tableContent objectAtIndex:0] replaceObjectAtIndex:1 withObject:#"new object"];
You don't want to alloc+init for the sub-arrays because the retain count of the sub-arrays will be too high (+1 for the alloc, then +1 again as it is inserted into the outer array).
You're creating immutable arrays, and trying to change the values stored in them. Use NSMutableArray instead.
You want either NSMutableArray's insertObject:atIndex: or replaceObjectAtIndex:withObject: (the former will push the existing element back if one already exists, while the latter will replace it but doesn't work for indices that aren't already occupied). The message setValue:forKey: takes a value type for its first argument and an NSString for its second. You're passing an integer rather than an NSString, which is never valid.
Sorry for responding 1 and half years old question :D
I got the same problem, and at last I solved it with counting the elements, then do addObject to push to the array element