Rust Cocoa - How to make an NSDictionary? - objective-c

I've been reading around trying to understand it. You can see here dictionaryWithObjects:objects the takes an array of Objects and Keys:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects
forKeys:keys
count:count];
https://developer.apple.com/documentation/foundation/nsdictionary#overview
but initWithObjectsAndKeys_ only a single Object for input? 🤷🏻‍♂️
unsafe fn initWithObjectsAndKeys_(self, firstObject: *mut Object) -> *mut Object
https://docs.rs/cocoa/0.24.0/cocoa/foundation/trait.NSDictionary.html#tymethod.initWithObjectsAndKeys_

Many of Rust cocoa APIs are direct wrappers of the underlying Objective-C APIs, like initWithObjectsAndKeys: https://developer.apple.com/documentation/foundation/nsdictionary/1574190-initwithobjectsandkeys?language=objc. Callers are expected to pass a pointer to the first element of a null-terminated array containing alternating value and key elements for the dictionary.

In Objective C you call initWithObjectsAndKeys like this:
NSMutableDictionary *aDictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
#"Value1", #"Key1",
#"Value2", #"Key2",
#"Value3", #"Key3",
nil, nil
];
(Technically speaking, one nil would be sufficient, but I find it lacks symmetry so I put two :-P)
It's a variadic function that can take multiple arguments.
I'm afraid I don't know enough about Rust to know if it can handle things the same way.

Of course after learning how to do it from the great answers here (as well as, before posting this question, searching the repo, all GitHub, and the web for an undisclosed amount of time), I find an example in a test from #Josh Matthews servo/core-foundation-rs library here:
let mkstr = |s| unsafe { NSString::alloc(nil).init_str(s) };
let keys = vec!["a", "b", "c", "d", "e", "f"];
let objects = vec!["1", "2", "3", "4", "5", "6"];
unsafe {
let keys_raw_vec = keys.clone().into_iter().map(&mkstr).collect::<Vec<_>>();
let objs_raw_vec = objects.clone().into_iter().map(&mkstr).collect::<Vec<_>>();
let keys_array = NSArray::arrayWithObjects(nil, &keys_raw_vec);
let objs_array = NSArray::arrayWithObjects(nil, &objs_raw_vec);
let dict = NSDictionary::dictionaryWithObjects_forKeys_(nil, objs_array, keys_array);
}
From https://github.com/servo/core-foundation-rs/blob/355740417e69d3b1a8d909f84d91a6618c9721cc/cocoa-foundation/tests/foundation.rs#L145

Related

Objective-c Workaround for running intepretted code

I'm used to using eval() in languages like javascript e.t.c.
In cocos2d, to select the level, I am passing "1" or "2" to my Loadlevel.m file, the level Classes are named "LevelOne" and "LevelTwo" accordingly, I wanted to create a dictionary lookup that paried "1" => "LevelOne" e.t.c then run eval on that string to effectively call [MyLevel node];
Apparently we can't use eval in IOS code, so how would I go about doing this?
Try using the NSStringFromClass and NSClassFromString functions.
Specifically, represent the classes as strings in your dictionary, e.g.,
[myDictionary setObject:NSStringFromClass([LevelOne class]) forKey:#"1"]
Then, to use the right level from your dictionary, you could do:
NSString *levelAsString = [myDictionary objectForKey:#"1"];
id node = [NSClassFromString(levelAsString) node]
(I'm assuming +node is a class method)
Now, this is a pretty uncommon and odd design in Cocoa. Perhaps if you explain more about what you're doing, we could suggest alternate design choices that might be better.
I'm not one hundred percent clear on what you're asking, but you can store class objects directly in your dictionary, retrieve them, and send them messages:
NSDictionary * d = [NSDictionary dictionaryWithObjectsAndKeys:[LevelOne class], #"1", [LevelTwo class], #"2", nil];
Class selectedLevel = [d objectForKey:#"1"];
[selectedLevel node];

Easiest way to create objects in ios

Does iOS support something like
s = { x:3,y:5 , o:{x:0,y:1}}
like in Javascript?
Or what is the best way to use something like this in iOS?
A literal translation would be :
NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:3], #"x",
[NSNumber numberWithInt:5], #"y",
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:0], #"x",
[NSNumber numberWithInt:1], #"y",
nil ], #"o",
nil ];
But literal translations, even in programming languages, are not best. It all depends on the context and what you need to accomplish. There's probably less code required depending on what you need.
EDIT: There's now a new literal syntax allowing to shrink this code further:
NSDictionary *s = #{ #"x": #3
, #"y": #5
, #"o": #{ #"x": #0, #"y": #1}
};
This syntax implicitely creates the NSDictionary & NSNumber objects.
No, there is no shortcut syntax for dicts in Objective-C. You have to do it manually.
It depends exactly what type of object you mean. There really isn't a generic answer here.
If you're referring to an NSDictionary for example, you could use
+ (id)dictionaryWithObjects:(NSArray *)objects forKeys:(NSArray *)keys
or any of the variants documented on the same page.
You could write a setter or helper method that could take a string in the above format, syntax check it, parse it, and use the string fields to create or modify an NSDictionary for you automatically from a one line call.
But it's not a built function of Obj C or the NSFoundation frameworks. Some JSON Category additions might do something like this though.
What you can do is create a JSON string and then use a JSON framework to generate the corresponding NSArray/NSDictionary structure. This would approach what you are trying to do, but not quite there... Then you can describe your object structure with something like this:
{"x": 3, "y" : 5, "o" : {"x": 0, "y" : 1}}
Main limitation is that you won't have numeric types out of the box, but you can use NSNumber, NSScanner and so on to get them.
No, it does not support. But i am sorry, im not fammiliar with JS.
To creat a obj in iOS you do the following:
ObjName *objVar;
then you set its properties:
#property (nonatomic, strong) ObjName *objVar;
then you syntesize it in the .m
#syntesize objVar;
and the you alloc it and init it in the desired method.

Objective-C: Using an NSString variable to send a message to one of a variety of objects

I'll use an example from JavaScript to help clarify my question. Let's assume I have the following object:
sports = {
soccer: {...},
basketball: {...},
baseball: {...}
}
If at some point in my script I have a variable, sportString, that simply holds a string, I can dynamically call one of the sports objects in the following way:
sports[sportString];
This frees me from having to use a bunch of nested if statements, testing the value of the string such as:
if(sportString === 'soccer'){
sports.soccer;
}else if(sportString === 'basketball){....
So, my question is how can I accomplish something similar to sports[sportString] in Objective-C, if sportString is an NSString object?
Use an NSDictionary as your sports object. Then you can do lookups like this:
[sports objectForKey: sportsString];
The people saying you should use NSDictionary for general key/value storage are 100 % right. However, I think it’s useful to know that you can call a message specified by a string:
SEL selector = NSSelectorFromString(#"foo"); // Or #selector(foo) if you know it at compile time
id value = [object performSelector:selector];
You can also use selectors with up to two arguments, as long as they take objects:
SEL selector2 = NSSelectorFromString(#"setFoo:");
[object performSelector:selector2 withObject:value];
It’s possible to invoke arbitrary methods using IMPs or casting objc_msgSend(), but now I’m getting way beyond the scope of your actual question. :-)
Your JavaScript object sports would typically be an NSDictionary or NSMutableDictionary.
Example:
NSMutableDictionary *sports = [NSMutableDictionary dictionary];
[sports setObject:#"Foo" forKey:#"soccer"];
[sports setObject:#"Bar" forKey:#"basketball"];
NSString *sportString = #"soccer";
NSString *sportValue = [sports objectForKey:sportString];
NSLog(#"%#", sportValue); //logs "Foo"

Approaches to create a nested tree structure of NSDictionaries?

I'm parsing some input which produces a tree structure containing NSDictionary instances on the branches and NSString instance at the nodes.
After parsing, the whole structure should be immutable. I feel like I'm jumping through hoops to create the structure and then make sure it's immutable when it's returned from my method.
We can probably all relate to the input I'm parsing, since it's a query string from a URL. In a string like this:
a=foo&b=bar&a=zip
We expect a structure like this:
NSDictionary {
"a" => NSDictionary {
0 => "foo",
1 => "zip"
},
"b" => "bar"
}
I'm keeping it just two-dimensional in this example for brevity, though in the real-world we sometimes see var[key1][key2]=value&var[key1][key3]=value2 type structures. The code hasn't evolved that far just yet.
Currently I do this:
- (NSDictionary *)parseQuery:(NSString *)queryString {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *pairs = [queryString componentsSeparatedByString:#"&"];
for (NSString *pair in pairs) {
NSRange eqRange = [pair rangeOfString:#"="];
NSString *key;
id value;
// If the parameter is a key without a specified value
if (eqRange.location == NSNotFound) {
key = [pair stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
value = #"";
} else {
// Else determine both key and value
key = [[pair substringToIndex:eqRange.location] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if ([pair length] > eqRange.location + 1) {
value = [[pair substringFromIndex:eqRange.location + 1] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
} else {
value = #"";
}
}
// Parameter already exists, it must be a dictionary
if (nil != [params objectForKey:key]) {
id existingValue = [params objectForKey:key];
if (![existingValue isKindOfClass:[NSDictionary class]]) {
value = [NSDictionary dictionaryWithObjectsAndKeys:existingValue, [NSNumber numberWithInt:0], value, [NSNumber numberWithInt:1], nil];
} else {
// FIXME: There must be a more elegant way to build a nested dictionary where the end result is immutable?
NSMutableDictionary *newValue = [NSMutableDictionary dictionaryWithDictionary:existingValue];
[newValue setObject:value forKey:[NSNumber numberWithInt:[newValue count]]];
value = [NSDictionary dictionaryWithDictionary:newValue];
}
}
[params setObject:value forKey:key];
}
return [NSDictionary dictionaryWithDictionary:params];
}
If you look at the bit where I've added FIXME it feels awfully clumsy, pulling out the existing dictionary, creating an immutable version of it, adding the new value, then creating an immutable dictionary from that to set back in place. Expensive and unnecessary?
I'm not sure if there are any Cocoa-specific design patterns I can follow here?
Expensive and unnecessary?
Yes. Apple's Cocoa APIs regularly say they return an immutable object, but actually return a mutable subclass that's been cast to the immutable version. This is a standard operating procedure and an accepted Cocoa design principle. You just trust that your clients aren't going to cast it back to a mutable version and change things from underneath you.
From Cocoa Core Competencies: Object Mutability:
Receiving Mutable Objects
When you call a method and receive an object in return, the object could be mutable even if the method’s return type characterizes it as immutable. There is nothing to prevent a class from declaring a method to return an immutable object but returning a mutable object in its implementation. Although you could use introspection to determine whether a received object is actually mutable or immutable, you shouldn’t. Always use the return type of an object to judge its mutability.
See also: Cocoa Fundamentals Guide: Cocoa Objects.

Objective-C arrays, style and spirit

Newbie question. Looking at arrays (ie: dynamically sized) this works:
NSArray *array;
array = [NSArray arrayWithObjects:
#"one", #"two", nil];
This does not:
array = [NSArray arrayWithObjects:
1, 2, nil];
Ok, I get it. This works:
array = [NSArray arrayWithObjects:
[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], nil];
Its sorta less "on the fly" as C++ / Java. I see the same thing with the init examples I'm reading. For example:
// pseudo objc example
MyVar v = [MyVar init]; // blank
[v setSomething];
[v setSomethingElse];
// use v down here
In C++/Java I'd do:
MyVar v = new MyVar("foo", "bar", "baz", "quux");
And I'd know that v is ready to go by default. Is there a spirit of ObjC that I should not fight? Should I just expect to write more lines and less "one-liners"?
In Objective-C, the "init" method is just a method. Unlike Java or C++ whose constructors are different than other methods. So you can define your own init methods that behave like C++ or Java constructors. For example, you could define an init method that takes several parameters. It might look something like this.
MyVar* v = [[MyVar alloc] initWithName:#"foo" andTitle:#"bar"];
// do something with v
[v release];
Common practice is to simply create new methods that perform object initialization, and prefix the method name with "init" for clarity and consistency.
NSArray, its modifiable variant NSMutableArray, and all collection structures (NSDictionary, NSSet) are made for storing objects. This is why you see [NSNumber numberWithInt:1] instead of simply 1.
For strings, note that an Objective-c string (like #"one", including leading #) is an object of type NSString, whereas a C string (like "one", without #) is not an object.
If you want the simplicity of storing simple values in arrays, don't forget that Objective-C is a superset of C. This means that you can use a declaration like:
int array[] = { 1, 2, 3 };
What you get with Objective-C's verbosity is greater readability.
Sure, while you're writing code, it might be easy to knock up something like you wrote
MyRect rect = new MyRect(1.0, 1.0, 3.0 3.0);
But when you, or more likely someone else, comes to maintain your code then I would argue that this is much easier to read:
MyRect *rect = [[MyRect alloc] initWithX:1.0 Y:1.0 width:3.0 height:3.0];
And in these days of smart editors it isn't that much harder to write.
You can write constructors that take named parameters to get it down to one line:
MyVar *v = [[MyVar alloc] initWithFoo:#"foo" bar:#"bar" baz:#"baz" quux:#"quux"];
Yeah, Objective-C is pretty verbose, so you're going to need to get used to that.
The MyVar init case is not quite right; you generally need to do a [[MyVar alloc] init] or a [MyVar new]. And in many cases, there are init variants that take arguments, like
MyVar v = [[MyVar alloc] initWithSomething: "foo" somethingElse: "bar"];
If you want something less verbose, where you can get more one liners in, you might want to look into MacRuby, which is a binding between Ruby and Objective-C that gives you access to all of the Cocoa APIs but with a much more compact, high level syntax (and you can go even more compact still with HotCocoa. Of course, there is a bit of a performance penalty using MacRuby, so if you're doing a high performance game it might not be ideal to write your drawing loop in RubyCocoa, but its fine for the vast majority of applications.