When would you use NSNumber literal to create encapsulated character values? - ios

I'm just going through Apple's iOS development tutorial at the moment and reading the chapter on the Foundation framework and value objects.
Just on the NSNumber class, it says:
You can even use NSNumber literals to create encapsulated Boolean and
character values.
NSNumber *myBoolValue = #YES; NSNumber *myCharValue = #'V';
I'm just wondering, when, or why, or in what scenario, might you want to use NSNumber for a character value rather than using NSString, say?

An NSNumber is useful for encapsulating primitive values to be inserted into Objective-C collection classes such as NSArray, NSSet, NSDictionary, etc.
Image a scenario where you would want to iterate over each character in an ASCII string and extract a unique set of vowels used. You can evaluate each character and add it to an NSMutableSet. To do so, you would need to encapsulate each character in an NSNumber as NSMutableSet expects an Objective-C object. This is just one example, but the concept applies to many situations where primitives need to be added into a collection.

Well, one case is where you're using KVC to set a value for a key, and the property type is char:
[object setValue:#'a' forKey:someCharPropertyName];

You can use NSNumber with characters to return its ASCII Code, so V would return 86.
I don't think many people use it that much, but you could probably use it for character validation. I think it just one of those things where Apple went, yeah, lets put that in for the heck of it.
It's really not used for much else. The #YES and #NO is the same as YES and NO, so its kinda inelegant in some places.

Related

How are parameters of various types accessed in Cordova iOS plugins written in Swift

The available documentation for cordova plugins in iOS is pretty poor, and assumes knowledge of Objective C (which I have never learned). It also seems to skip over certain things you're likely to need to know, like what a CDVInvokedUrlCommand is, exactly, and what types of values can be extracted from it.
I see from the header file for the type that it contains a method argumentAtIndex:, which I presume from Swift examples that I've seen interacts with the swift subscript operator, as these suggest command.arguments[number] as a means of getting the argument. However, I've seen no examples at any point that retrieve an argument of any type other than strings (which return either String or NSString ... and to be honest, I'm not really sure what the difference between these types is). So as I understand it, if I get string values, I can extract them like this:
#objc(myStringAction:)
func myStringAction (command: CDVInvokedUrlCommand) {
let firstString = command.arguments[0] as String;
let secondString = command.arguments[1] as String;
...
}
So, assuming I'm implementing another action myComplexAction which receives: an integer, a floating point number, an array of integers, and an object containing string values. How can I extract these into appropriate variables, and what are the resulting types?
After some research, a little bit of learning to read Objective C, and a whole load of digging in the badly-documented Cordova iOS platform library source code, it turns out that the arguments array in the CDVInvokedUrlCommand class is simply a standard array that is initialized from a JSON string using the standard platform NSJSONSerialization service. Therefore, you can assume that the array contains entries of one of the following types:
NSString, NSNumber, or NSNull for standard basic types.
Note that NSNumber is used to encode boolean values as well as numeric ones
NSArray with the same kind of entry types for any arrays
NSDictionary with NSString keys and the same kind of entry types for object values.
Referencing from Swift, the following conversions to standard Swift value types are handled by the runtime:
NSString can be converted to String using as (or implicitly for Swift 2 or earlier)
NSNumber can be converted to an optional of any Swift numeric type using the as? operator (the conversion will fail if the contained number isn't representable in the target type).
NSNull doesn't get converted, but can be tested for using is
NSDictionary is implicitly convertible to [AnyHashable:Any]
NSArray is implicitly converted to [Any].

iOS - Why Does It Work When I Compare Two NSNumbers With "=="?

In my app, I accidentally used "==" when comparing two NSNumber objects like so:
NSNumber *number1;
NSNumber *number2;
Later on, after these objects' int values were set, I accidentally did this:
if (number1 == number2) {
NSLog(#"THEY'RE EQUAL");
}
And, confusingly, it worked! I could have sworn I was taught to do it this way:
if (number1.intValue == number2.intValue) {
NSLog(#"THEY'RE EQUAL");
}
How did using "==" between the two NSNumber objects work, and why? Does that mean it's okay to compare them that way, or was it just a fluke and this is generally not guaranteed to work every time? It really confused me :(
It's not a fluke.
It's due to the tagged pointers feature of the Objective-C runtime while using an ARM64 CPU.
In Mac OS X 10.7, Apple introduced tagged pointers. Tagged pointers allow certain classes with small amounts of per-instance data to be stored entirely within the pointer. This can eliminate the need for memory allocations for many uses of classes like NSNumber, and can make for a good performance boost.[…] on ARM64, the Objective-C runtime includes tagged pointers, with all of the same benefits they've brought to the Mac
Source
That is possibly a fluke.
From NSHipster :
Two objects may be equal or equivalent to one another, if they share a common set of observable properties. Yet, those two objects may still be thought to be distinct, each with their own identity. In programming, an object’s identity is tied to its memory address.
Its possible that your statement evaluated to YES because number1 and number2 were pointing to the same object. This would not work if they had the same value but were two different objects.
The obvious reason NSNumber variables would point to the same would be that you explicitly assigned one to the other, like so:
number1 = number2;
But there's one other thing. From this answer :
This is likely either a compiler optimisation or an implementation detail: as NSNumber is immutable there's no need for them be separate instances. probably an implementation optimisation thinking about it. Likely numberWithInt returns a singleton when called subsequently with the same integer.
But anyways, its safest to use isEqualToNumber:, as there is no telling what other "things" are lurking in the depths of code that may or may not cause it to evaluate YES
From RyPress :
While it’s possible to directly compare NSNumber pointers, the isEqualToNumber: method is a much more robust way to check for equality. It guarantees that two values will compare equal, even if they are stored in different objects.
There two concepts of equality at work here:
Object identity: Comparing that two pointers point to the same objects
Value equality: That the contents of two objects are equal.
In this case, you want value equality. In your code you declare two pointers to NSNumber objects:
NSNumber *number1;
NSNumber *number2;
But at no point show assignment of a value to them. This means the contents of the pointers can be anything, and quite by chance you have two pointers pointing to the memory locations (not necessarily the same ones) where (number1.intValue == number2.intValue) happens to be true.
You can expect the behaviour to change in unstable ways - for instance as soon as you add any more code.
Of course you can compare two NSNumber* with ==. This will tell you whether the pointers are equal. Of course if the pointers are equal then the values must be the same. The values can be the same without the pointers being equal.
Now you need to be aware that MaxOS X and iOS do some significant optimisations to save storage, especially in 64 bit code. Many NSNumbers representing the same integer value will actually be the same pointer.
NSNumber* value1 = [[NSNumber alloc] initWithInteger:1];
NSNumber* value2 = [[NSNumber alloc] initWithInteger:1];
These will be the same pointers. In 64 bit, many others will be the same pointers. There are only ever two NSNumber objects with boolean values. There is only ever one empty NSArray object, and only one [NSNull null] object.
Don't let that lull you into any wrong assumptions. If you want to see if two NSNumbers have the same value, use isEqualToNumber: You may say "if (number1 == number2 || [number1 isEqualToNumber:number2])"; that's fine (didn't check if I got the names right).

Do I really need to convert my project currency to NSDecimalNumber?

Yes, I know I should use NSDecimalNumber to deal with currency, money, price... I've read this. The problem is, I adapted an existed project, which use NSString and NSNumber (float, double, CGFloat...) as currency. They deal with floating point by using NSNumberFormatter, as I can see it's not a big problem (yet?). Those currency is stored to coredata.
Now, if I want to convert all of those currency into NSDecimalNumber, I'll have to do a massive refactor in the code and migration in coredata. Here come the question:
If (I assume) double, CGFloat, NSNumber can hold the value as large as NSDecimalNumber, why should I use NSDecimalNumber since I can use
other with NSNumberFormatter? Is it because of performance?
In case of the necessary of the converting, can I do an auto migration with the help of MappingModel only, of course), or do I have
to adapt a custom migration policy?
Because the coredata use both NSString and NSNumber as currency, so please help me find a solution to migrate from both data type. I'm not used to work with NSDecimalNumber in coredata. Thanks in advance.
EDIT
Okay, I got it that NSDecimalNumber is necessary. Please help me answer the second question: Can I do auto migration, using mappingModel + the thing like FUNCTION($source.price, "decimalValue") (this is incorrect since decimalValue return NSDecimal, not NSDecimalNumber). Do I really have to write a custom migration policy?
Do I really have to write a custom migration policy?
Yes. Create a subclass of NSEntityMigrationPolicy for each entity that needs conversion and implement the menthod createDestinationInstancesForSourceInstance:entityMapping:manager:error:. Enter the name of this class in the Custom Policy of the entity in the mapping model.
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager
error:(NSError **)error
{
BOOL aBool = [super createDestinationInstancesForSourceInstance:sInstance entityMapping:mapping manager:manager error:error];
if (aBool)
{
NSArray *aSourceArray = [[NSArray alloc] initWithObjects:&sInstance count:1];
NSArray *aDestArray = [manager destinationInstancesForEntityMappingNamed:[mapping name] sourceInstances:aSourceArray];
if (aDestArray && [aDestArray count] > 0)
{
NSManagedObject *dInstance = [aDestArray objectAtIndex:0];
// conversion
}
}
return aBool;
}
The issue is that double, CGFloat, and those value types stored in NSNumber cannot precisely handle decimal values. Thus, if you set 4.50, you may end up with some close approximation. This is generally not desirable for monetary values, as you want exact representations. You can perhaps get away with rounding using NSNumberFormatter, but if you start doing math with these values or need more accuracy than hundredths, you could end up with incorrect amounts.
I suspect you would have to do a heavy-weight migration to change the type of an attribute on a CoreData entity. Even if you didn't have to, you might want to, so that you could ensure the values that you convert to Decimal are rounded to the precision you choose (since they may or may not be what you want in the database right now).
It sounds like you have some technical debt in your project. Wether you choose to pay that debt down now or wait until it grows is up to you in the end.
You probably want to use NSDecimalNumber, not NSDecimal. NSDecimal is a struct, while NSDecimalNumber is an Objective-C class that inherits from NSNumber. Thus you can usually pass an NSDecimalNumber wherever an NSNumber is accepted. NSNumberFormatter formats NSDecimalNumber instances automatically, and if you set your formatter's generatesDecimalNumbers to YES, it will parse strings into NSDecimalNumber instances.
The range of NSDecimal and NSDecimalNumber is larger than double or CGFloat. The decimal types store more significant digits and have a larger exponent range.
I don't know the answer to the auto migration question, but I do know that Core Data supports NSDecimalNumber. You can set an attribute's type to decimal (NSDecimalAttributeType) in your model.

How are identical NSStrings determined in Objective-C?

Consider the following code:
+ (NSString *)helloString
{
return #"hello";
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *a = [AppDelegate helloString];
NSString *b = [AppDelegate helloString];
NSLog(#"%#", a == b ? #"yes" : #"no");
abort();
}
On my machine the result is always "yes". Does it mean that the NSString literal #"hello" is always the same "object" in Objective-C runtime?
Actually my original purpose is to use an uniquely identifiable object to bind in NSNotification's postNotificationName:object: method. I plan to use a NSString literal to act as the object. Is it safe/recommended to do so?
Historically, NSString literals were guaranteed to be unique within a translation unit, and were often unique even between translation units in practice. The current documentation no longer makes this claim as far as I know, and the Clang docs recommend against relying on it.
If you want a string that's guaranteed to always be the same object, you can simply assign a string to a global constant. All references to that constant will definitely yield the same object.
With regard to NSNotification, though, I wouldn't use such a string as the object. The semantics of NSNotification say the the object argument should be the object that triggered the notification — where it conceptually "comes from." Other information associated with the notification would make more sense in the user info dictionary.
[a isEqualToString: b] compares two strings a and b, and returns YES if the contents is the same. This works if they are the same object, or different objects, or one is an NSString and one is an NSMutableString, or one is one of the many classes that behave like strings. If a is nil the result is NO, if a is not nil but b is nil you get a crash.
Comparing strings with a == b is interesting: If a and b are both nil the result is YES, one nil but not the other returns NO. If a and b are the same string because you have the same string literal or the same NSString object assigned to a and b, the result is YES. In your example, the "helloString" method always returns the same literal. Not just a literal with the same characters, but the same literal.
If you use literals with the same characters, they may or may not be the same. No guarantees. If you use the copy method, the result may be the same as the original or not. No guarantees. All in all, == or != for NSString is not very useful. It's only useful to compare nil vs. not nil, or if you know exactly which string was assigned.
BTW >=, >, <=, < give undefined behaviour if the strings are not the same, so they are completely useless.
That depends on the compiler implementation, which in theory could change from time to time, so it is not reliable. If you need to make sure that the NSString pointer is always the same, you should use a constant.
const NSString* kSomeConstantName = #"ConstantValue";
Just use the following Code:
[a isEqualToString:b]
Actually in your case #"hello" is always returned same pointer as mostly all language including objective c use string interning for string literals which means for same string literal it reruns single object.
But this is not the case which is always true means two strings with same characters may have different references if you allocate them differently by alloc keyword.
So always use isEqualToString: if you need to check string equality for its contents.== is used for reference equality(locations in memory) which may or may not different for same contents strings.
Does it mean that the NSString literal #"hello" is always the same "object" in Objective-C runtime?
I wouldn't count on it being true in all cases. Strings should be compared with -isEqualToString:.
Actually my original purpose is to use an uniquely identifiable object to bind in NSNotification's postNotificationName:object: method.
That seems like a misuse of the API. In most cases, you should just pass the object that's posting the notification.

NSNumber arithmetic

I want to perform some simple arithmetic on NSNumbers and preserve the type. Is this possible?
For example:
- (NSNumber *)add:(NSNumber *)firstNumber to:(NSNumber *)secondNumber;
Is my method definition, and firstNumber and secondNumber are integers then I would like to return an integer that is the integer addition of them both. Equally if both are doubles then to return the result as a double.
It looks like I can get the type (except for boolean) using [NSNumber objCType] as found in this question: get type of NSNumber but I can't seem to extract those types and do the calculation without lots of code to extract the values do the calculation and return the result for every possible type.
Is there a short and concise way of doing this?
If you want to perform arithmetic the best bet would be using an NSDecimalNumber.
NSDecimalNumber have methods to perform arithmetic operations like :
– decimalNumberByAdding:
– decimalNumberBySubtracting:
– decimalNumberByMultiplyingBy:
– decimalNumberByDividingBy:
– decimalNumberByRaisingToPower:
– decimalNumberByMultiplyingByPowerOf10:
– decimalNumberByAdding:withBehavior:
– decimalNumberBySubtracting:withBehavior:
– decimalNumberByMultiplyingBy:withBehavior:
– decimalNumberByDividingBy:withBehavior:
– decimalNumberByRaisingToPower:withBehavior:
– decimalNumberByMultiplyingByPowerOf10:withBehavior:
And since NSDecimalNumber extends NSNumber it also have all methods of an NSNumber, so i think that you could use it in your case without any problem.
For nearly all applications it will be fine to convert to double and back using -doubleValue and –initWithDouble:. This will let you use the standard C symbols (+, -, ...) and functions (exp(), sin()). The only way you would run into trouble is if you were using close to the full precision for 64-bit integer values.
If you want to stick with Objective-C class operations you can use NSDecimalNumber instead.
See also: How to add two NSNumber objects?
How about calculating the expression value as a double (with all the inputs as double values), and then checking if the result is an integer? Then you just use NSNumber numberWithInt: or NSNumber numberWithDouble: to return the result.
When you check if the result value is an integer, be sure to account for the rounding error (e.g. when 1 is expressed as 0.99999999, etc).
EDIT: Just noticed in the docs for NSNumber this phrase:
Note that number objects do not necessarily preserve the type they are
created with.
I think this means you can't reliably do what you're trying to do.

Resources