Printing a struct using description - objective-c

I would like to know if it is possible to use the description function in the Cocoa framework to log the contents of a struct. For example:
typedef struct {float a,b,c;}list;
list testlist = {1.0,2.5,3.9};
NSLog(#"%#",testlist); //--> 1.0,2.5,3.9

No. The description message is a method found in the NSObject protocol, so by definition, must be an object. There is, however, a more convenient way of log debugging, using a LOG_EXPR() macro. This will take objects and structs:
LOG_EXPR(testlist);
Which would output:
testlist = {1.0, 2.5, 3.9};
This code can be found here.

description is a method and as such can only be called on an object. In turn, the %# format specifier only works for objects which respond to description.
You can write your own function to make a pretty NSString with the contents of your struct:
NSString * pretty_string_from_list( list l ){
return [NSString stringWithFormat:#"<list: [%f, %f, %f]>", l.a, l.b, l.c];
}
Then call that function when you log the struct:
NSLog(#"%#", pretty_string_from_list(testlist));

Related

how to transfer objective-c language (`va_list`,`va_start `,`va_end ` ) to swift language?

This is my codes. they are objective-c language:
- (void)objcMethod:(NSString *)format, ...
{
va_list args;
va_start(args, format);
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
NSLog(#"%#", msg);
va_end(args);
va_start(args, format);
va_end(args);
}
how to transfer objective-c language (va_list,va_start,va_end ) to swift language?
I also need to call this swift method in objective-c xxx.m file.
Need help. thanks!
======================================================================
update:
I tried MartinR's answer NSLog is unavailable , but something wrong, I can not add #objc in front of the method, need help.thanks.
my codes...
From #MartinR comment and reference to NSLog is unavailable you know that Swift can call (Objective-)C functions & methods that take va_list arguments through the use of the Swift types CVarArg and CVaListPointer.
Many of the common (Objective-)C variadic functions & methods have a sibling which takes a va_list, so this support in Swift provides access to them.
I also need to call this swift method in objective-c xxx.m file.
However you wish to go the other way, and having written a Swift variadic function version of your Objective-C method you found you couldn't call it. You attempted to ask what the solution was, How do you call a Swift variadic method from Objective-C?, the indirect answer (your question was marked as duplicate) to that question provides a hint – to use an array – but doesn't handle the generality you require for your formatted-print type scenario. Let's see if we can get there...
(Using Xcode 10/Swift 4.2, any other version of Swift is probably different.)
We'll use the following Swift class as basis:
class SwiftLog : NSObject
{
// Swift entry point
static func Log(_ format : String, args : CVarArg...)
{
withVaList(args) { LogV(format, $0)}
}
// Shared core
private static func LogV(_ format : String, _ args: CVaListPointer)
{
NSLogv(format, args)
}
}
This provides Swift with a variadic function which will take all the Swift library types you are probably interested in, and a few more you not (3287 are listed in Apple's CVarArg documentation). The private core function here is trivial, you probably wish to do something a little more involved.
Now you wish to call Log() from Objective-C but, as you've discovered, you cannot due to the CVarArg. However Objective-C can call Swift functions which take NSObject arguments, and NSObject implements CVarArg, which gets us to our first attempt:
// Objective-C entry point
#objc static func Log(_ format : String, args : [NSObject])
{
withVaList(args) { LogV(format, $0) }
}
This works as-is but every argument must be an object and formatted with %#, switching to Objective-C:
[SwiftLog LogObjects:#"%#|%#|%#|%#|%#|%#" args:#[#"42", #4.2, #"hello", #31, #'c', NSDate.new]];
produces:
42|4.2|hello|31|99|Sun Nov 11 08:47:35 2018
It works within limits, we have lost the flexibility of formatting – no %6.2f, %x etc. – and the character has come out as 99.
Can we improve it? If you are prepared to sacrifice the ability to print NSNumber values as is, then yes. Over in Swift change the Log() function to:
#objc static func Log(_ format : String, args : [NSObject])
{
withVaList(args.map(toPrintfArg)) { LogV(format, $0) }
}
Skipping toPrintfArg for the moment (its just large and ugly) over in Objective-C we can call this version as:
[SwiftLog Log:#"%#|%4.2f|%10s|%x|%c|%#" args:#[#"42", #4.2, #((intptr_t)"hello"), #31, #'c', NSDate.new]];
which produces:
42|4.20| hello|1f|c|Sun Nov 11 08:47:35 2018
Much better, and the character is correct. So what does toPrintfArg do?
In the above we had to pass an array of objects to Swift, and to do that all the primitive values are wrapped as NSNumber objects.
In Objective-C an NSNumber object does not reveal much about what it wraps, the access methods (.doubleValue, .integerValue etc.) will convert whatever the wrapped value was into a value of the requested type and return it.
However NSNumber is "toll-free bridged" to the the Core Foundation types CFBoolean and CFNumber; the former of these is for booleans (obviously!) and the latter for all the other numeric types and, unlike NSNumber, provides a function that returns the type of the wrapped value so it can be unwrapped without conversion. Using this information we can extract the original (experts, yes, see below) values from the NSNumber objects, all those extracted value in Swift will all implement CVarArg, here goes:
private static func toPrintfArg(_ item : NSObject) -> CVarArg
{
if let anumber = item as? NSNumber
{
if type(of:anumber) == CFBoolean.self { return anumber.boolValue }
switch CFNumberGetType(anumber)
{
case CFNumberType.sInt8Type: return anumber.int8Value
case CFNumberType.sInt16Type: return anumber.int16Value
case CFNumberType.sInt32Type: return anumber.int32Value
case CFNumberType.sInt64Type: return anumber.int64Value
case CFNumberType.float32Type: return Float32(anumber.floatValue)
case CFNumberType.float64Type: return Float64(anumber.doubleValue)
case CFNumberType.charType: return CChar(anumber.int8Value)
case CFNumberType.shortType: return CShort(anumber.int16Value)
case CFNumberType.intType: return CInt(anumber.int32Value)
case CFNumberType.longType: return CLong(anumber.int64Value)
case CFNumberType.longLongType: return CLongLong(anumber.int64Value)
case CFNumberType.floatType: return anumber.floatValue
case CFNumberType.doubleType: return anumber.doubleValue
case CFNumberType.cfIndexType: return CFIndex(anumber.int64Value)
case CFNumberType.nsIntegerType: return NSInteger(anumber.int64Value)
case CFNumberType.cgFloatType: return CGFloat(anumber.doubleValue)
}
}
return item;
}
This function will unwrap (experts, yes, most, see below) NSNumber objects to the original value type while leaving all other objects as is to be formatted by %# (as shown by the NSString and NSDate objects in the example).
Hope that helps, at least more than it confuses! Notes for the curious/experts follow.
Notes & Caveats
Preserving C Pointers
In the above example the C string "hello" was passed by converting it to intptr_t, a C integer type the same size as a pointer, rather than as a pointer value. In this context this is fine, a va_list is essentially an untyped bob of bytes and the format tells NSLogv() what type to interpret the next bytes as, converting to intptr_t keeps the same bytes/bits and allows the pointer to be wrapped as an NSNumber.
However if you application needs to have an actual pointer on the Swift side you can instead wrap the C string as an NSValue:
[NSValue valueWithPointer:"hello"]
and unwrap it in toPrintfArg by adding:
if let ptr = (item as? NSValue)?.pointerValue
{
return ptr.bindMemory(to: Int8.self, capacity: 1)
}
This produces a value of type UnsafeMutablePointer<Int8>, which implements CVarArg (and as the latter the capacity is irrelevant).
Do You Always Get The Same Type Back?
If you wrap a C type as an NSNumber and then unwrap it as above, could the type change due to argument promotion (which means that integer types smaller than int get passed as int values, float values as double) in C? The answer is maybe but the required type for the CVarArg value is the promoted type so it should not make any difference in this context – the type of the unwrapped value with suit the expected format specifier.
What about NSDecimalNumber?
Well spotted, if you try to print an NSDecimalNumber, which is a subclass of NSNumber, the above toPrintfArg will unpack it as a double and you must use a floating-point format and not %#. Handling this is left as an exercise.

Corrupted string when returning new string from objective-c to swift

I try to build a string from an enum in Objective-C:
+ (NSString *)getStringFromKey:(ITGenericNotification)key {
return [NSString stringWithFormat:#"notification%lu", (long)key];
}
This code worked really well for over a year
However when I try to call it from swift with:
let notificationKey = NSNotification.getStringFromKey(ITNotificationWithObject.DidChangedQuestion.hashValue)
I get a corrupted string :
What is happening ?
The value of ITGenericNotification seems to be correct,
ITGenericNotification is defined as typedef long ITGenericNotification; and is used as base type for an enum with typedef NS_ENUM(ITGenericNotification, ITNotificationWithObject)
And + (NSString *)getStringFromKey:(ITGenericNotification)key is implemented as a category to NSNotification
You probably want .rawValue, not .hashValue, to get the underlying
integer value of the enum.
(Also the debugger variables view is sometimes wrong. When in doubt,
add a println() or NSLog() to your code to verify the values of
your variables.)

Printable type string from parameter

Using clang and objective-c I'm wondering if I can get a printable string describing the type of a potentially null parameter (i.e. compile time only type information.)
For example something like:
- (void)myFunc:(NSString *)aNilString {
NSLog(#"%s", __typeof__(aNilString));
}
Obviously this doesn't work because __typeof__ gets me the actual type, not a string. In C++ we have typeid which returns a std::type_info, but that name is mangled, e.g. "P12NSString*" and not "NSString*".
Ideally I'd like something that can be passed into functions like objc_getClass(). Is there a way to get what I want?
Edit: I'd like not to have to compile as C++, so this solution is out:
abi::__cxa_demangle(typeid(*aNilString).name(), 0, 0, 0));
To get a string that can be passed into functions like objc_getClass() just use the description of the class object.
In this example, I changed the type of your argument from NSString * to id since if you already knew that the argument was an NSString * then this question would be kind of moot. Note that I call the class method of the object being passed in and then the description method of the class in order to get an NSString * which describes the class and can be used with the methods that you reference.
Note also that if the object is nil then there is no class and calling description will simply return nil and the NSLog will print (null). You can't determine the type of pointer at runtime because it is simply a pointer to a class object (and that doesn't exist for nil).
- (void)myFunc:(id)aClassObject {
// Get the description of aClassObject's class:
NSString *classString = [[aClassObject class] description];
NSLog(#"%#", classString); // Prints __NSCFConstantString
// Bonus: Get a new object of the same class
if ([classString length] > 0) {
id theClass = objc_getClass([classString UTF8String]);
id aNewObjectOfTheSameType = [[theClass alloc] init];
NSLog(#"%#", [[aNewObjectOfTheSameType class] description]); // Prints __NSCFConstantString
}
}

Is there a way to reflectively call a function in Objective-C from a string?

Are there any Objective-C runtime functions that will allow me to get a function (or block) pointer from a string identifying the function? I need some way to dynamically find and invoke a function or static method based on some form of string identifying it.
Ideally this function should be able to exist in any dynamically loaded library.
Looking at Objective-C Runtime Reference, the best bet looks like class_getClassMethod, but there don't appear to be any function-related functions in this reference. Are there other raw C ways of getting a pointer to a function by name?
if you want to invoke some static objc method, you can make it as a class method of a class
#interface MyClas : NSObject
+ (int)doWork;
#end
and call the method by
[[MyClass class] performSelector:NSSelectorFromString(#"doWork")];
if you real want to work with C-style function pointer, you can check dlsym()
dlsym() returns the address of the code or data location specified by
the null-terminated character
string symbol. Which libraries and bundles are searched depends on the handle
parameter If dlsym() is called with the special handle RTLD_DEFAULT,
then all mach-o images in the process
(except those loaded with dlopen(xxx, RTLD_LOCAL)) are searched in the order they were loaded. This
can be a costly search and should be avoided.
so you can use it to find the function pointer base on asymbol name
not sure why you want to do this, sometimes use function table can do
typedef struct {
char *name,
void *fptr // function pointer
} FuncEntry;
FuncEntry table[] = {
{"method", method},
{"method2", method2},
}
// search the table and compare the name to locate function, you get the idea
If you know method signature you can create selector to it with NSSelectorFromString function, e.g.:
SEL selector = NSSelectorFromString(#"doWork");
[worker performSelector:selector];
You may be able to do what you want with libffi. But unless you are doing something like create your own scripting language or something like that where you need to do this sort of thing a lot. It is probable overkill
I've wondered the SAME thing.. and I guess, after having researched it a bit.. there is NOT a "standard C" way to do such a thing.. (gasp).. but to the rescue? Objective C blocks!
An anonymous function.. that can be OUTSIDE any #implementation, etc...
void doCFunction() { printf("You called me by Name!"); }
Then, in your objective-C method… you can somehow "get" the name, and "call" the function...
NSDictionary *functionDict = #{ #"aName" : ^{ doCFunction(); } };
NSString *theName = #"aName";
((void (^)()) functionDict[theName] )();
Result: You called me by Name!
Loves it! 👓 ⌘ 🐻

Functions in Objective-C

I am trying to write a function which returns a string created from two input strings;
but when I try the function declaration
NSString Do_Something(NSString str1, NSString str2)
{
}
the compiler gets sick. (Worked fine for a different function with int arguments.)
If I change the input arguments to pointers to strings, in also gets sick.
So how do I pass Objective-C objects into a function?
All Objective-C objects being passed to functions must be pointers. Rewriting it like this will fix your compiler error:
NSString *Do_Something(NSString *str1, NSString *str2) { }
Also, please keep in mind that this is a (C-style) function and not an instance method written on an Objective-C object. If you wanted this to actually be a method on an object it would probably look something like this:
NSString *doSomethingWithString1:(NSString *)str1 string2:(NSString *)str2 { }
I say "probably" because you can name it however you want.
Functions are perfectly fine in Objective-C (and in fact earn some of the language's benefits).
See my answer to C function always returns zero to Objective C, where someone was trying what you are and had a problem with the compiler assuming return type. The structure that I set up there is important when you are using functions, just like when you are using objects and methods. Be sure to get your headers right.
To be pedantic, you're using a function definition of:
NSString *DoSomething(NSString *str1, NSString *str2) {
// Drop the _ in the name for style reasons
}
And you should be declaring it in a .h file like so:
NSString *DoSomething(NSString *str1, NSString *str2);
Just like C.
that doesn't work for me. i've just declared in the .h:
NSString *myFunction(NSDecimal *value);
and i type in the .m:
NSString *myFunction(NSDecimal *value){
//code
}
but always i get an error saying expected '(' before '*' token
now is fixed. for some reason... sorry.