I recently started using Faux Pas (http://fauxpasapp.com/), and for my project I get the following warning a number of times:
Old, verbose Objective-C syntax
-[NSDictionary objectForKey:] is called. Consider using an Objective-C subscript expression instead.
I'm unsure what the subscript expression is, and I'm not having much luck finding anything on it. I was curious if anyone here could help.
Thanks!
There is no performance difference, just that the literal syntax is more clear, less verbose and has been available for several years now.
If the current code is like:
id var = [dictionary objectForKey:#"key"];
replace it with:
id var = dictionary[#"key"];
Well typically you would have written something like this:
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:42] forKey:#"foo"];
id value = [dictionary objectForKey:#"foo"];
And now you would write something like this:
NSDictionary *dictionary = #{#"foo": #42};
id value = dictionary[#"foo"];
Which, I think you will agree is a lot simpler and nicer to look at.
Some nice information about Object Subscripting can be found at NSHipster
You want to learn about Objective-C 2.0. The best way is to go straight to the source:
http://clang.llvm.org/docs/ObjectiveCLiterals.html
As you can see, a number of language features were introduced: NSNumber literals and "boxing" expressions, along with subscripting of NSArray and NSDictionary (and your own classes if you like). This is considered the "modern" way.
Xcode will refactor your code into "modern Objective-C" for you (see under Edit > Refactor), so you can modernize your code without doing any work!
Related
I would like to understand more about the way XCode/Objective-C handle constant strings. I found a related question, but I would like more information. Consider the following code:
NSString *a = [[NSString alloc] initWithUTF8String:[[_textFieldA stringValue] UTF8String]];
NSString *b = [[NSString alloc] initWithUTF8String:[[_textFieldB stringValue] UTF8String]];
NSString *c = [a copy];
NSString *d = [a mutableCopy];
Note that the textFields are just a way to set the strings at runtime ensuring that the compiler doesn't get too smart on me and build in a single instance.
If my text fields are empty, or contain a single character such as "x" or "$", then a == b == c == the same constant NSString instance. If I instead provide "xy", then a == c != b. d is always unique, as one might expect since it is mutable.
Now normally this wouldn't be an issue, I'm not trying to modify the contents of these strings, however, I am working on a system where I frequently use objc_setAssociatedObject. So here now I might come accross an empty string, and then set associated object data on it, and then have another empty string and collide with the first.
I have, for the moment, solved my issue by creating mutable strings instead.
So my questions:
Is this an Objective-C specification, or an XCode excentricity?
Does anyone know how the instance is determined? Why "x" get's one instance, but not "xy"? I would think some internal dictionary is involved and there's no good reason to stop at 1 character.
Is there a way to turn this off, so all empty strings are unique instances, or other suggestions?
I am using XCode 5.1.1, OSX 10.9.4, SDK 10.9.
Thank you!
Is this an Objective-C specification, or an XCode excentricity?
It is just implementation detail. Not documented any where. These kind of behaviour may changed in future without notice.
Does anyone know how the instance is determined? Why "x" get's one instance, but not "xy"? I would think some internal dictionary is involved and there's no good reason to stop at 1 character.
No until someone able to access source code want to share the details with us.
Is there a way to turn this off, so all empty strings are unique instances, or other suggestions?
No way to turn it off. Don't use objc_setAssociatedObject with NSString
As #Ken Thomases said in comment
In general, it probably doesn't make sense to use objc_setAssociatedObject() with any value class.
Some other examples are NSNumber, NSData and NSValue. They are often cached and reused.
I've been developing iOs and OsX applications for several months now and it still feels like I'm doing something wrong. I try to stick to the Guidelines and I try to use the objects Apple provides as often as I can. But it seems they are making my code very hard to understand.
Example:
When I want to just "increment" a NSNumber Object (which is not mutable, but you get what I mean), I use awkward lines like this:
int value = [counter intValue];
counter = [NSNumber numberWithInt:value +1];
Is this really necessary? Are there more elegant ways (i++, inc(i), etc) to do simple things like this? Especially when you're working with coordinates it gets really frustrating and hard to work with.
When working with Objective C I feel like I'm allocating, deallocating and converting objects all the time and wasting so much of my own time and the CPU time with all those conversions. Thanks for your time, I really appreciate your answers and I'm looking forward to your tipps!
Using your example, is there any particular reason you are using NSNumber for a counter? It would be much better to use int so that you can use value++.
The key to good Objective-C code is to use objects when they make sense. Don't be afraid to use non-object data types and don't be afraid to drop down (not the best term) to C when required.
As #sosborn wrote: use objects only when it's required. But: when it's required, and you still feel wrong, simply don't. Write a macro for incrementing an NSNumber, use ARC for let the compiler do the memory management for you as efficiently as possible, etc. If you really worried about time, use C or assembly for time-critical tasks, or C++ if you want OO.
P. s.: NSNumber increment macro:
#define NSNUM_INC(n) do { n = [NSNumber numberWithInt:[n intValue] + 1]; } while (0);
You can write your category for NSNumber to implement the methods you need. For your example the file of category contains the following function:
-(NSNumber *)numberByAddingInt:(int)i
{
...
}
Include this file and then you can call it as:
counter = [counter numberByAddingInt:1];
This is my main:
int x=0;
NSString *new=[[NSString alloc]initWithString:#"9+4"];
x=[new intValue];
NSLog(#"hi %i",x);
This results in 9.. .since giving the intValue of a string will read only numbers and stops when the character is not a digit.
So how can i print the result of my string and get a 13 instead??
Actually, NSExpression was made just for this:
NSExpression *expression = [NSExpression expressionWithFormat:#"9+4"];
// result is a NSNumber
id result = [expression expressionValueWithObject:nil context:nil];
NSLog(#"%#", result);
NSExpression is very powerful, I suggest you read up on it. You can use variables by passing in objects through the format string.
Change this line
NSString *new=[[NSString alloc]initWithString:#"9+4"];
to
NSString *new=[NSString stringWithFormat:#"%f",9+4];
You will have to manually parse it. You could write a subclass of NSString that overrides the intValue method, and parses it to find arithmetic symbols and perform the math, but thats as close to automatic as you're gonna get I'm afraid
You will need to parse and evaluate it yourself, as seemingly simple calculations like this are beyond the scope of the basic string parsing Apple provides you. It might seem to be a no-brainer if you're used to interpreted languages like Ruby, Perl and the like. But for a compiled language support for runtime evaluation of expressions are uncommon (there are languages that do support them, but Objective-C is not one of them).
When attempting to parse an expression in a string you will want to use Reverse Polish Notation. Here is the first example I cam across in a google search for Objective-C.
While in the process of debugging code written by a co-worker, I stumbled across the following that has me mystified:
NSMutableArray *array = [NSMutableArray array];
NSUInteger arrayCount = array.count;
Why does this work? It also works for NSDictionary and other types, but nowhere in the documentation nor Cocoa headers can those #property definitions be found.
Googling for "NSArray property" doesn't yield many useful results, so I'm reaching out to SO for what will surely be a very embarrassing question.
It works because dot syntax has nothing to do with properties. It is simply syntactic sugar (though I don't like it, so perhaps it's "syntactic salt").
When you use dot syntax as the rvalue to an expression (or the expression to the right of the equal sign), it simple turns:
bar = myObject.thing;
Into
bar = [myObject thing];
When the dot is to the left of the equal sign (as an lvalue), it turns it into the setter. So:
myObject.thing = 42;
Becomes
[myObject setThing:42];
So yes, you can do things like myObject.retain. But you should never ever do that. You should only ever use dot syntax as accessors to declared properties (ie, things that have been explicitly declared via #property). <insert remark about how you should never use dot syntax at all.>
For more information, checkout out the documentation on dot syntax (specifically the "incorrect use" section).
the dot syntax is actually just an alternative for accessing methods that either take no parameter and return a value like array.count. It is bad form to actually access the methods in that way.
It can also be used for things like [object setValue:(id)something] and access it by doing object.setValue = something;
I'm iterating through some objects and trying to set some properties that, apparently, don't like Key-Value coding. so I'm unable to create a string at runtime to represent the "key".
This line of ViewController won't compile:
[self setValue:offsetX forKeyPath:[NSString stringWithFormat:#"myView%d.center.x", anInt]];
but I can set these properties with dot notation in a ridiculous switch statement:
myView1.center.x = offsetX;
Is there another way to go about this? perhaps create an accessor like myView(i).center.x ? Knowing full well it was going to be futile, i even tried: [NSString stringWithFormat:#"myView%d", anInt].center.x
to no avail...
The reason it wont compile is presumably because offsetX is an int or float, not an NSNumber (it would be helpful to give the compiler error message in your question).
However KVC and setValue:forKeyPath: is very clever and will automatically convert from an NSNumber for you, so use:
[self setValue:[NSNumber numberWithInt:offsetX] forKeyPath:[NSString stringWithFormat:#"myView%d.center.x", anInt]];
(or numberWithFloat as appropriate).