How can I distinguish a boolean NsNumber from a real number? - objective-c

I'm getting a value back from an Objective C library (it's Firebase, but that doesn't really matter) of type id. The documentation states that this value will be an NSNumber for both boolean and numeric results. I want to take a different action based on whether or not this result corresponds to a boolean.
I know this is possible because printing out the class of the result via NSStringFromClass([value class]); gives "__NSCFBoolean" for booleans, but I'm not really sure how to correctly structure the comparison.

The objCType method gives information about the type of the data contained in the
number object:
NSNumber *n = #(1.3);
NSLog(#"%s", [n objCType]); // "d" for double
NSNumber *b = #YES;
NSLog(#"%s", [b objCType]); // "c" for char
The possible values are documented in
"Type Encodings"
in the "Objective-C Runtime Programming Guide".
Since BOOL is defined as unsigned char, it is reported as such by this method.
This means that you cannot distinguish it from a NSNumber object containing any char.
But it is sufficient to check between "boolean" and "numeric":
if (strcmp([obj objCType], #encode(BOOL)) == 0) {
// ...
} else if (strcmp([obj objCType], #encode(double)) == 0) {
// ...
} else {
// ...
}

Related

Convert Swift convenience init with Switch statement to Objective-C

I am trying to convert this swift code to Objective-C
convenience init(fromString string: String, format:DateFormat)
{
if string.isEmpty {
self.init()
return
}
let string = string as NSString
switch format {
case .DotNet:
let startIndex = string.rangeOfString("(").location + 1
let endIndex = string.rangeOfString(")").location
let range = NSRange(location: startIndex, length: endIndex-startIndex)
let milliseconds = (string.substringWithRange(range) as NSString).longLongValue
let interval = NSTimeInterval(milliseconds / 1000)
self.init(timeIntervalSince1970: interval)
So far, I am doing this:
-(id) initFromString: (NSString *) string format: (DateFormat *) format{
if (string == nil) {
self = [self init];
return self;
}
switch (format) {
case .DotNet:
NSRange *startIndex = [[string rangeOfString:#"("] location]+1;
}
}
and have already run into the following errors:
for the switch(format): statement requires expression of integer type (DateFormat * __strong' invalid)
and for the 2 following lines: Expected expression
Any ideas ?
In Objective-C, the string is impliedly optional. Testing for nil merely tests whether a string was supplied. It doesn't check whether an empty string was supplied. You probably want to switch to string.length == 0 as, by the magic of nil-messaging, that'll work to check for either an empty string or no string at all.
Objective-C uses C's switch statement. So you can switch on integral types only. If this were Objective-C code originally, DateFormat would probably be an NS_ENUM — an integral type rather than an object type. It looks like the original was an enumeration from your use of dot syntax? If you can make it an Objective-C enumeration then do so and simply use:
- (id)initFromString:(NSString *)string format:(DateFormat)format {
....
switch(format)
{
case DateFormatDotNet: {
....
} break;
}
(with the curly brackets within the case being because you want to declare variables in there).
Otherwise, if it must be an object format then you're looking at a painful construction like:
if([format isEqual:[DateFormat dotNetFormat]]) {
}
else if([format isEqual:[DateFormat otherFormat]]) {
}
... etc ...
Also Objective-C has a syntactic distinction between structs, which are exactly what they are in C — named fields but no built-in behaviour — and object types, which is again because it's a strict superset of C. NSRange is a struct. So square bracket messaging syntax doesn't work on it. Instead of:
[[string rangeOfString:#"("] location]
Use:
[string rangeOfString:#"("].location
Square brackets around the rangeOfString call because it's a message dispatch to an object, then a dot for location because you get back a C struct as a value, and that's how one accesses a field in a C struct.
(dot syntax also works for properties on Objective-C objects, but explicitly to alias to getter and setter calls, and only for about the most recent of Objective-C's three decades)
Assuming this code is related to How to convert a Swift enum: String into an Objective-C enum: NSString?
Since your DateFormat variable is an object with a dateFormatType that is an NSString, you are going to have to use a chained if-else construct to select from the various possibilities:
if([format.dateFormatType compare: DotNetDateFormatType] == NSOrderedSame) {
[self handleDotNetDateFormat: format]
} else if ([format.dateFormatType compare: RSSDateFormatType] == NSOrderedSame) {
[self handleRSSDateFormat: format]
...
Objective-C has no concept of the dot-value syntax for enum values (so ".DotNet" is not a valid term in Objective-C). That's why the compiler is complaining about those either lines.

Logically ANDing NSUInteger and String Type?

I've searched Stackoverflow and other sites, but I can't seem to find this answer.
In Apple Text Editor source, they have at least one routine that does some apparently strange logical ANDing between two non-boolean variables. Casting them as Bools CAN be done, but doesn't make much sense. I'm learning Swift and much less familiar with Objective-C, but for the life of me, I can't figure out how they are trying to achieve the goal stated as "Build list of encodings, sorted, and including only those with human readable names."
Here is the code:
/* Return a sorted list of all available string encodings.
*/
+ (NSArray *)allAvailableStringEncodings {
static NSMutableArray *allEncodings = nil;
if (!allEncodings) { // Build list of encodings, sorted, and including only those with human readable names
const CFStringEncoding *cfEncodings = CFStringGetListOfAvailableEncodings();
CFStringEncoding *tmp;
NSInteger cnt, num = 0;
while (cfEncodings[num] != kCFStringEncodingInvalidId) num++; // Count
tmp = malloc(sizeof(CFStringEncoding) * num);
memcpy(tmp, cfEncodings, sizeof(CFStringEncoding) * num); // Copy the list
qsort(tmp, num, sizeof(CFStringEncoding), encodingCompare); // Sort it
allEncodings = [[NSMutableArray alloc] init]; // Now put it in an NSArray
for (cnt = 0; cnt < num; cnt++) {
NSStringEncoding nsEncoding = CFStringConvertEncodingToNSStringEncoding(tmp[cnt]);
if (nsEncoding && [NSString localizedNameOfStringEncoding:nsEncoding]) [allEncodings addObject:[NSNumber numberWithUnsignedInteger:nsEncoding]];
}
free(tmp);
}
return allEncodings;
}
The line in question contains the "&&." Any guidance would be appreciated.
Objective-C is a strict superset of C, so the same rules for logical
operators apply. In contrast to Swift, which is much more strict with
types, the logical operators in C take arbitrary scalar operands.
(The boolean type bool did not even exist in early versions of C,
it was added with the C99 standard.)
The C standard specifies (see e.g. http://port70.net/~nsz/c/c11/n1570.pdf, which is a draft of the C11 standard):
6.5.13 Logical AND operator
Constraints
2 Each of the operands shall have scalar type.
Semantics
3 The && operator shall yield 1 if both of its operands compare
unequal to 0; otherwise, it yields 0. The result has type int.
In your case, in
if (nsEncoding && [NSString localizedNameOfStringEncoding:nsEncoding])
the left operand has type NSUInteger (which can be unsigned long
or unsigned int, depending on the platform), and the right
operand has type NSString *, which is a pointer type. Therefore
the above expression is equivalent to
if (nsEncoding != 0 && [NSString localizedNameOfStringEncoding:nsEncoding] != 0)
where the zero in the right operand is the null pointer constant
which is usually written as NULL, or nil for Objective-C pointers:
if (nsEncoding != 0 && [NSString localizedNameOfStringEncoding:nsEncoding] != nil)
Some more information how this relates to Swift
Cocoa/Cocoa Touch Objective-C methods which return an object pointer
usually return nil to indicate an error
(compare Handling Error Objects Returned From Methods
in the "Error Handling Programming Guide"). So
[NSString localizedNameOfStringEncoding:nsEncoding] != nil
would mean "no localized name for the encoding could be determined".
The Swift equivalent would be a method returning an optional string,
and you could check the success with
NSString.localizedNameOfStringEncoding(nsEncoding) != nil
However, this does not compile, and here is the reason why: If you option-click on the Objective-C localizedNameOfStringEncoding method
in Xcode to show its declaration then you'll see
+ (NSString * _Nonnull)localizedNameOfStringEncoding:(NSStringEncoding)encoding
Here _Nonnull indicates that the method is not expected to return
nil. This kind of nullability annotations were introduced to
improve the mapping of Objective-C methods to Swift, see for example
"Nullability and Objective-C" in the Swift Blog.
Because of this _Nonnull annotation, the method is imported to Swift
as
public class func localizedNameOfStringEncoding(encoding: UInt) -> String
So testing the return value in Objective-C can be done but makes no
sense because the method always returns a non-nil value.
In Swift the compiler assumes that the return value is never nil
and returns a non-optional String.
The translation of that if-statement to Swift would therefore just be
if nsEncoding != 0 {
// ...
}

NSNumber Literals BOOL reverse cause objCType type change?

I test these codes below:
BOOL b1 = YES;
BOOL b2 = NO;
BOOL b3 = !b1;
NSNumber *num1 = #(b1);
NSNumber *num2 = #(b2);
NSNumber *num3 = #(b3);
NSNumber *num4 = #(!b1);
the objCType([num1 objCType]) type of num1, num2, num3 is "c", but the objcType of num4 is "i". Why I reverse the bool and the NSNumber's objcType is changed?
===========================
My problem is that, my origin code likes like this:
BOOL *m_bFirstEvent;
[pDict setValue:**#[[NSNumber numberWithBool:!m_bFristEvent]]** forKey:#"params"];
NSData *pPostData = [NSJSONSerialization dataWithJSONObject:pDict options:0 error:&pError];
the json string is
{"method":"getEvent", "id":7, "params":[false]}
after I use XCode's refactor->"convert to modern Objective-c syntax", the code changes to:
BOOL *m_bFirstEvent;
[pDict setValue:**#[#(!m_bFristEvent)]** forKey:#"params"];
NSData *pPostData = [NSJSONSerialization dataWithJSONObject:pDict options:0 error:&pError];
the json string is
{"method":"getEvent", "id":7, "params":[0]}
How can I avoid this NSNumber Literals mistake and safely use XCode's refactor->"convert to modern Objective-c syntax" for NSNumber Literals?
Most of the time, people don't care whether an NSNumber contains a BOOL value, or an integer with a value of 0 or 1. When you create JSON (and possibly when you process JSON), you care.
Only [NSNumber numberWithBool:xxx] or [[NSNumber alloc] initWithBool:xxx] creates an NSNumber that will be converted to true or false (and not 1 or 0) in JSON. The same if you use #YES, #NO, or #(xxx) where xxx has type BOOL, because all these eventually call initWithBool. So in your case, unfortunately Apple's conversion tool did the wrong thing.
There are actually only two NSNumber objects containing booleans ever in existence (obviously one for #YES and one for #NO). So if you process JSON, you can check for example
if (jsonResult == #YES)
NSLog (#"json data contained the value 'true'");
else if (jsonResult == #NO)
NSLog (#"json data contained the value 'false'");
else if ([jsonResult isKindOfClass:[NSNumber class]])
NSLog (#"json data contained some kind of number");
and this will distinguish between true and false, and 0, 1 or other values. Yes, the comparison with == is done on purpose and correct.
# ((BOOL) xxx)
will create an NSNumber with a bool value. For example
# ((BOOL) ! myBoolValue)
# ((BOOL) (x >= 0 && x <= 100)
and so on. It seems that sometimes BOOL is signed char, and sometimes it is bool. That makes a difference if you write
# ((BOOL) 256) // May be #YES or #NO, depending on target
# ((BOOL) 0.3) // May be #YES or #NO, depending on target
Likewise, casting to bool instead of BOOL may or may not create a numberWithBool. Both
# ((BOOL) (bool) 256)
# ((BOOL) (bool) 0.3)
will produce the value #YES, because casting to bool produces 1 if the value was non-zero and 0 if it was zero; this can also be used with C pointers or object pointers.
I believe is due to C's rule that all inputs and intermediate results of computations on smaller-than-int datatypes are integers. So, using ! (or other operators) on a BOOL type produces an int.
That's because if you look at BOOL definition then you will realize that the BOOL is a signed char. In fact you cannot negate signed char that means it it casted into int and then the negation goes.
EDIT
The bool definition is now slighty different: it is casted directly to bool for 64bit devices. From objc.h file:
#if !defined(OBJC_HIDE_64) && TARGET_OS_IPHONE && __LP64__
typedef bool BOOL;
#else
typedef signed char BOOL;
// BOOL is explicitly signed so #encode(BOOL) == "c" rather than "C"
// even if -funsigned-char is used.
#endif

get type of NSNumber

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
}

How to check a character array is null in objective C

How to check a character array is null in objective C?
char hName[255];
- (void)setHost {
phent = gethostbyaddr((const char*)&haddr, sizeof(int), AF_INET);
if(phent){
strncpy(hName,phent->h_name,255);
}
-(void) getHost {
NSString *str = [[NSString alloc] initWithBytes:tTemp.hName
length:sizeof(tTemp.hName) encoding:NSASCIIStringEncoding];
}
I have a character array named hName[255]. I will assign values to this array at some point of my project. Now i need to check if the hName[255] contains null value. i tried some methods. I get a string str from that array and check if it is equal to #""; It failed. Then i check the length of the string str. Even if the array contains no values it will return 255. How can i check the array contains null value. Any help? Thanks in advance!!
You might need to elaborate a bit on your question. For example, to just check if a pointer is null is pretty simple :
char *my_chars;
...
if (! my_chars)
NSLog(#"Woo, my_chars is null");
else
NSLog(#"my_chars is at 0x%08x", my_chars);
because null is just 0 :)
However, it doesn't look like that's your problem. you've created an array of characters like so
char my_chars[255];
so my_chars is not going to be null.
However, as outis says in his answer, you've just allocated it and not zeroed the contents so you have no idea what's in those 255 bytes! Out of the three options he suggests I'd personally go with this one :
char my_chars[255];
memset(my_chars, 0, sizeof(my_chars));
now, you have an array of 255 zeroes :) This is pretty easy to check to see if it's null :
if (0 == strlen(my_chars))
NSLog(#"It's not null but it is an empty string!");
else
NSLog(#"my_chars contains a valid string which is %i chars long", strlen(my_chars));
Hope that helps.
Sam
First thing, note that the word "null" is overloaded. You can have null pointers and null (empty) strings, and there's the null character ('\0', equal to 0 when converted to an int:((int)'\0') == 0). There are also uninitialized variables, which may or may not be null. I'm guessing you're talking about an uninitialized character array, used as a c-string.
Most likely, hName is being allocated on the stack (I can't tell without seeing more of the source code), which means it's not zero-initialized. Practically speaking, hName will hold whatever data was last stored in the region of memory that hName occupies. You'll need to initialize it yourself.
char hName[255] = {0};
// or
memset(hName, 0, sizeof(hName));
// or, if you have bzero
bzero(hName, sizeof(hName));
Also note that since hName is declared as an array rather than a pointer, sizeof(hName) is the number of characters it stores.
void test() {
char *name1 = "";
char name2[255];
// All the following lines will be true
strlen(name1) == 0;
sizeof(name2) == 255
0 <= strlen(name2) && strlen(name2) < 255;
// pointers are 4 or 8 bytes on most machines these days
sizeof(name1) == 4 || sizeof(name1) == 8;
}
Edit (addressing code sample):
The length of str in getHost is 255 because you tell it to have that length when you copy from tTemp.hName. NSStrings can contain nulls, though you may have difficulty printing them and any characters following.
It's not clear from the code sample if hName is a global (globals are bad) or a property. Similarly, the scope of the other variables, such as haddr and tTemp, is unclear. Some of those should be parameters to the methods.
The name "setHost" should be reserved for a setter–one of a pair of methods ("accessors", in Objective-C parlance) that get and set a property. They return and take (respectively) a type that's notionally the type of the property. In this case, NSString* makes the most sense; best to use an NSString in your code and switch to (via NSString's cStringUsingEncoding: or UTF8String). The partner to -(void)setHost:(NSString*) would be -(NSString*)host.
Once you make the switch to NSString (and use stringFromCString:withEncoding:), you can simply examine its length or compare it to #"" to check for an empty string.
#interface MyHost : NSObject {
NSString *name_;
...
}
/* post ObjC 2.0 */
#property(retain) NSString* name;
/* pre ObjC 2.0 */
-(NSString*)name;
-(void)setName:(NSString*);
/* any ObjC version */
-(int)setHostFromAddress:(MyAddress*)addr;
...
#end
#implementation MyHost
/* post ObjC 2.0 */
#synthesize name = name_;
-(int)setHostFromAddress:(MyAddress*)addr {
struct hostent *phost;
phost = gethostbyaddr(addr.address, addr.length, addr.type);
if (phost) {
self.name = [NSString stringWithCString:phost->h_hname encoding:NSASCIIStringEncoding];
}
return h_errno;
}
/* pre ObjC 2.0 */
-(NSString*)name {
return name_;
}
-(NSString*)setName:(NSString*)nom {
[name_ release];
name_ = [nom retain];
}
-(int)setHostFromAddress:(MyAddress*)addr {
struct hostent *phost;
phost = gethostbyaddr([addr address], [addr length], [addr type]);
if (phost) {
[self setName:[NSString stringWithCString:phost->h_hname encoding:NSASCIIStringEncoding]];
}
return h_errno;
}
...
#end