Why is NSNumber immutable? - objective-c

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 NSNumbers, 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.

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

Related

Calculator App - strange error: 'unrecognized selector sent to instance' when trying to update display

I'm relatively new to Objective-C, so I'm not 100% about everything I'm coding. However, I'm tackling my errors as they happen, and I am getting a run-time error that I'm not sure how to fix. The error claims that an 'unrecognized selector [was] sent to instance.'
- (IBAction) equalsPressed{
self.userIsInTheMiddleOfTypingANumber = NO;
if (self.brain.operationIsPicked) {
[self.brain pushOperand: [self.display.text doubleValue]];
double result = [self.brain performOperation: self.brain.operation];
// The line below this
self.display.text = [NSString stringWithFormat: #"%g", result];
}
}
I'm doing the Calculator app taught by the Stanford professor whose course is posted in iTunes U. However, I edited it to not include an enterPressed command and instead use an altered and more user-friendly 'equalsPressed.' After equalsPressed is finished (meaning when I click the equals sign on the calculator), the error is displayed. Might anyone know what is causing this problem? I already found someone else who has an error very similar to this one, but occurring in a slightly different place. Here are the other code snippets that could help elucidate the problem.
#import "CalculatorBrain.h"
#interface CalculatorBrain()
#property (nonatomic, strong) NSMutableArray *operandStack;
#end
#implementation CalculatorBrain
#synthesize operandStack = _operandStack;
#synthesize operandStackIsEmpty = _operandStackIsEmpty;
#synthesize operationIsPicked = _operationIsPicked;
#synthesize operation = _operation;
- (NSMutableArray *) operandStack {
if (!_operandStack) {
_operandStack =[[NSMutableArray alloc] init];
_operandStackIsEmpty = YES;
_operationIsPicked = NO;
}
return _operandStack;
}
- (void) resetStack {
[self.operandStack removeAllObjects];
self.operandStackIsEmpty = YES;
self.operationIsPicked = NO;
}
- (void) pushOperand : (double) operand{
[self.operandStack addObject: [NSNumber numberWithDouble:operand]];
self.operandStackIsEmpty = NO;
}
- (double) popOperand {
NSNumber *num = [self.operandStack lastObject];
if (num) {[self.operandStack removeLastObject];}
return [num doubleValue];
}
- (double) performOperation : (NSString *) operation{
double result = 0;
double num2 = [self popOperand];
double num1 = [self popOperand];
if ([operation isEqualToString:#"+"])
result = num2 + num1;
else if ([operation isEqualToString:#"-"])
result = num2 - num1;
else if ([operation isEqualToString:#"*"] || [operation isEqualToString:#"x"])
result = num2 * num1;
else if ([operation isEqualToString:#"/"]){
if (num2 == 0)
[self resetStack];
else
result = num2 / num1;
}
[self pushOperand:result];
self.operationIsPicked = NO;
return result;
}
#end
Also, the debugging NSLog that I had print the display in the console works correctly, BUT the display in the Calculator view doesn't actually update (when I used breakpoints to slow the function down).
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController ()
#property (nonatomic) BOOL userIsInTheMiddleOfTypingANumber;
#property (nonatomic, strong) CalculatorBrain *brain;
#end
#implementation CalculatorViewController
#synthesize userIsInTheMiddleOfTypingANumber = _userIsInTheMiddleOfTypingANumber;
#synthesize brain = _brain;
#synthesize display = _display;
- (CalculatorBrain *) brain {
if (!_brain) {
_brain =[[CalculatorBrain alloc] init];
}
return _brain;
}
- (IBAction) digitPressed: (UIButton *)sender {
NSString *digit = [sender currentTitle];
if (self.userIsInTheMiddleOfTypingANumber)
self.display.text = [self.display.text stringByAppendingString: digit];
else {
self.display.text = digit;
self.userIsInTheMiddleOfTypingANumber = YES;
}
}
- (IBAction) clearPressed {
self.userIsInTheMiddleOfTypingANumber = NO;
self.display.text = #"0";
[self.brain resetStack];
}
- (IBAction) equalsPressed {
self.userIsInTheMiddleOfTypingANumber = NO;
if (self.brain.operationIsPicked) {
[self.brain pushOperand: [self.display.text doubleValue]];
double result = [self.brain performOperation: self.brain.operation];
self.display.text = [NSString stringWithFormat: #"%g", result];
NSLog(#"%#",self.display.text);
}
NSLog(#"%#",self.display.text);
}
- (IBAction) operationPressed:(UIButton *) sender {
if (self.userIsInTheMiddleOfTypingANumber) {
[self.brain pushOperand: [self.display.text doubleValue]];
self.brain.operation = [sender currentTitle];
self.brain.operationIsPicked = YES;
self.userIsInTheMiddleOfTypingANumber = NO;
NSLog(#"%#", sender.currentTitle);
}
}
Thank you!
To quote the answer in the comments:
"The full error said that I sent an invalid argument to "[self.brain equalsPressed:]". In reality, equalsPressed takes no argument, but when I was linking the '=' button in my view to its respective method, I forgot to indicate that the function took no arguments. Hence even though the function as I wrote it has no arguments, I told the compiler (to begin with) that it was supposed to take an argument."

key-value coding compliance

I was foolish and didn't test continually as I programmed, so now I'm not sure where the error has crept in. Am working on a programmable calculator. When I run, it crashes before displaying anything and gives me this message:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<CalculatorViewController 0x6a405a0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key description.'
I'd use NSLog to look for the bug, but I don't know where to try it when the crash happens before anything shows up. Thoughts on what I'm doing wrong?
Here's CalculatorViewController.m, with some extra property declarations for unfinished, commented-out code I've omitted:
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController ()
#property (nonatomic) BOOL userIsEnteringNumber;
#property (nonatomic) BOOL numberIsNegative;
#property (nonatomic,strong) CalculatorBrain *brain;
#end
#implementation CalculatorViewController
#synthesize display = _display;
#synthesize descriptionLabel = _descriptionLabel;
#synthesize userIsEnteringNumber = _userIsEnteringNumber;
#synthesize numberIsNegative;
#synthesize brain = _brain;
-(CalculatorBrain *)brain
{
if (!_brain) _brain = [[CalculatorBrain alloc] init];
return _brain;
}
//This adds a pressed digit to the display label.
- (IBAction)digitPressed:(UIButton *)sender
{
NSString *digit = sender.currentTitle;
//Enter digit if it wouldn't create a two-decimal-point case.
NSRange range = [self.display.text rangeOfString:#"."];
if (range.location==NSNotFound || (![digit isEqualToString:#"."]))
if (self.userIsEnteringNumber)
{
self.display.text = [self.display.text stringByAppendingString:digit];
self.descriptionLabel.text = [self.display.text stringByAppendingString:#" "];
}
else
{
self.descriptionLabel.text = [self.descriptionLabel.text stringByAppendingString:digit];
self.descriptionLabel.text = [self.descriptionLabel.text stringByAppendingString:#" "];
if (![sender.currentTitle isEqualToString:#"."])
{
self.display.text = digit;
}
else
{
self.display.text = #"0.";
}
self.userIsEnteringNumber = YES;
}
}
//This sets up an operation.
- (IBAction)operationPressed:(UIButton *)sender
{
if (self.userIsEnteringNumber) [self enterPressed];
NSString *operation = sender.currentTitle;
double result = [self.brain performOperation:operation];
self.display.text = [NSString stringWithFormat:#"%g",result];
{
NSString *descr = [self.brain description];
self.descriptionLabel.text = descr;
}
}
- (IBAction)enterPressed
{
NSCharacterSet *set = [NSCharacterSet decimalDigitCharacterSet];
NSRange range = [self.display.text rangeOfCharacterFromSet:set];
if (range.location==NSNotFound)
{
[self.brain pushOperandAsVariable:self.display.text];
}
else
{
[self.brain pushOperand:[self.display.text doubleValue]];
}
self.userIsEnteringNumber = NO;
}
#end
And here's CalculatorBrain.m:
#import "CalculatorBrain.h"
#interface CalculatorBrain()
#property (nonatomic, strong) NSMutableArray *programStack;
#property (nonatomic,strong)NSDictionary *variableValues;
#end
#implementation CalculatorBrain
#synthesize programStack = _programStack;
#synthesize variableValues = _variableValues;
- (NSMutableArray *)programStack
{
if (!_programStack) _programStack = [[NSMutableArray alloc] init];
return _programStack;
}
- (id)program
{
return [self.programStack copy];
}
//Here are the two types of pushes that the ViewController can implement. First, operand pushes . . .
- (void)pushOperand:(double)operand
{
[self.programStack addObject:[NSNumber numberWithDouble:operand]];
}
//. . . and then variable pushes.
- (void) pushOperandAsVariable:(NSString *)variable
{
//Create dictionary
//Move this later on to ViewController but for now leave where it is....
NSMutableArray *variablesUsed = [[NSMutableArray alloc] init];
NSArray *objects = [[NSArray alloc] initWithObjects:[NSNumber numberWithDouble:3],[NSNumber numberWithDouble:4.1],[NSNumber numberWithDouble:-6],[NSNumber numberWithDouble:4.5298], [NSNumber numberWithDouble:3.14159], nil];
NSArray *keys = [[NSArray alloc] initWithObjects:#"x",#"y",#"z",#"foo", #"π", nil];
NSDictionary *variableValues = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
//Check program for keys
NSNumber *operand;
for (int i=0; i<keys.count; i++)
{
if ([[keys objectAtIndex:i] isEqual:variable])
[variablesUsed addObject:variable];
operand = [variableValues objectForKey:variable];
}
[self.programStack addObject:operand];
}
- (double)performOperation:(NSString *)operation
{
[self.programStack addObject:operation];
return [[self class] runProgram:self.program];
}
+ (double)popOffStack:(NSMutableArray *)stack
{
double result = 0;
id topOfStack = [stack lastObject];
if (topOfStack) [stack removeLastObject];
if ([topOfStack isKindOfClass:[NSNumber class]])
{
result = [topOfStack doubleValue];
}
//Here are the results for various operations.
else if ([topOfStack isKindOfClass:[NSString class]])
{
NSString *operation = topOfStack;
if ([operation isEqualToString:#"+"])
{
result = [self popOffStack:stack] +
[self popOffStack:stack];
}
else if ([#"*" isEqualToString:operation])
{
result = [self popOffStack:stack] *
[self popOffStack:stack];
}
else if ([operation isEqualToString:#"-"])
{
double subtrahend = [self popOffStack:stack];
result = [self popOffStack:stack] - subtrahend;
}
else if ([operation isEqualToString:#"/"])
{
double divisor = [self popOffStack:stack];
if (divisor) result = [self popOffStack:stack] / divisor;
}
else if ([operation isEqualToString:#"sin"])
{
result = sin([self popOffStack:stack]);
}
else if ([operation isEqualToString:#"cos"])
{
result = cos([self popOffStack:stack]);
}
else if ([operation isEqualToString:#"√"])
{
result = sqrt([self popOffStack:stack]);
}
else if ([operation isEqualToString:#"π"])
{
result = M_PI;
}
}
return result;
}
+ (double)runProgram:(id)program
{
//Run program.
NSMutableArray *mutableCopyOfProgram;
if ([program isKindOfClass:[NSArray class]])
{
mutableCopyOfProgram = [program mutableCopy];
return [self popOffStack:mutableCopyOfProgram];
}
else return 0;
}
#end
As always, thanks for your help.
The most common cause of this is that you have a CalculatorViewController object defined in a storyboard or xib file and something in the graphical interface has a link to an outlet on it called "description". Meanwhile, you no longer have a description outlet in the code for that class.
This will usually be fixed by tracking down the stray reference in interface builder and getting rid of it.

Realtime Calculator

For a school assignment I have been told to make a calculator app, the same as the spotlight calculator. It works in realtime and has no buttons for things to begin.
So far this is my code. It is written in a text field with the event Editing Did End. Im pretty sure thats wrong but i can't find an alternative solution. Also i haven't gotten the realtime thing to work so i've kind of reverted to completing the following steps when pressed off the text field.
- (IBAction)Didend_Action:(id)sender {
NSString *list = [Sum_TextField text];
NSArray *listItemsArray = [list componentsSeparatedByString:#" "];
float firstNumber = [[listItemsArray objectAtIndex: 0] floatValue];
NSString *symbol = [listItemsArray objectAtIndex: 1];
float secondNumber = [[listItemsArray objectAtIndex: 2] floatValue];
{
Calculator* calc = [[Calculator alloc] init];
[calc setNum1:firstNumber];
[calc setNum2:secondNumber];
if ([symbol isEqualToString:#"-"])
{
[calc minus];
}
else if ([symbol isEqualToString:#"+"])
{
[calc add];
}
if ([symbol isEqualToString:#"*"])
{
[calc multiply];
}
else if ([symbol isEqualToString:#"/"])
{
[calc divide];
}
[Answer_TextField setText:[NSString stringWithFormat:#"%d", [calc answer]]];
}
}
I think a better way to do it would be to implement the UITextViewDelegate protocol methods like textViewDidChange:. For example, you could do something like this:
- (void)textViewDidChange:(UITextView *)textView {
NSString *currentText = [textview text];
NSArray *currentItems = [currentText componenetsSeparatedByString:#" "];
float result = 0.0;
//If a valid expression is in the text view
if([currentItems count] > 2) {
float num1 = [[currentItems objectAtIndex:0] floatValue];
float num2 = [[currentItems objectAtIndex:2] floatValue];
NSString *operator = [currentItems objectAtIndex:1];
if([operator isEqualToString:#"+"]) {
result = num1 + num2;
answerTextField.text = [NSString stringWithFormat:#"%f", result];
}
else if([operator isEqualToString:#"-"]) {
result = num1 - num2;
answerTextField.text = [NSString stringWithFormat:#"%f", result];
}
else if([operator isEqualToString:#"*"]) {
result = num1 * num2;
answerTextField.text = [NSString stringWithFormat:#"%f", result];
}
else if([operator isEqualToString:#"/"]) {
result = num1 / num2;
answerTextField.text = [NSString stringWithFormat:#"%f", result];
}
else{
answerTextField.text = #"Invalid Operation";
}
}
}
This would be called every time the user edited the text in the text view. It should work, but I didn't test it out. Make sure that in the header of whatever file this code is in, you do this:
#interface yourClassName : yourSuperclass <UITextViewDelegate> {
//Your instance variables
}
//Your method and property declarations
EDIT:
Let's say I put the - (void)textViewDidChange:(UITextView *)textView code in a file called MyClass.m. The file MyClass.m would then look like this:
#implementation MyClass
- (void)textViewDidChange:(UITextView *)textView {
//All the above code goes here
}
- (void)viewDidLoad
{
[super viewDidLoad];
//INCLUDE THESE LINES
Sum_TextField.delegate = self;
Answer_TextField.delegate = self;
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
In the header file (MyClass.h), I would put this:
#interface MyClass : UIViewController <UITextViewDelegate>
//Note: you don't declare - (void)textViewDidChange:(UITextView *)textView in the header file because you are implementing
//a protocol method.
//MAKE SURE SUM_TEXTFIELD AND ANSWER_TEXTFIELD ARE UITEXTVIEWS NOT UITEXTFIELDS
#property (strong, nonatomic) IBOutlet UITextView *Sum_TextField;
#property (strong, nonatomic) IBOutlet UITextView *Answer_TextField;
#end
Hope this helps!

iOS RPNCalculatorStanfordVidOne can't figure out why all but one line is working

I am 100% brand new to iOS.
I am watching Stanford U's videos online to learn iOS. I spent hours slowly going through the second video meticulously ensuring I didn't goof on a line of code. Everything was perfect until - Wouldn't ya know it - the LAST minute of coding.
My wife and I spent a good amount of time trying to figure it out but my code matches the professor's as best we can tell. Please help me. I am loving learning through these videos but I can't move on until I have this functioning and take time to review it.
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController()
#property (nonatomic) BOOL userIsInTheMiddleOfEnteringANumber;
#property (nonatomic, strong) CalculatorBrain *brain;
#end
#implementation CalculatorViewController
#synthesize display = _display;
#synthesize userIsInTheMiddleOfEnteringANumber =
_userIsInTheMiddleOfEnteringANumber;
#synthesize brain = _brain;
- (CalculatorBrain *)brain
{
if (!_brain) _brain = [[CalculatorBrain alloc] init];
return _brain;
}
- (IBAction)digitPressed:(UIButton *)sender
{
NSString *digit = sender.currentTitle;
if (self.userIsInTheMiddleOfEnteringANumber)
{
self.display.text = [self.display.text stringByAppendingString:digit];
}
else
{
self.display.text = digit;
self.userIsInTheMiddleOfEnteringANumber = YES;
}
}
- (IBAction)enterPressed
{
[self.brain pushOperand:[self.display.text doubleValue]];
self.userIsInTheMiddleOfEnteringANumber = NO;
}
- (IBAction)operationPressed:(id)sender
{
if (self.userIsInTheMiddleOfEnteringANumber) [self enterPressed];
double result = [self.brain performOperation:sender.currentTitle]; // <- HERE IS THE LINE IN QUESTION
NSString *resultString = [NSString stringWithFormat:#"%g", result];
self.display.text = resultString;
}
#end
CalculatorBrain.m
#import "CalculatorBrain.h"
#interface CalculatorBrain()
#property (nonatomic, strong) NSMutableArray *operandStack;
#end
#implementation CalculatorBrain
#synthesize operandStack = _operandStack;
- (NSMutableArray *) operandStack
{
if (_operandStack == nil) _operandStack = [[NSMutableArray alloc] init];
return _operandStack;
}
- (void)pushOperand:(double)operand
{
[self.operandStack addObject:[NSNumber numberWithDouble:operand]];
}
- (double)popOperand
{
NSNumber *operandObject = [self.operandStack lastObject];
if (operandObject) [self.operandStack removeLastObject];
return [operandObject doubleValue];
}
- (double)performOperation:(NSString *)operation
{
double result = 0;
if ([operation isEqualToString:#"+"])
{
result = [self popOperand] + [self popOperand];
}
else if ([#"*" isEqualToString:operation])
{
result = [self popOperand] * [self popOperand];
}
[self pushOperand:result];
return result;
}
#end
I think the problem is with your if statement not closing properly.
- (IBAction)operationPressed:(id)sender
{
if (self.userIsInTheMiddleOfEnteringANumber){ [self enterPressed];
double result = [self.brain performOperation:sender.currentTitle];
NSString *resultString = [NSString stringWithFormat:#"%g", result];
self.display.text = resultString;
}
}
or
- (IBAction)operationPressed:(id)sender
{
if (self.userIsInTheMiddleOfEnteringANumber)
{
[self enterPressed];
}
double result = [self.brain performOperation:sender.currentTitle];
NSString *resultString = [NSString stringWithFormat:#"%g", result];
self.display.text = resultString;
}
Suggest looking at the pdf file of the tutorial for a more concrete code source. Hope that help.

Don't know how to access property from class method in Xcode

I want to access the property storedValue from the class method popOperandOffStack:, but I get an error. I read that you cannot access storedValue because it is a class method or something like that. Can anyone tell me how can I access storedValue or how can I make storedValue global so that I can use it in popOperandOffStack:?
Here is my header:
#import <Foundation/Foundation.h>
#interface CalculatorBrain : NSObject
- (void)pushOperand:(double)operand; // - = instance method
- (double)performOperation: (NSString *)operation;
#property (nonatomic,readonly) id program;
+ (double)runProgram:(id)program; // + = class method
+ (NSString *)descriptionOfProgram:(id)program;
#end
And this is my m file:
#import "CalculatorBrain.h"
#interface CalculatorBrain()
#property (nonatomic,strong) NSMutableArray *programStack;
#property (nonatomic) double storedValue;
#end
#implementation CalculatorBrain
#synthesize programStack = _programStack;
#synthesize storedValue = _storedValue;
- (NSMutableArray *)programStack
{
if (_programStack == nil)
_programStack = [[NSMutableArray alloc] init];
return _programStack;
}
- (void)setOperandStack:(NSMutableArray *)programStack
{
_programStack = programStack;
}
- (void)pushOperand:(double)operand
{
[self.programStack addObject:[NSNumber numberWithDouble:operand]];
}
- (double)performOperation: (NSString *)operation
{
[self.programStack addObject:operation];
return [[self class] runProgram:self.program];
}
- (id)program
{
return [self.programStack copy];
}
+ (NSString *)descriptionOfProgram:(id)program
{
return #"Implement this in Assignment 2";
}
+ (double) popOperandOffStack:(NSMutableArray *)stack
{
double result = 0;
id topOfStack = [stack lastObject];
if (topOfStack)
[stack removeLastObject];
if ([topOfStack isKindOfClass:[NSNumber class]])
{
result = [topOfStack doubleValue];
}
else if ([topOfStack isKindOfClass:[NSString class]])
{
NSString *operation = topOfStack;
if ([operation isEqualToString:#"+"]){
result = [self popOperandOffStack:stack] + [self popOperandOffStack:stack];
} else if ([operation isEqualToString:#"-"]){
result = -[self popOperandOffStack:stack] + [self popOperandOffStack:stack];
} else if ([operation isEqualToString:#"*"]){
result = [self popOperandOffStack:stack] * [self popOperandOffStack:stack];
} else if ([operation isEqualToString:#"/"]){
double divide = [self popOperandOffStack:stack];
if(divide){
result = [self popOperandOffStack:stack] / divide;
} else {
result = 0;
}
} else if ([operation isEqualToString:#"1/x"]){
result = 1 / [self popOperandOffStack:stack];
} else if ([operation isEqualToString:#"sqrt"]){
double sqrtNumber = [self popOperandOffStack:stack];
if (sqrtNumber > 0){
result = sqrt(sqrtNumber);
} else {
result = 0;
}
} else if ([operation isEqualToString:#"sin"]){
result = sin( [self popOperandOffStack:stack]);
} else if ([operation isEqualToString:#"cos"]){
result = cos( [self popOperandOffStack:stack]);
} else if ([operation isEqualToString:#"MS"]){
self.storedValue = [self popOperandOffStack:stack];
} else if ([operation isEqualToString:#"MR"]){
result = self.storedValue; //error here
} else if ([operation isEqualToString:#"MC"]){
result = [self popOperandOffStack:stack];
self.storedValue = 0; //error here
} else if ([operation isEqualToString:#"M+"]){
result = [self popOperandOffStack:stack];
self.storedValue = self.storedValue + result; //error here
} else if ([operation isEqualToString:#"M-"]){
result = [self popOperandOffStack:stack];
self.storedValue = self.storedValue - result; //error here
} else if ([operation isEqualToString:#"C"]){
result = 0;
while ( [self popOperandOffStack:stack] )
{
//clear operands until returns NO
}
} else if ([operation isEqualToString:#"CE"]){
result = 0;
self.storedValue = 0; //error here
while ( [self popOperandOffStack:stack] )
{
//clear operands until returns NO
}
}
if (![operation isEqualToString:#"MS"] && ![operation isEqualToString:#"CE"])
{
return result;
} else {
return self.storedValue;
}
}
return result;
}
+ (double)runProgram:(id)program
{
NSMutableArray *stack;
if ([program isKindOfClass:[NSArray class]]) {
stack = [program mutableCopy];
}
return [self popOperandOffStack:stack];
}
#end
I cannot use self.storedValue. What should I do besides changing the + (static method) to - (instance method) (I am not allowed to do that)?
You can't use instance getter/setter methods from inside a class method because you don't have an object that is an instance of CalculatorBrain. (I haven't used that course but I would hope that it explains the difference between classes and objects.)
Even though it's not a good idea from the point of view of creating an object-oriented program, you could use a static variable to hold your double. Look at a C language reference for the phrase "external static" to see examples.