Why is NSNumber immutable? Was there a good reason? Because now I am thinking about creating my own class just for the sake of mutability.
A number is a very basic data type. A number is just that - a number. If you mutate it, it just happens to be something else. A number simply cannot change.
Compare that with more complex data, where the object itself still represents the same thing.
Immutable numbers save space. Assume that your program creates many NSNumber
s, and most of them happen to be small numbers like 0 or 1. With immutable numbers, you only need a handful objects, one for each distinct value. With mutable numbers, you have as many objects as you have numbers.
Immutable numbers are easy to share. Assume that you wrap a primitive number (like int
) with an NSNumber
. With immutable NSNumber
, the compiler is always sure that the values match, so it can unwrap it and pass the primitive value to function that expect a primitive value. With mutable NSNumber
, you can't be sure than another thread did not change the value, and have to actually unwrap it every time, or even think about synchronization. This becomes more costly if the value is passed further and further in nested calls.
Immutable objects have many other useful properties: they are good hash keys, their lifetime and scope is easier to determine, etc. Many functional languages, e.g. Erlang or XSLT, only have immutable data structures.
NSNumber
objects are really tagged pointers. If the value is small enough, it gets encoded directly inside the pointer and there is no corresponding object allocated in memory. This won't be possible if NSNumber
is mutable. –
Grantgranta Eiko makes a good point: a NSNumber represents a basic data type and it makes no sense to make it mutable.
It's like having a int i=0;
and asking why 0 is not mutable. And in OS X Lion x64, it is exactly that for integers, because NSNumbers are implemented as tagged pointers, which are pointers that contain data instead an address.
Example, let’s say we want to store the integer 42 in a pointer. We could create a NSNumber and then point to it, or we could replace the address with a 42, in which case we can skip object creation. But how can we tell if we are dealing with a common pointer or a tagged pointer?
A x86 64 pointer has 64 bits, but only uses 48 bits for the pointer address. The reason is that 48 bit provides a 256 TB address space, which is a lot. Using 64 bits would be wasteful because it would require more transistors in the CPU. So the potential address space is 64bit, but current CPUs are only able to use 48. Because of this, the ending bits of a pointer are 0 because they are left unused. We use these last bits to indicate that the pointer is a tagged pointer that represents an integer with a given number of bits.
Therefore, a OS X NSNumber representing an integer is literally just an integer number, but the runtime is able to detect it as a tagged pointer and present it to the user as a common instance.
For other numeric types the implementation is way more complicated, as seen in the NSNumber toll-free Core Foundation counterpart CFNumber.
I think everybody had a pretty good answer except maybe 9000. not sure what he's talking about, though maybe it's just over my head.
The decision to make NSNumber immutable is a design decision by the creators of the Foundation Framework. I think we can all agree on that.
I assume the reason they did that was because all of the instantiated objects in Objective-C are referenced using pointers, including NSNumber. This causes some design concerns when passing NSNumber around. Let's say you create a class called "Person", with an NSNumber property, "myAge". So your application instantiates an instance of Person and sets myAge to 28. Some other part of the application now asks the Person object for its age, and it returns (NSNumber*)myAge, or a pointer to the NSNumber object that wraps the value 28. Since a pointer was passed, your Person object now has to wonder if that other part of the application changed the value of myAge!
So NSNumber is immutable, because it is an object meant to hold a value that is free to be created, retrieved, and passed around your application as a value, not as unique object.
A NSNumber Subclassing example
Note: as documentation says, objCType and value accessor of implemented type (intValue here) must be implemented.
This is done here with a designated initializer (-)init... but this may be done with the (+)method.
@interface NSMutableNumber : NSNumber
{
int intValue;
}
@property const char *objCType;
+ (id) mutablenumberWithInt:(int)value;
@end
@implementation NSMutableNumber
@synthesize objCType;
+ (id) mutablenumberWithInt:(int)value {
return [[self alloc] initWithInt:value];
}
- (id) initWithInt:(int)value {
if (self=[super init]) {
intValue=value;
objCType="i";
}
return self;
}
- (int)intValue {
return intValue;
}
- (void)setInt:(int)value {
intValue=value;
}
@end
Then
NSMutableNumber *mutn=[NSMutableNumber mutablenumberWithInt:2];
NSLog(@"%@\n", mutn);
// return 2
[mutn setInt:4];
NSLog(@"%@\n", mutn);
// return 4
I guess it is immutable, like other classes (NSArray, NSString, etc.), because immutable objects are easier to use and share and pass around in threaded code. See wikipedia.
If you want to create your on NSMutableNumber for whatever reason, and in order for you class to work properly, you would have to override most of the methods except a few; Keep in mind that changing a value of any type will result in changing all the others, and will update the objCType
property, so you would want to override your setters accordingly as well. I have written such a subclass that is mutable and behaves mostly like an NSNumber with a few different details and extra methods; The description
for a BOOL
type now returns a YES
or NO
string instead of 1
or 0
, also I have added initWithDecimal
, numberWithDecimal
, initWithLongDouble
and numberWithLongDouble
methods as well.
here it goes:
Here is your NSMutableNumber.h
file:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSMutableNumber : NSNumber
@property(nonatomic, readwrite) BOOL boolValue;
@property(nonatomic, readwrite) char charValue;
@property(nonatomic, readwrite) unsigned char unsignedCharValue;
@property(nonatomic, readwrite) short shortValue;
@property(nonatomic, readwrite) unsigned short unsignedShortValue;
@property(nonatomic, readwrite) int intValue;
@property(nonatomic, readwrite) unsigned int unsignedIntValue;
@property(nonatomic, readwrite) NSInteger integerValue;
@property(nonatomic, readwrite) NSUInteger unsignedIntegerValue;
@property(nonatomic, readwrite) long longValue;
@property(nonatomic, readwrite) unsigned long unsignedLongValue;
@property(nonatomic, readwrite) long long longLongValue;
@property(nonatomic, readwrite) unsigned long long unsignedLongLongValue;
@property(nonatomic, readwrite) float floatValue;
@property(nonatomic, readwrite) double doubleValue;
@property(nonatomic, readwrite) long double longDoubleValue;
@property(nonatomic, readwrite) NSDecimal decimalValue;
@property(nonatomic, readwrite) const char *objCType;
+ (NSMutableNumber *)numberWithBool:(BOOL)value;
+ (NSMutableNumber *)numberWithChar:(char)value;
+ (NSMutableNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSMutableNumber *)numberWithShort:(short)value;
+ (NSMutableNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSMutableNumber *)numberWithInt:(int)value;
+ (NSMutableNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSMutableNumber *)numberWithInteger:(NSInteger)value;
+ (NSMutableNumber *)numberWithUnsignedInteger:(NSUInteger)value;
+ (NSMutableNumber *)numberWithLong:(long)value;
+ (NSMutableNumber *)numberWithUnsignedLong:(unsigned long)value;
+ (NSMutableNumber *)numberWithLongLong:(long long)value;
+ (NSMutableNumber *)numberWithUnsignedLongLong:(unsigned long long)value;
+ (NSMutableNumber *)numberWithFloat:(float)value;
+ (NSMutableNumber *)numberWithDouble:(double)value;
+ (NSMutableNumber *)numberWithLongDouble:(long double)value;
+ (NSMutableNumber *)numberWithDecimal:(NSDecimal)value;
- (NSMutableNumber *)initWithBool:(BOOL)value;
- (NSMutableNumber *)initWithChar:(char)value;
- (NSMutableNumber *)initWithUnsignedChar:(unsigned char)value;
- (NSMutableNumber *)initWithShort:(short)value;
- (NSMutableNumber *)initWithUnsignedShort:(unsigned short)value;
- (NSMutableNumber *)initWithInt:(int)value;
- (NSMutableNumber *)initWithUnsignedInt:(unsigned int)value;
- (NSMutableNumber *)initWithInteger:(NSInteger)value;
- (NSMutableNumber *)initWithUnsignedInteger:(NSUInteger)value;
- (NSMutableNumber *)initWithLong:(long)value;
- (NSMutableNumber *)initWithUnsignedLong:(unsigned long)value;
- (NSMutableNumber *)initWithLongLong:(long long)value;
- (NSMutableNumber *)initWithUnsignedLongLong:(unsigned long long)value;
- (NSMutableNumber *)initWithFloat:(float)value;
- (NSMutableNumber *)initWithDouble:(double)value;
- (NSMutableNumber *)initWithLongDouble:(long double)value;
- (NSMutableNumber *)initWithDecimal:(NSDecimal)value;
- (NSString *)stringValue;
- (NSString *)description;
- (NSString *)descriptionWithLocale:(NSLocale *)locale;
@end
NS_ASSUME_NONNULL_END
Here is your NSMutableNumber.m
file:
#import "NSMutableNumber.h"
@interface NSMutableNumber ()
- (void)setAllIntValues:(unsigned long long)ULLongVal allFloats:(long double)LDoubleVal;
- (NSMutableNumber *)initAllints:(unsigned long long)ULLongVal allFloats:(long double)LDoubleVal;
@end
@implementation NSMutableNumber
@synthesize boolValue;
@synthesize charValue;
@synthesize unsignedCharValue;
@synthesize shortValue;
@synthesize unsignedShortValue;
@synthesize intValue;
@synthesize unsignedIntValue;
@synthesize integerValue;
@synthesize unsignedIntegerValue;
@synthesize longValue;
@synthesize unsignedLongValue;
@synthesize longLongValue;
@synthesize unsignedLongLongValue;
@synthesize floatValue;
@synthesize doubleValue;
@synthesize decimalValue;
@synthesize objCType;
// Designated initializer - private method
- (NSMutableNumber *)initAllints:(unsigned long long)ULLongVal allFloats:(long double)LDoubleVal
{
boolValue = ULLongVal;
charValue = (char)ULLongVal;
unsignedCharValue = (unsigned char)ULLongVal;
shortValue = (short)ULLongVal;
unsignedShortValue = (unsigned short)ULLongVal;
intValue = (int)ULLongVal;
unsignedIntValue = (unsigned int)ULLongVal;
integerValue = (NSInteger)ULLongVal;
unsignedIntegerValue = (NSUInteger)ULLongVal;
longValue = ULLongVal;
unsignedLongValue = ULLongVal;
longLongValue = ULLongVal;
unsignedLongLongValue = ULLongVal;
floatValue = LDoubleVal;
doubleValue = LDoubleVal;
_longDoubleValue = LDoubleVal;
NSNumber *decimalNumber = [NSNumber numberWithUnsignedLongLong:LDoubleVal];
NSDecimal decimal = [decimalNumber decimalValue];
decimalValue = decimal;
return self;
}
// Private method
- (void)setAllIntValues:(unsigned long long)ULLongVal allFloats:(long double)LDoubleVal
{
boolValue = ULLongVal;
charValue = (char)ULLongVal;
unsignedCharValue = (unsigned char)ULLongVal;
shortValue = (short)ULLongVal;
unsignedShortValue = (unsigned short)ULLongVal;
intValue = (int)ULLongVal;
unsignedIntValue = (unsigned int)ULLongVal;
integerValue = (NSInteger)ULLongVal;
unsignedIntegerValue = (NSUInteger)ULLongVal;
longValue = ULLongVal;
unsignedLongValue = ULLongVal;
longLongValue = ULLongVal;
unsignedLongLongValue = ULLongVal;
floatValue = LDoubleVal;
doubleValue = LDoubleVal;
_longDoubleValue = LDoubleVal;
NSNumber *decimalNumber = [NSNumber numberWithUnsignedLongLong:LDoubleVal];
NSDecimal decimal = [decimalNumber decimalValue];
decimalValue = decimal;
}
+ (NSMutableNumber *)numberWithBool:(BOOL)value
{
return [[super alloc] initWithBool:value];
}
+ (NSMutableNumber *)numberWithChar:(char)value
{
return [[super alloc] initWithChar:value];
}
+ (NSMutableNumber *)numberWithUnsignedChar:(unsigned char)value
{
return [[super alloc] initWithUnsignedChar:value];
}
+ (NSMutableNumber *)numberWithShort:(short)value
{
return [[super alloc] initWithShort:value];
}
+ (NSMutableNumber *)numberWithUnsignedShort:(unsigned short)value
{
return [[super alloc] initWithUnsignedShort:value];
}
+ (NSMutableNumber *)numberWithInt:(int)value
{
return [[super alloc] initWithInt:value];
}
+ (NSMutableNumber *)numberWithUnsignedInt:(unsigned int)value
{
return [[super alloc] initWithUnsignedInt:value];
}
+ (NSMutableNumber *)numberWithInteger:(NSInteger)value
{
return [[super alloc] initWithInteger:value];
}
+ (NSMutableNumber *)numberWithUnsignedInteger:(NSUInteger)value
{
return [[super alloc] initWithUnsignedInteger:value];
}
+ (NSMutableNumber *)numberWithLong:(long)value
{
return [[super alloc] initWithLong:value];
}
+ (NSMutableNumber *)numberWithUnsignedLong:(unsigned long)value
{
return [[super alloc] initWithUnsignedLong:value];
}
+ (NSMutableNumber *)numberWithLongLong:(long long)value
{
return [[super alloc] initWithLongLong:value];
}
+ (NSMutableNumber *)numberWithUnsignedLongLong:(unsigned long long)value
{
return [[super alloc] initWithUnsignedLongLong:value];
}
+ (NSMutableNumber *)numberWithFloat:(float)value
{
return [[super alloc] initWithFloat:value];
}
+ (NSMutableNumber *)numberWithDouble:(double)value
{
return [[super alloc] initWithDouble:value];
}
+ (NSMutableNumber *)numberWithLongDouble:(long double)value
{
return [[super alloc] initWithLongDouble:value];
}
+ (NSMutableNumber *)numberWithDecimal:(NSDecimal)value
{
return [[super alloc] initWithDecimal:value];
}
- (NSMutableNumber *)initWithBool:(BOOL)value;
{
if (self = [super init]) {
objCType = @encode(BOOL);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithChar:(char)value
{
if (self = [super init]) {
objCType = @encode(char);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithUnsignedChar:(unsigned char)value
{
if (self = [super init]) {
objCType = @encode(unsigned char);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithShort:(short)value
{
if (self = [super init]) {
objCType = @encode(short);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithUnsignedShort:(unsigned short)value
{
if (self = [super init]) {
objCType = @encode(unsigned short);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithInt:(int)value
{
if (self = [super init]) {
objCType = @encode(int);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithUnsignedInt:(unsigned int)value
{
if (self = [super init]) {
objCType = @encode(unsigned int);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithInteger:(NSInteger)value
{
if (self = [super init]) {
objCType = @encode(NSInteger);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithUnsignedInteger:(NSUInteger)value
{
if (self = [super init]) {
objCType = @encode(NSUInteger);
self =[self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithLong:(long)value
{
if (self = [super init]) {
objCType = @encode(long);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithUnsignedLong:(unsigned long)value
{
if (self = [super init]) {
objCType = @encode(unsigned long);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithLongLong:(long long)value
{
if (self = [super init]) {
objCType = @encode(long long);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithUnsignedLongLong:(unsigned long long)value
{
if (self = [super init]) {
objCType = @encode(unsigned long long);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithFloat:(float)value
{
if (self = [super init]) {
objCType = @encode(float);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithDouble:(double)value
{
if (self = [super init]) {
objCType = @encode(double);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithLongDouble:(long double)value
{
if (self = [super init]) {
objCType = @encode(long double);
self = [self initAllints:value allFloats:value];
}
return self;
}
- (NSMutableNumber *)initWithDecimal:(NSDecimal)value
{
if (self = [super init]) {
objCType = @encode(NSDecimal);
decimalValue = value;
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithDecimal:value];
unsigned long long UlongLongVal = [decimalNumber unsignedLongLongValue];
long double LDoubleVal = [decimalNumber doubleValue];
boolValue = UlongLongVal;
charValue = (char)UlongLongVal;
unsignedCharValue = (unsigned char)UlongLongVal;
shortValue = (short)UlongLongVal;
unsignedShortValue = (unsigned short)UlongLongVal;
intValue = (int)UlongLongVal;
unsignedIntValue = (unsigned int)UlongLongVal;
integerValue = (NSInteger)UlongLongVal;
unsignedIntegerValue = (NSUInteger)UlongLongVal;
longValue = (long)UlongLongVal;
unsignedLongValue = (unsigned long)UlongLongVal;
longLongValue = (long long)UlongLongVal;
unsignedLongLongValue = (unsigned long long)UlongLongVal;
floatValue = (float)LDoubleVal;
doubleValue = (double)LDoubleVal;
_longDoubleValue = LDoubleVal;
}
return self;
}
- (NSString *)stringValue
{
NSLocale *locale = [NSLocale systemLocale];
return [self descriptionWithLocale:locale];
}
- (NSString *)description
{
return [self stringValue];
}
- (NSString *)descriptionWithLocale:(NSLocale *)locale
{
NSString *description;
NSString *(^desc)(NSLocale *locale, NSString *format, ...);
desc = ^(NSLocale *locale, NSString *format, ...) {
va_list args;
va_start (args, format);
NSString *valueAsString = [[NSString alloc] initWithFormat:format locale:locale arguments:args];
va_end(args);
return valueAsString;
};
if (strcmp(objCType, @encode(BOOL)) == 0) {
if (self.boolValue) {
description = desc(locale, @"%YES");
} else {
description = desc(locale, @"%NO");
}
} else if (strcmp(objCType, @encode(char)) == 0) {
description = desc(locale, @"%hhi", self.charValue);
} else if (strcmp(objCType, @encode(unsigned char)) == 0) {
description = desc(locale, @"%hhu", self.unsignedCharValue);
} else if (strcmp(objCType, @encode(short)) == 0) {
description = desc(locale, @"%hd", self.shortValue);
} else if (strcmp(objCType, @encode(unsigned short)) == 0) {
description = desc(locale, @"%hu", self.unsignedShortValue);
} else if (strcmp(objCType, @encode(int)) == 0) {
description = desc(locale, @"%d", self.intValue);
} else if (strcmp(objCType, @encode(unsigned int)) == 0) {
description = desc(locale, @"%u", self.unsignedIntValue);
} else if (strcmp(objCType, @encode(NSInteger)) == 0) {
description = desc(locale, @"%ld", (long)self.integerValue);
} else if (strcmp(objCType, @encode(NSUInteger)) == 0) {
description = desc(locale, @"%lu", (long)self.unsignedIntegerValue);
} else if (strcmp(objCType, @encode(long)) == 0) {
description = desc(locale, @"%ld", self.longValue);
} else if (strcmp(objCType, @encode(unsigned long)) == 0) {
description = desc(locale, @"%lu", self.unsignedLongValue);
} else if (strcmp(objCType, @encode(long long)) == 0) {
description = desc(locale, @"%lld", self.longLongValue);
} else if (strcmp(objCType, @encode(unsigned long long)) == 0) {
description = desc(locale, @"%llu", self.unsignedLongLongValue);
} else if (strcmp(objCType, @encode(float)) == 0) {
description = desc(locale, @"%f", self.floatValue);
} else if (strcmp(objCType, @encode(double)) == 0) {
description = desc(locale, @"%lf", self.doubleValue);
} else if (strcmp(objCType, @encode(long double)) == 0) {
description = desc(locale, @"%Lf", self.longDoubleValue);
} else if (strcmp(objCType, @encode(NSDecimal)) == 0) {
NSDecimalNumber *decimal = [NSDecimalNumber decimalNumberWithDecimal:decimalValue];
description = desc(locale, @"%@", [decimal stringValue]);
}
return description;
}
- (void)setBoolValue:(BOOL)value
{
objCType = @encode(BOOL);
[self setAllIntValues:value allFloats:value];
}
- (void)setCharValue:(char)value
{
objCType = @encode(char);
[self setAllIntValues:value allFloats:value];
}
- (void)setUnsignedCharValue:(unsigned char)value
{
objCType = @encode(unsigned char);
[self setAllIntValues:value allFloats:value];
}
- (void)setShortValue:(short)value
{
objCType = @encode(short);
[self setAllIntValues:value allFloats:value];
}
- (void)setUnsignedShortValue:(unsigned short)value
{
objCType = (@encode(unsigned short));
[self setAllIntValues:value allFloats:value];
}
-(void)setIntValue:(int)value
{
objCType = @encode(int);
[self setAllIntValues:value allFloats:value];
}
-(void)setUnsignedIntValue:(unsigned int)value
{
objCType = @encode(unsigned int);
[self setAllIntValues:value allFloats:value];
}
- (void)setIntegerValue:(NSInteger)value
{
objCType = @encode(NSInteger);
[self setAllIntValues:value allFloats:value];
}
- (void)setUnsignedIntegerValue:(NSUInteger)value
{
objCType = @encode(NSUInteger);
[self setAllIntValues:value allFloats:value];
}
- (void)setLongValue:(long)value
{
objCType = @encode(long);
[self setAllIntValues:value allFloats:value];
}
- (void)setUnsignedLongValue:(unsigned long)value
{
objCType = @encode(unsigned long);
[self setAllIntValues:value allFloats:value];
}
- (void)setLongLongValue:(long long)value
{
objCType = @encode(long long);
[self setAllIntValues:value allFloats:value];
}
- (void)setUnsignedLongLongValue:(unsigned long long)value
{
objCType = @encode(unsigned long long);
[self setAllIntValues:value allFloats:value];
}
- (void)setFloatValue:(float)value
{
objCType = @encode(float);
[self setAllIntValues:value allFloats:value];
}
- (void)setDoubleValue:(double)value
{
objCType = @encode(double);
[self setAllIntValues:value allFloats:value];
}
- (void)setLongDoubleValue:(long double)value
{
objCType = @encode(long double);
[self setAllIntValues:value allFloats:value];
}
- (void)setDecimalValue:(NSDecimal)value
{
objCType = @encode(NSDecimal);
decimalValue = value;
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithDecimal:value];
unsigned long long UlongLongVal = [decimalNumber unsignedLongLongValue];
long double LDoubleVal = [decimalNumber doubleValue];
boolValue = UlongLongVal;
charValue = (char)UlongLongVal;
unsignedCharValue = (unsigned char)UlongLongVal;
shortValue = (short)UlongLongVal;
unsignedShortValue = (unsigned short)UlongLongVal;
intValue = (int)UlongLongVal;
unsignedIntValue = (unsigned int)UlongLongVal;
integerValue = (NSInteger)UlongLongVal;
unsignedIntegerValue = (NSUInteger)UlongLongVal;
longValue = (long)UlongLongVal;
unsignedLongValue = (unsigned long)UlongLongVal;
longLongValue = (long long)UlongLongVal;
unsignedLongLongValue = (unsigned long long)UlongLongVal;
floatValue = (float)LDoubleVal;
doubleValue = (double)LDoubleVal;
_longDoubleValue = LDoubleVal;}
@end
© 2022 - 2024 — McMap. All rights reserved.