objective-c determine if parameter is an object - objective-c

in Objective-c I have this function prototype:
-(NSString*)formatSQL:(NSString*) sql, ...
I may pass to this function any type of parameters: NSString, NSNumber, integer, float
How can I determine in the function if a parameter is an object (NSString..) or a primitive (integer...)?
thanks
BrochPirate

If you're going to have a parameter that accepts multiple types, you can only safely do it by using Obj-C objects, which means using id as the type. You can't safely inter-mingle id with float, integer etc.
If you wrapped up all floats and ints in NSNumbers, you could have a method like so:
- (NSString *)formatSQL:(id)obj
{
if ([obj isKindOfClass:[NSString class]]) {
// Format as a string
}
else if ([obj isKindOfClass:[NSNumber class]]) {
// Further processing will be required to differentiate between ints and floats
}
}
There are a few caveats to using isKindOfClass:, but this should serve as a good starting point.

Related

How to pass single string to a method expects array of string

I'm new to objective C. Usually in C# we can use params[] string as argument type when we want to send single or multiple strings to a method. Which we can either pass a single string or collection of strings without any data type issue. Do we have anything similar in objective C
All Objective-C reference type is a subclass of NSObject. This allow us some level of dynamic typing.
Below two versions are basically the same.
Old, generic version:
- (void)funcWithString:(id)param {
NSLog(#"funcWithString %#", param);
}
- (void)funcWithStringArray:(NSArray*)paramArr {
NSLog(#"funcWithStringArray %#", paramArr);
for (id str in paramArr) {
[self funcWithString:str];
}
}
More modern, static version:
- (void)funcWithString:(NSString*)param {
NSLog(#"funcWithString %#", param);
}
- (void)funcWithStringArray:(NSArray<NSString*>*)paramArr {
NSLog(#"funcWithStringArray %#", paramArr);
for (NSString* str in paramArr) {
[self funcWithString:str];
}
}
C# Object ~> Obj-C id
C# [] ~> Obj-C NSArray
Used like below (all 3 are equivalent):
[self funcWithString: #"single string"];
[self funcWithStringArray: #[#"single string"]];
[self funcWithStringArray: [NSArray arrayWithObject: #"single string"]];
In objective-c , if you are want to pass the single object where array of object is required, then you just have to declare the array with single object.
You cannot pass the single object where array is considered
In Objective-c Array is declared as simple
NSArray *array = [NSArray arrayWithObject:#"String1"];
Pass this array to function

Why does passing an unsigned int to performSelector lose bits?

I am trying to pass a hex value as an unsigned int to a method using a dynamic link. The value I pass as a parameter is getting corrupted somehow. What is happening?
- (void)callPerformSelector
{
NSNumber *argument = [NSNumber numberWithUnsignedInt:(unsigned int)0xFFFFFFFF];
SEL selector = NSSelectorFromString(#"testPerformSelector:");
NSLog(#"testPerformSelector object %#", argument);
[self performSelector:selector withObject:argument];
}
- (void)testPerformSelector:(unsigned int) arg1
{
NSLog(#"testPerformSelector unsigned int %u", arg1);
NSLog(#"testPerformSelector hex %X", arg1);
}
Output is:
testPerformSelector object 4294967295
testPerformSelector unsigned int 4294967283
testPerformSelector hex FFFFFFF3
Because it should be:
- (void)callPerformSelector
{
NSNumber *argument = #0xFFFFFFFF;
SEL selector = #selector(testPerformSelector:);
NSLog(#"testPerformSelector object %#", argument);
[self performSelector:selector withObject:argument];
}
- (void)testPerformSelector:(NSNumber *) arg1
{
NSLog(#"testPerformSelector unsigned int %u", arg1.unsignedIntValue);
}
unsigned int and NSNumber * is two different things
There's an easy reason and a complex reason.
Simple reason: Why this doesn't work. The first argument to the target of a performSelectorWithObject must be an object. You are specifying a pointer to an unsigned integer in your function signature but then passing an object (NSNumber) when you call it. So instead of:
- (void)testPerformSelector:(unsigned int) arg1
you should have
- (void)testPerformSelector:(NSNumber *) arg1
You will then need to use NSNumber's unsignedIntValue to get the 0xFFFFFFFF out of the object.
The complex reason is much more interesting: Why this nearly works and looks like it loses a few bits. NSNumber is an object that wraps a numeric value this is very different from a raw numeric value. However NSNumber is implemented as a tagged pointer so although objective-c knows it is an object and treats it like an object, a subset of NSNumber values are implemented as tagged pointers where real values are coded into the "pointer" and the fact that this is not a normal pointer is indicated in the (otherwise always zero) bottom four bits of a pointer see Friday Q&A.
You can only pass objects to selectors and not primitive types, and therefore the selector should be:
- (void)testPerformSelector:(NSNumber *) arg1
{
NSLog(#"testPerformSelector hex %x", [arg1 unsignedIntValue]);
}
Update: As pointed-out by #gnasher729, the reason the number passed appears to be -13 is because it's a tagged pointer.

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.

How can I distinguish a boolean NsNumber from a real number?

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 {
// ...
}

Objective-C: How to check if a variable is an object, a struct or another primitive

I want to write a function or a directive like NSLog() that takes any kind of variable, primitives and objects. In that function I want to distinguish those.
I know how it works for objects:
- (void)test:(id)object {
if ([object isKindOfClass:[NSString class]])
...
but how do I distinguish objects from structs or even integer or floats.
Something like:
"isKindOfStruct:CGRect" or "isInt"
for example?
Is this possible?
I thought since you can send everything to NSLog(#"...", objects, ints, structs) it must be possible?
Thanks for any help!
EDIT
My ultimate goal is to implement some kind of polymorphism.
I want to be able to call my function:
MY_FUNCTION(int)
MY_FUNCTION(CGRect)
MY_FUNCTION(NSString *)
...
or [self MYFUNCTION:int]...
and in MY_FUNCTION
-(void)MYFUNCTION:(???)value {
if ([value isKindOf:int])
...
else if ([value isKindOf:CGRect])
...
else if ([value isKindOfClass:[NSString class]])
...
}
I know that isKindOf doesn't exists and you can't even perform such methods on primitives. I'm also not sure about the "???" generic type of "value" in the function header.
Is that possible?
#define IS_OBJECT(T) _Generic( (T), id: YES, default: NO)
NSRect a = (NSRect){1,2,3,4};
NSString* b = #"whatAmI?";
NSInteger c = 9;
NSLog(#"%#", IS_OBJECT(a)?#"YES":#"NO"); // -> NO
NSLog(#"%#", IS_OBJECT(b)?#"YES":#"NO"); // -> YES
NSLog(#"%#", IS_OBJECT(c)?#"YES":#"NO"); // -> NO
Also, check out Vincent Gable's The Most Useful Objective-C Code I’ve Ever Written for some very handy stuff that uses the #encode() compiler directive (that) returns a string describing any type it’s given..."
LOG_EXPR(x) is a macro that prints out x, no matter what type x is, without having to worry about format-strings (and related crashes from eg. printing a C-string the same way as an NSString). It works on Mac OS X and iOS.
A function like NSLog() can tell what types to expect in its parameter list from the format string that you pass as the first parameter. So you don't query the parameter to figure out it's type -- you figure out what type you expect based on the format string, and then you interpret the parameter accordingly.
You can't pass a C struct or primitive as a parameter of type id. To do so, you'll have to wrap the primitive in an NSNumber or NSValue object.
e.g.
[self test: [NSNumber numberWithInt: 3.0]];
id is defined as a pointer to an Objective-C object.
#alex gray answer did not work(or at least did not work on iOS SDK 8.0). You can use #deepax11 answer, however I want to point how this 'magic macro' works. It relies on type encodings provided from the system. As per the Apple documentation:
To assist the runtime system, the compiler encodes the return and argument types for each method in a character string and associates the string with the method selector. The coding scheme it uses is also useful in other contexts and so is made publicly available with the #encode() compiler directive. When given a type specification, #encode() returns a string encoding that type. The type can be a basic type such as an int, a pointer, a tagged structure or union, or a class name—any type, in fact, that can be used as an argument to the C sizeof() operator.
To break the macro apart, we first get "typeOf" our variable, then call #encode() on that type, and finally compare returned value to 'object' and 'class' types from encoding table.
Full example should look like:
const char* myType = #encode(typeof(myVar));//myVar declared somewhere
if( [#"#" isEqualToString:#(myType)] || [#"#" isEqualToString:#(myType)] )
{
//myVar is object(id) or a Class
}
else if ( NSNotFound != [[NSString stringWithFormat:#"%s", myType] rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:#"{}"]].location )
{
//myVar is struct
}
else if ( [#"i" isEqualToString:#(myType)] )
{
//my var is int
}
Please note that NSInteger will return int on 32-bit devices, and long on 64-bit devices. Full list of encodings:
‘c’ - char
‘i’ - int
’s’ - short
‘l’ - long
‘q’ - long long
‘C’ - unsigned char
‘I’ - unsigned int
’S’ - unsigned short
‘L’ - unsigned long
‘Q’ - unsigned long long
‘f’ - float
‘d’ - double
‘B’ - C++ bool or a C99 _Bool
‘v’ - void
‘*’ - character string(char *)
‘#’ - object(whether statically typed or typed id)
‘#’ - class object(Class)
‘:’ - method selector(SEL)
‘[<some-type>]’ - array
‘{<some-name>=<type1><type2>}’ - struct
‘bnum’ - bit field of <num> bits
‘^type’ - pointer to <type>
‘?’ - unknown type(may be used for function pointers)
Read more about Type Encodings at Apple
#define IS_OBJECT(x) ( strchr("##", #encode(typeof(x))[0]) != NULL )
This micro works which I got somewhere in stack overflow.
It's important to note that id represents any Objective-C object. And by Objective-C object, I mean one that is defined using #interface. It does not represent a struct or primitive type (int, char etc).
Also, you can only send messages (the [...] syntax) to Objective-C objects, so you cannot send the isKindOf: message to a normal struct or primitive.
But you can convert a integer etc to a NSNumber, a char* to a NSString and wrap a structure inside a NSObject-dervied class. Then they will be Objective-C objects.