Is there any way that an Objective-C method could operate on primitive parameters without knowing their types? For instance,
-(Any)returnFirst:(Any)first notSecond:(Any)second {
return first;
}
This could then be used like:
int a = [self returnFirst:500 notSecond:1000];
char b = [self returnFirst:'a' notSecond:'b'];
There is no equivalent to Any in Objective-C. Your example returnFirst:notSecond: cannot even be declared in Objective-C.
In Swift 3, the id type in Objective-C now maps to the Any type in Swift, which describes a value of any type, whether a class, enum, struct, or any other Swift type.
Source: https://developer.apple.com/swift/blog/?id=39
Related
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.
Within a Swift class derived from an Obj-C based framework (but could just as easily be a Swift class with an #objc attribute) I declare two stored properties:
var optInt: Int?
var optString: String?
Only optString is being exposed to Obj-C via the generated -Swift.h header.
String? is presumably fine because it is exposed using an NSString object which can be nil, so the bridging has a way to represent no value.
If I remove the ? from optInt it's exposed with an NSInteger type, so I can see that for non-optional integers it avoids objects and bridges value type to value type, but does this literally mean that an Int? can't be exposed?
I can't seem to find any documentation that explicitly says this is the case. There is a whole list of incompatible Swift features here that this doesn't appear on: Using Swift from Objective-C
The use case here is the classic situation requiring the passing of a numeric ID which could legitimately be zero. In the pre-Swift world NSNumber and nil is exactly how I went about implementing this, but it just feels wrong to be trying to migrate a class to Swift but then hanging on to Obj-C types within the Swift class specifically for this reason.
I suppose I had envisaged that an Int? unlike Int would bridge as an NSNumber in the background, with its potentially nil value feeding the "has no value" element of the optional in Swift.
Is there anything I'm missing here? To reiterate, can a Swift Optional Int (Int?) be exposed to Objective-C via bridging?
The problem with exposing an Int? property as NSNumber* is that you could store a non-Int-compatible NSNumber to it from Objective-C.
You can make a computed NSNumber? property to wrap your Int? property. The getter would simply return the Int? variable. The setter would set the Int variable from -[NSNumber integerValue].
Here's a concrete answer for the solution described above:
private var _number: Int?
public var number: NSNumber? {
get {
return _number as NSNumber?
}
set(newNumber) {
_number = newNumber?.intValue
}
}
// In case you want to set the number in the init
public init(number: NSNumber?) {
_number = number?.intValue
}
I have this type of Enum with TypeDef:
typedef enum {
ControlDisplayOptionNone = 0,
ControlDisplayOptionOne = 100
} ControlDisplayOption;
And I'd like to be able to put them in an array like this:
- (NSArray *)displayOptions {
return #[#ControlDisplayOptionNone];
}
but that won't work, and even this won't work:
NSNumber *test = #ControlDisplayOptionNone;
the only option that will work is traditional:
return #[[NSNumber numberWithInt:ControlDisplayOptionNone]];
Is there any way to use autoboxing for this?
Use parentheses: #(ControlDisplayOptionNone)
The syntax is explained in the Clang documentation for Objective-C Literals. The "Boxed Enums" section says:
Cocoa frameworks frequently define constant values using enums. Although enum values are integral, they may not be used directly as boxed literals (this avoids conflicts with future '#'-prefixed Objective-C keywords). Instead, an enum value must be placed inside a boxed expression.
I want to use the return type of a selector to determine how it is used in my code is there a way to differentiate the return types in objective c I'll give you an example.
SEL selectors[]=
{#selector(method1),
... //each method returns a different type
#selector(methodn);}
for (SEL sel in selectors)
{
switch [[self performSelector:sel]/*idk something here maybe?*/]
{
case int:
//do some stuff
...
case NSString *:
//do some other stuff
}
}
Thanks in advance I couldn't find anything anywhere on this that talked about objective c
you can do this via method_copyReturnType in objc runtime.
however, objc types for return and parameters are all the same (last i checked), such that the runtime will not return the type with the description "NSArray" -- it will just be the identifier for an objc type. nevertheless, that level of detail is descriptive enough for your int or NSString case, and you can use an NSObject instance's class or isKindOfClass: (etc.) instance methods to determine its type once you have a handle on it.
You can get the NSMethodSignature of the method using -methodSignatureForSelector:. And then you can get the return type with the -methodReturnType from the NSMethodSignature object. Don't mess with the runtime if you don't have to.
I was wondering if there is any sample code out there for objective-C for implementing a NSMutableArray of type struct. Inside, I need there to be 2 mutable arrays (via NSMutableArray also) declared in the struct. All the code samples in my book show me how to make an array of defined size via C array syntax (with the brackets), but I don't know how to get one going with NSMutableArray. Has anyone else done this before? Here's my code so far...It compiles fine I have defined the size of the arrays (2 and 5 are used in my code below as an example, but I need to set it so I can have them mutable. I can work with simple structs when they just have some of the "easier-to-understand" data types like int, double, short, long, BOOL (you get the idea). When it gets into pointers though, this is where I become lost (I can use pointers fine, but knowing how to put them in a struct is the difficult part). Once the code is working with NSMutableArray's, would I put "network" in the interface as a pointer to type "Network"? I tried this before, but I got errors. In the end, I basically want to be able to write
network.input[2].list[1].value = 5.0;
on an arbitrarily defined array of type "Network". Could anyone offer suggestion or links to information about making a NSMutableArray of type "Network" which includes a struct of two NSMutableArray's? Thanks for any help!
SomeFile.h
#import <Foundation/Foundation.h>
struct lists{
double value;
};
// supporting structs
struct inputs{
struct lists list[2];
};
struct Network {
struct inputs input[5];
struct lists output[5];
}
#interface SomeFile : NSObject {
}
#end
SomeFile.m
#import "SomeFile.h"
#implementation SomeFile
#end
NSArray and NSMutableArray can only contain Objective-C objects, so you can't store structs in them. If the contents must be structs and you want something similar to NSArray, use NSPointerArray, available in 10.5 and later.
You can store pointers to Objective-C objects (like NSPointerArray* or id) inside a struct just like any other pointer type. For example, you could declare a struct for a doubly-linked list node that stores an Objective-C object like this:
typedef struct DoublyLinkedListNode {
id object;
__strong struct DoublyLinkedListNode *next;
__strong struct DoublyLinkedListNode *prev;
} DoublyLinkedListNode;
The __strong attribute is used in connection with garbage collection in Objective-C 2.0, since pointers to Objective-C objects act as strong references, but C pointer types do not by default. This way, as long as one node in the list is referenced from a __strong reference, the list won't disappear. (Read the Garbage Collection Programming Guide for details, and particularly the second half of Using Core Foundation with Garbage Collection.) You'll probably want to consider doing this for your structs.
As far as your desired syntax, I may not have fully understood your question, but you won't be able to use the bracket syntax to access objects in a Cocoa collections like an NSPointerArray. (Also, odds are you'll have to use the "->" operator instead of "." for the structs, since they're likely to be allocated on the heap. All Objective-C objects must be, and I assume you'll want to store these structs outside of the local scope of a method.)
Since Objective-C doesn't have generics, you also can't "implement [an] NSMutableArray of type struct". In fact, one of your previous SO questions has more detail on the subject. If that's not what you meant, feel free to clarify.
This is not be a complete answer; I’m not sure what you mean by not knowing how to put pointers in structs. I’m going to proceed by assuming you want to model an network of multiple inputs and outputs with a dynamic number of both.
You have a few choices here:
Use value objects instead of structs to store your values:
[[[network inputs] objectAtIndex:2] replaceObjectAtIndex:1 withObject:[NSNumber numberWithDouble:5.0]];
Model your Network with an object:
#interface Network : NSObject {
// ivars
}
- (void) setInput:(double)value atIndex:(NSInteger)valueIndex ofInputAtIndex:(NSInteger)inputIndex;
- (double) outputAtIndex:(NSInteger)index;
#end
Just use structs like you’re already doing; if you need to change the size up-front, use your friend malloc:
struct Network_value {
double value;
}
struct Network {
struct Network_value **inputs;
struct Network_value *outputs;
};
void Network_alloc(Network *n, unsigned inputs, unsigned input_values, unsigned outputs) {
n->outputs = calloc(sizeof(Network_value), outputs);
n->inputs = calloc(sizeof(Network_value *), inputs);
while (inputs --> 0) {
n->inputs[inputs] = calloc(sizeof(Network_value), input_values);
}
}
void Network_free(Network *n, unsigned inputs) {
free(n->outputs);
while (inputs --> 0) {
free(n->inputs[inputs]);
}
free(n->inputs);
}
Network network;
Network_alloc(&network, 5, 2, 5);
network.inputs[2][1].value = 5.0;
Network_free(&network, 2);
Combine ideas 2 and 3 by presenting a Network object but internally store the values with structs. This is probably a good idea if the number of inputs and outputs is very large.