Well, for integers I would use NSNumber. But YES and NO aren't objects, I guess. A.f.a.i.k. I can only add objects to an NSDictionary, right?
I couldn't find any wrapper class for booleans. Is there any?
You use NSNumber.
It has init... and number... methods that take booleans, just as it does integers and so on.
From the NSNumber class reference:
// Creates and returns an NSNumber object containing a
// given value, treating it as a BOOL.
+ (NSNumber *)numberWithBool:(BOOL)value
and:
// Returns an NSNumber object initialized to contain a
// given value, treated as a BOOL.
- (id)initWithBool:(BOOL)value
and:
// Returns the receiver’s value as a BOOL.
- (BOOL)boolValue
The new syntax since Apple LLVM Compiler 4.0
dictionary[#"key1"] = #(boolValue);
dictionary[#"key2"] = #YES;
The syntax converts BOOL to NSNumber, which is acceptable to NSDictionary.
If you are declaring it as a literal and you are using clang v3.1 or above, you should use #NO / #YES if you are declaring it as a literal. E.g.
NSMutableDictionary* foo = [#{ #"key": #NO } mutableCopy];
foo[#"bar"] = #YES;
For more info on that:
http://clang.llvm.org/docs/ObjectiveCLiterals.html
As jcampbell1 pointed out, now you can use literal syntax for NSNumbers:
NSDictionary *data = #{
// when you always pass same value
#"someKey" : #YES
// if you want to pass some boolean variable
#"anotherKey" : #(someVariable)
};
Try this:
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setObject:[NSNumber numberWithBool:TRUE] forKey:#"Pratik"];
[dic setObject:[NSNumber numberWithBool:FALSE] forKey:#"Sachin"];
if ([dic[#"Pratik"] boolValue])
{
NSLog(#"Boolean is TRUE for 'Pratik'");
}
else
{
NSLog(#"Boolean is FALSE for 'Pratik'");
}
if ([dic[#"Sachin"] boolValue])
{
NSLog(#"Boolean is TRUE for 'Sachin'");
}
else
{
NSLog(#"Boolean is FALSE for 'Sachin'");
}
The output will be as following:
Boolean is TRUE for 'Pratik'
Boolean is FALSE for 'Sachin'
Related
So I've tested this, but just wanting to make sure it wasn't some random undefined behavior. I want to use the shorthand ternary on a dictionary but I want it to return a default value when it is not set. Here is the test I wrote:
NSMutableDictionary *test = [NSMutableDictionary new];
test[#"test_1"] = #NO;
test[#"test_2"] = #YES;
BOOL test1 = [test[#"test_1"] boolValue] ?: NO;
BOOL test2 = [test[#"test_2"] boolValue] ?: NO;
BOOL test3 = [test[#"test_3"] boolValue] ?: NO;
LogDebug(#"test1 = %#", (test1 ? #"YES" : #"NO"));
LogDebug(#"test2 = %#", (test2 ? #"YES" : #"NO"));
LogDebug(#"test3 = %#", (test3 ? #"YES" : #"NO"));
Now I got the correct value for test3, but I'm wondering whether thats just a random fluke of undefined behavior. I'm wondering this because when I checked for that value in the debugger by typing this in the console:
po [test[#"test_3"] boolValue]
I got:
error: no known method '-boolValue'; cast the message send to the method's return type
error: 1 errors parsing expression
Are the results of test3 reliable?
NSDictionary (and its subclass, NSMutableDictionary) is documented as returning nil for a key with no value.
In Objective-C, sending a message to nil is documented to return NO when you expect a BOOL result.
Therefore, if you want the default value to be NO, this suffices:
BOOL test3 = [test[#"test_3"] boolValue];
If you only intend to store NSString keys and NSNumber values (which #YES and #NO are) in the dictionary, you can declare it like this:
NSMutableDictionary<NSString *, NSNumber *> *test = [NSMutableDictionary new];
And then you can use a property accessor if you prefer:
BOOL test3 = test[#"test_3"].boolValue;
If you want a different default value (and the only other possibility for BOOL values is YES), then #trojanfoe's suggestion of creating a method is what I'd recommend, but I'd probably make a more general category on NSDictionary like this:
// NSDictionary+kailoon.h
#interface NSDictionary<KeyType, ObjectType> (kailoon)
- (ObjectType)kailoon_objectForKey:(KeyType)key default:(ObjectType)defaultObject;
#end
// NSDictionary+kailoon.m
#implementation NSDictionary (kailoon)
- (id)kailoon_objectForKey:(id)key default:(id)defaultObject {
id value = self[key];
return value ? value : defaultObject;
}
#end
Then I'd use it like this:
BOOL test3 = [test kailoon_objectForKey:#"test_3" default:#YES].boolValue;
As for your debugger problem, it's incorrect to use po to print a BOOL, because po means “print object” and a BOOL is not an object. (It is a primitive.) So you should just use p. But that's not really the problem here. To fix the “no known method” problem, you use a cast, like this:
(lldb) p (BOOL)[test[#"test_3"] boolValue]
(BOOL) $1 = NO
The output you are getting from your code is correct and defined.
First off, your code can be simplified to:
BOOL test1 = [test[#"test_1"] boolValue];
BOOL test2 = [test[#"test_2"] boolValue];
BOOL test3 = [test[#"test_3"] boolValue];
You will get NO for any key that doesn't exist. If test[#"some_nonexistent_key"] returns nil, calling boolValue on nil results in NO.
The debugger is just getting confused because it doesn't know what to do with the call to boolValue on nil since there are more than one possible boolValue methods (NSNumber and NSString).
That code is reliable, but only for booleans and only when the default value is NO, as non-existence of the value in the dictionary will give NO and hence the default value will be used.
However it's somewhat clumsy as:
[test[#"test_3"] boolValue] ?: NO;
is basing the result of the ternary expression on whether the boolean stored in the NSNumber is YES or NO, when what you want to know is is the value set in the dictionary?.
This is better:
BOOL test1 = test[#"test_1"] != nil ? [test[#"test_1"] boolValue] : NO;
and will use the default value only if the dictionary does not contain the specified key. This pattern can also be extended to other types and can also be used if the default value is YES.
Wrapped into a method (recommended), it would be the following (this code also checks the value type):
- (BOOL)boolForKey:(NSString *)key defaultValue:(BOOL)defaultValue
{
id value = self.dictionary[key];
return [value isKindOfClass:[NSNumber class]] ? [value boolValue] : defaultValue;
}
In Objective C, I have:
NSMutableArray *retVal = [[NSMutableArray alloc]initWithCapacity:1];
NSMutableString *justTest = [[NSMutableString alloc]initWithString:#"hello"];
unsigned char ch = //anything
[retVal insertObject:[justTest appendFormat:#"%hhu", ch] atIndex:0]; //error here
X Code 5.1.1 gives me an error in the 4th line(as mentioned as comment) as Sending 'void' to parameter of incompatible type 'id'
What am I doing wrong here? Any help is appreciated.
If you have a read of the Apple Documentation for NSMutableString you will find that the instance method appendString: doesn't actually return a value. It adds a structured string to the end of the receive and that's it.
So when you do [retVal insertObject:[justTest appendFormat:#"%hhu", ch] atIndex:0]; you are actually really doing [retVal insertObject:void atIndex:0]; and obviously you can't pass void in as a parameter which expects a valid object of id.
Here's the method declaration: - (void)appendFormat:(NSString *)format ... which you can see has a return type of void.
So what you need to be doing is you need to make the amendment to the string before you pass it into the insertObject:atIndex: method.
So change to
[justTest appendFormat:#"%hhu", ch]; // Append to existing string, DOESN'T return anything
[retVal insertObject:justTest atIndex:0]; // Pass string in as object at index
appendFormat doesn't return anything, it adjusts the mutable string. You need to do this:
NSMutableArray *retVal = [[NSMutableArray alloc]initWithCapacity:1];
NSMutableString *justTest = [[NSMutableString alloc]initWithString:#"hello"];
unsigned char ch = //anything
[justTest appendFormat:#"%hhu", ch]
[retVal insertObject:justTest atIndex:0]; //error here
[justTest appendFormat:#"%hhu", ch] return void.
You need:
[retVal insertObject:([justTest appendFormat:#"%hhu", ch],justTest) atIndex:0];
I have set up my simple Xcode project with a table that is binded to an array controller. It works fine if the array controller is full of entities with a string attribute. However I want to change the attribute to a BOOL and have the table show the string "true" or "false" based on the BOOL.
I have overrided the following two methods from NSFormatter:
-(NSString*) stringForObjectValue:(id)object {
//what is the object?
NSLog(#"object is: %#", object);
if(![object isKindOfClass: [ NSString class ] ] ) {
return nil;
}
//i'm tired....just output hello in the table!!
NSString *returnStr = [[NSString alloc] initWithFormat:#"hello"];
return returnStr;
}
-(BOOL)getObjectValue: (id*)object forString:string errorDescription:(NSString**)error {
if( object ) {
return YES;
}
return NO;
}
So the table gets populated with "hello" if the attribute is a string however if I switch it to a boolean, then the table gets populated with lots of blank spaces.
I don't know if this helps but on the line where I'm outputting the object, it outputs __NSCFString if the attribute is a string and "Text Cell" if I switch the attribute to a boolean. This is something else I don't understand.
Ok, it's not 100% clear what you're trying to do from the code, but first things first - BOOL is not an object, it's basically 0 or 1, so to place BOOL values into an array, you're probably best off using NSNumber:
NSNumber *boolValue = [NSNumber numberWithBool:YES];
and placing these into your array. Now you want to change your method:
-(NSString*) stringForObjectValue:(id)object {
NSNumber *number = (NSNumber *)object;
if ([number boolValue] == YES)
return #"true";
else
return #"false";
}
There's a few things here - for example, you want to avoid passing around id references if you can (if you know all your objects in the NSArray are NSNumber, you shouldn't need to).
I have this statement:
[custData setObject: [rs stringForColumnIndex:2] forKey: #"email"];
where [rs stringForColumnIndex:2] obtained from a SQLite3 d/b has a value of nil. The app crashes giving me the error:
NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: email)'
Is there a way to prevent this? (like a setting for NSMutableDictionary?)
UPDATE: this is what I finally did:
[custData setObject: ([rs stringForColumnIndex:2] != nil? [rs stringForColumnIndex:2]:#"") forKey: #"email"];
There is a non-nil object called NSNull that is built specifically to represent nils in situations where "plain" nil is not acceptable. If you replace your nils with [NSNull null] object, NSDictionary will take them. You would need to check for NSNull on the way out, though.
Note that this is important only when you must differentiate between a value not being set and a value being set to nil. If your code is such that it can interpret a missing value as nil, you do not need to use NSNull at all.
It is not possible with a pure NSMutableDictionary, and in most cases you want to convert nil values into [NSNull null] or just omit them from the dictionary. Sometimes (very seldom), though, it is convenient to allow nil values, and in those cases you can use CFMutableDictionary with custom callbacks.
If you go this way, I recommend that you use CoreFoundation API for all accesses, e.g. CFDictionarySetValue and CFDictionaryGetValue.
However, if you know what you're doing, you can use toll-free bridging and cast that CFMutableDictionary to NSMutableDictionary or NSDictionary. This may be useful if you have a bunch of helpers that accept NSDictionary, and you want to use them on your modified nil-capable dictionary. (Of course, make sure that the helpers aren't surprised by nil values.)
If you do the bridging, note that:
1) NSMutableDictionary setter raises errors on nil values before bridging, so you need to use CFDictionarySetValue to set values that are potentially nil.
2) technically, we're violating a contract of NSMutableDictionary here, and things may break (e.g. in future OS updates)
3) a lot of code will be very surprised to find nil values in a dictionary; you should only pass the bridged frankendictionaries to the code that you control
See ridiculousfish's post on toll-free bridging for an explanation of why a bridged CFDictionary behaves differently from NSDictionary.
Example:
#import <Foundation/Foundation.h>
const void *NullSafeRetain(CFAllocatorRef allocator, const void *value) {
return value ? CFRetain(value) : NULL;
}
void NullSafeRelease(CFAllocatorRef allocator, const void *value) {
if (value)
CFRelease(value);
}
const CFDictionaryValueCallBacks kDictionaryValueCallBacksAllowingNULL = {
.version = 0,
.retain = NullSafeRetain,
.release = NullSafeRelease,
.copyDescription = CFCopyDescription,
.equal = CFEqual,
};
int main(int argc, const char * argv[])
{
#autoreleasepool {
CFMutableDictionaryRef cfdictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kDictionaryValueCallBacksAllowingNULL);
CFDictionarySetValue(cfdictionary, #"foo", #"bar");
CFDictionarySetValue(cfdictionary, #"boz", nil);
NSMutableDictionary *dictionary = CFBridgingRelease(cfdictionary);
NSLog(#"dictionary[foo] = %#", dictionary[#"foo"]);
NSLog(#"dictionary[foo] = %#", dictionary[[#"fo" stringByAppendingString:#"o"]]);
NSLog(#"dictionary[boz] = %#", dictionary[#"boz"]);
NSLog(#"dictionary = %#", dictionary);
NSLog(#"(dictionary isEqualTo: dictionary) = %d", [dictionary isEqualToDictionary:dictionary]);
}
return 0;
}
outputs:
dictionary[foo] = bar
dictionary[foo] = bar
dictionary[boz] = (null)
dictionary = {
boz = (null);
foo = bar;
}
(dictionary isEqualTo: dictionary) = 1
I needed to set a NSDictionary value to one that may or may not be set yet from NSUserDefaults.
What I did was wrap the values in a stringwithFormat call. Both values are not yet set so start as null. When I run without the stringwithFormat call the app crashes. So I did this and in my situation worked.
-(NSDictionary*)userDetailsDict{
NSDictionary* userDetails = #{
#"userLine":[NSString stringWithFormat:#"%#",[[NSUserDefaults standardUserDefaults]stringForKey:kSelectedLine] ],
#"userDepot":[NSString stringWithFormat:#"%#",[[NSUserDefaults standardUserDefaults]stringForKey:#"kSelected Duty Book"]]
};
return userDetails;
}
I want to get the type of NSNumber instance.
I found out on http://www.cocoadev.com/index.pl?NSNumber this:
NSNumber *myNum = [[NSNumber alloc] initWithBool:TRUE];
if ([[myNum className] isEqualToString:#"NSCFNumber"]) {
// process NSNumber as integer
} else if ([[myNum className] isEqualToString:#"NSCFBoolean"]) {
// process NSNumber as boolean
}
Ok, but this doesn't work, the [myNum className] isn't recognized by the compiler.
I'm compiling for iPhone.
I recommend using the -[NSNumber objCType] method.
It allows you to do:
NSNumber * n = [NSNumber numberWithBool:YES];
if (strcmp([n objCType], #encode(BOOL)) == 0) {
NSLog(#"this is a bool");
} else if (strcmp([n objCType], #encode(int)) == 0) {
NSLog(#"this is an int");
}
For more information on type encodings, check out the Objective-C Runtime Reference.
You can get the type this way, no string comparisons needed:
CFNumberType numberType = CFNumberGetType((CFNumberRef)someNSNumber);
numberType will then be one of:
enum CFNumberType {
kCFNumberSInt8Type = 1,
kCFNumberSInt16Type = 2,
kCFNumberSInt32Type = 3,
kCFNumberSInt64Type = 4,
kCFNumberFloat32Type = 5,
kCFNumberFloat64Type = 6,
kCFNumberCharType = 7,
kCFNumberShortType = 8,
kCFNumberIntType = 9,
kCFNumberLongType = 10,
kCFNumberLongLongType = 11,
kCFNumberFloatType = 12,
kCFNumberDoubleType = 13,
kCFNumberCFIndexType = 14,
kCFNumberNSIntegerType = 15,
kCFNumberCGFloatType = 16,
kCFNumberMaxType = 16
};
typedef enum CFNumberType CFNumberType;
If all you want is to differentiate between booleans and anything else, you can make use of the fact that boolean NSNumbers always return a shared instance:
NSNumber *num = ...;
if (num == (void*)kCFBooleanFalse || num == (void*)kCFBooleanTrue) {
// num is boolean
} else {
// num is not boolean
}
NSNumber explicitly doesn't guarantee that the returned type will match the method used to create it, so doing this at all is probably a bad idea.
However, you could probably do something like this (you could also compare to objc_getClass("NSCFNumber") etc., but this is arguably more portable):
Class boolClass = [[NSNumber numberWithBool:YES] class];
/* ... */
if([myNum isKindOfClass:boolClass]) {
/* ... */
}
In Swift:
let numberType = CFNumberGetType(answer)
switch numberType {
case .charType:
//Bool
case .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, .shortType, .intType, .longType, .longLongType, .cfIndexType, .nsIntegerType:
//Int
case .float32Type, .float64Type, .floatType, .doubleType, .cgFloatType:
//Double
}
Use the method -[NSNumber objCType] method to get the type.
If the type's equal to #encode(BOOL), or the number itself is kCFBooleanFalse, or kCFBooleanTrue, it's a boolean.
If it's anything else but 'c', it's a number.
If it's 'c', what appears to be the only way supported way, without checking against private class names, or comparing against undocumented singletons, is to turn make an array of one element, the number, and then use NSJSONSerialization to get the string representation. Finally, check if the string representation contains the string "true" or "false". Here is the full code for checking if an NSNumber is a BOOL:
-(BOOL)isBool
{
if(!strcmp(self.objCType, #encode(BOOL)) ||
self == (void*)kCFBooleanFalse ||
self == (void*)kCFBooleanTrue)
{
return YES;
}
if(strcmp(self.objCType, "c"))
{
return NO;
}
NSString * asString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:#[self] options:kNilOptions error:nil] encoding:NSUTF8StringEncoding];
return [asString containsString:#"true"] || [asString containsString:#"false"];
}
Note that using NSJSONSerialization is slow and if #NO/#YES ever stops always equalling kCFBooleanFalse/kCFBooleanTrue, then this method probably shouldn't be used in a tight loop.
The reason the compiler warns you and it doesn't work is because -[NSObject className] is declared in a category on NSObject on Mac OS X (in NSScriptClassDescription.h) and not declared on iPhone. (It doesn't support AppleScript, obviously.) NSStringFromClass([myNum class]) is what you should use to be safe across all platforms. Odds are that -className is declared as a simple wrapper around NSStringFromClass() anyway...
NSString *classString = NSStringFromClass([myNum class]);
That should ger the string you want.
To check that NSNumber contains a bool value Try this:
if (strcmp([myNumber objCType], [#(YES) objCType]) == 0)
NSLog(#"%#", [myNumber boolValue] ? #"true" : #"false");
objCType documentation states that The returned type does not necessarily match the method the number object was created with
Secondly, other methods of comparing the class of number to a given class type or assuming boolean number instances to be shared singletons are not documented behaviour.
A more(not completely though) reliable way is to depend on NSJSONSerialisation as it correctly recognises number instances created with bool and outputs true/false in json. This is something we can expect Apple to take care of while moving with new SDKs and on different architectures. Below is the code:
+(BOOL) isBoolType:(NSNumber*) number {
NSError* err;
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:#{#"key":number}
options:0
error:&err];
NSString* jsonString = [[NSString alloc]
initWithData:jsonData
encoding:NSUTF8StringEncoding];
return [jsonString containsString:#"true"]
|| [jsonString containsString:#"false"];
}
Swift Version
NSNumber is a class-cluster so each underlying type can be figured from the instance. This code avoids hard-coding the different NSNumber types by creating an instance of the expected type, and then comparing it against the unknown type.
extension NSNumber {
var isBool: Bool {
return type(of: self) == type(of: NSNumber(booleanLiteral: true))
}
}
check object is of NSNumber type :
if([obj isKindOfClass:NSClassFromString(#"__NSCFNumber")])
{
//NSNumber
}