I'm porting a Swift 1.2 framework to 2.0. After fixing the 3 million compiler errors courtesy of the new Swift 2.0 error handling scheme, I was finally able to link my test app (written in Objective C) to use the updated framework.
Xcode version 7.0 beta 3 (7A121l)
However, I ran into a problem. Some Swift functions are no longer generated into the automatically generated Objective C header (MyFramework-Swift.h) used by the Objective C test app.
Here is an example of a function that is not exposed: (My actual framework function returned an enum, but I tried to simplify to illustrate the problem more clearly).
public func calculateImportantValueDangerously() throws -> Int
{
return 42
}
Note that other functions like the following actually do get exposed as expected (and can be called):
public func doSomething()
{
}
public func doSomethingDangerous() throws
{
}
public func calculateMeaninglessValue() -> Int
{
return -1
}
Here's the Objective C side:
MyClass *newInstance = [[MyClass alloc] init];
[newInstance doSomething];
NSError *error = nil;
[newInstance doSomethingDangerousAndReturnError:&error];
long meaninglessValue = [newInstance calculateMeaninglessValue];
NSLog(#"%ld", meaninglessValue);
long importantValue = [newInstance calculateImportantValueDangerouslyAndReturnError:&error]; <-COMPILE ERROR
NSLog(#"%ld", importantValue);
From watching this video, I had expected that it should "just work":
https://developer.apple.com/videos/wwdc/2015/?id=401
...but it seems like we can't currently use functions that both throw and return a value.
Is this a bug, or not implemented feature? My apologies if I missed something in the release notes somewhere.
Any advice appreciated.
It is not possible.
If you annotate your method with #objc you will see the problem.
Throwing method cannot be marked #objc because it returns a value of
type 'Int'; return 'Void' or a type that bridges to an Objective-C
class
You can return only objects, primitives are not supported.
Although the selected answer is correct; you can't return an Int for a throwing function, I wanted to note an alternate solution since Apple has this same issue for a function in the CoreData framework:
func countForFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> Int
Note that in this case Apple is not using the throws pattern and instead reverting to the classic NSError out parameter mechanism.
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.
I'm trying to build an Objective-C block in Swift 2 in order to add it to an NSArray like so :
typealias CompletionBlock = () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
nsArray.addObject(aBlock) // Error
I know it will work just fine with a Swift array, but I need an NSArray here for compatibility with existing Objective-C code. And if I use a swift array the compiler will refuse to cast it to an NSArray because it won't be a [AnyObject] (it will be a [Any]).
The problem here is that a swift closure is not an object contrary to Objective-C blocks which are objects behind the scene (they are instances of NSBlock which is a subclass of NSObject)
So my question is : How do a create an Objective-C block in swift ? I've tried using #convention (block) in the typealias but it doesn't work.
EDIT : As of Swift 3, this is completely unnecessary (and doesn't even work). Adding closures to Objective-C arrays works out of the box in Swift 3. The answer below is valid for Swift 2 only.
I know this is a duplicate but I will still post a refactored answer from swift-closure-as-anyobject and cast-closures-blocks in case anyone lands on this one first.
The solution is to use the unsafeBitCast function to convert the Swift closure to an Objective-C compatible object before adding it to an NSArray and back before using it in Swift.
// The `#convention(block)` is important here in order to get
// Objective-C like memory management
typealias CompletionBlock = #convention(block) () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
let blockObject = unsafeBitCast(aBlock, AnyObject.self)
nsArray.addObject(blockObject)
let closureObject = nsArray[0]
let closure = unsafeBitCast(closureObject, CompletionBlock.self)
closure()
My Situation:
I have learnt Swift for a while and Swift is my only language I've learnt for iOS Development, which means I did not learn Objective-C Systematically. I can just read OC Code a little.
And I got a Type Recognition problem when I tried to translate a OC project to Swift project.
Objective-C Code:
static inline NSRegularExpression * AccountRegularExpression() {
static NSRegularExpression *_accountRegularExpression = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_accountRegularExpression = [[NSRegularExpression alloc] initWithPattern:AccountRegular options:NSRegularExpressionCaseInsensitive error:nil];
});
return _accountRegularExpression;
}
I confused with those mysterious code.
My Problem:
For now, I just met two different things in swift and OC, almost are variable instance and function, which means I can read and write the code to initial a variable instance and crate the function. But I never seen the thing like that in OC (Yep, I am a beginner...), it is not look like variable instance or method.
Before asked the question here, I tried to write a swift function to serve as the part like the original one. The thing is, complier did not thrown any error after I built the project.
Here is my Swift Code:
func AccountRegularExpression() -> NSRegularExpression {
var accountRegularExpression: NSRegularExpression!
var onceToken: dispatch_once_t = 0
dispatch_once(&onceToken, {
accountRegularExpression = NSRegularExpression(pattern: self.AccountRegular, options: NSRegularExpressionOptions.CaseInsensitive, error: nil)
})
return accountRegularExpression
}
I have no idea whether this Swift code have the same function like that OC code.
My Question:
Can you tell me is that OC code a variable instance? I think it's not a function or method.
I Google the key word, inline, which is the process for anti-compiling. Is this process still work in the Swift?
How to translate that OC code in Swift in the right way, I guess my swift code is not correct.
A big appreciation for your guide and time.
Ethan Joe
The translation is wrong because the variables _accountRegularExpression and onceToken are declared static in C, which for local variables means that its state is kept between function invocations, for the whole lifetime of the program, similar to global variables.
That they are static is essential for the correct functioning of this function, which is to return a singleton instance. If they are not static, then:
dispatch_once requires that you give it a pointer to a dispatch_once that is "stored in global or static scope". By giving it a local variable with automatic storage, you are invoking undefined behavior. It's impossible to know whether it will execute it once or more than once.
If _accountRegularExpression is not static, then that means this function cannot remember and return the same instance that it created before. Therefore, it is not a "singleton". If the dispatch_once only executes once, then on the second and subsequent time this function will return nil. If the dispatch_once executes more than once, then every time it executes again it will return a new independent instance. Under no situation does it return an instance that has been returned before.
To make it function correctly in Swift, one way is to make those variables global:
var accountRegularExpression: NSRegularExpression!
var onceToken: dispatch_once_t = 0
func AccountRegularExpression() -> NSRegularExpression {
dispatch_once(&onceToken, {
accountRegularExpression = NSRegularExpression(pattern: self.AccountRegular, options: NSRegularExpressionOptions.CaseInsensitive, error: nil)
})
return accountRegularExpression
}
However, a better way for the function to return a singleton in Swift would be something like:
func AccountRegularExpression() -> NSRegularExpression {
struct Singleton {
static let sharedInstance = NSRegularExpression(pattern: self.AccountRegular, options: NSRegularExpressionOptions.CaseInsensitive, error: nil)
}
return Singleton.sharedInstance
}
Or just get rid of the function and make it a computed property:
var accountRegularExpression : NSRegularExpression {
struct Singleton {
static let sharedInstance = NSRegularExpression(pattern: self.AccountRegular, options: NSRegularExpressionOptions.CaseInsensitive, error: nil)
}
return Singleton.sharedInstance
}
Or if this makes sense inside some class, then just put it directly as a static property of the class:
// inside some class
static let accountRegularExpression = NSRegularExpression(pattern: self.AccountRegular, options: NSRegularExpressionOptions.CaseInsensitive, error: nil)
TL;DR: It looks right, it is a function taking an argument of a function.
You are confused by block syntax in Objective-C, and closure (basically the same thing) syntax in Swift. Read the function / closure section in the swift language docs, and refer to this pages:
http://fuckingblocksyntax.com
http://fuckingclosuresyntax.com
A possibly correct translation would be-
class SomeClass
{
static let regularPatternForRegularAccount = "Some Pattern";
static let regularExpressionForRegularAccount = NSRegularExpression(pattern:SomeClass.regularPatternForRegularAccount,
options: NSRegularExpressionOptions.CaseInsensitive,
error: nil)
}
Here, we have made the regular expression a 'static let' and it will be evaluated lazily and only once for the first time it is accessed. Also look at:
singletons in swift
I'm running into a strange issue that involves simple inheritance in Swift. I might be doing something totally stupid so if anyone has any advice.. thanks in advance!
I am using the latest XCode 6 GM version 6A313.
Here are the 2 Swift classes that's made Objective-C backward compatible.
#objc
public class ObjectA : NSObject {
var testProp: String!
init(testProp: String) {
self.testProp = testProp
}
}
#objc
public class ObjectB : ObjectA {
var testPropB: String!
init(testProp: String, testPropB: String) {
self.testPropB = testPropB
super.init(testProp: testProp)
}
}
I then initialize and use the object in Objective-C code.
ObjectB *objectB = [[ObjectB alloc] initWithTestProp: #"TestProp" testPropB: #"TestPropB"];
// This executes correctly and has the correct value
NSLog(#"%#", objectB.testPropB);
// I then pass this newly constructed object to another class that's written in Swift
AnotherClass *anotherClass = [[AnotherClass alloc] init];
[anotherClass someMethod:objectB];
Here is the Swift class where when I attempt to access the inherited property, I get an EXC_BAD_ACCESS code 1.
#objc
public class AnotherClass : NSObject {
public func someMethod(objectB: ObjectB) {
// This executes and assigns correctly
let prop = objectB.testProp
// This errors out with EXC_BAD_ACCESS error code 1
// In the debugger, objectB.testPropB actually seem to have the correct value
let propB = objectB.testPropB
}
}
var testProp: String!
There is no reason for this to be an implicitly unwrapped optional. You always initialize it in init. If possible, it should be:
let testProp: String
If it must be mutable after assignment, then it should be:
var testProp: String
Fixing this will probably cause the compiler to shout at you where you're doing something wrong (possibly not in the code you've posted here). With a !, it is legal to fail to initialize this value (you'll just crash later). Without a !, the compiler will better verify that you do initialize this value.
This line:
ObjectB *objectB = [[ObjectB alloc] initWithTestProp: #"TestProp", testPropB: #"TestPropB"];
is not correct ObjC (there's an extra comma). I'll assume that it's a typo. I don't think that can result in a legal C comma-operator that would cause the wrong initializer to run. But maybe... it's at least worth checking if you actually do have a comma in there. (I'm pretty sure testPropB:#"..." isn't a legal expression, so this is a little far-fetched, but it would match your symptom).
So, I ended up creating a dummy project to test my theory in a more controlled environment. The fix is basically to ensure that ObjectA and ObjectB are in 2 separate Swift files. When they are in the same Swift file, the error occurs.
Here is the sample code for anyone interested.
https://github.com/MystKnight/swift-inheritance-test
I'm not sure why this is but for everyone out there, I guess limit your files to one class if you are using inheritance...
I'm trying to call a method implemented in Obj-C from C code as follows:
// MainViewController.m
- (void)Test
{
[self outputLine:#"Called from MyCode.c"];
}
.
.
.
cFunc(id param);
.
.
.
// MyCode.c
void cFunc(id param)
{
[param Test]; // compilation error: Parse issue: Expected expression
}
I guess it happens since the MainViewController is not declared in MyCode.c, but when I #include the MainViewController.h I get tons of other errors that suggests I'm totally wrong...
How should I handle it correctly?
TIA
You should compile the MyCode.c file as Objective-C. Objective-C is a superset of C, but it's not true the other way around. You can't have Objective-C code interspersed with your "pure C" code if you are intending to compile it as C.
Check: using objc_msgSend to call a Objective C function with named arguments
void cFunc(id param) {
objc_msgSend(param, sel_getUid("Test"));
}
But, as per the link above, this is dangerous for a few reasons, if your arguments don't fit in registers (i.e. floats, structs, blah blah).
The accepted way of doing this is to cast objc_msgSend:
void cFunc(id param) {
// Now let's pretend that you want to send someFloat to your method
void (*objc_msgSendTyped)(id self, SEL _cmd, float bar) = (void*)objc_msgSend;
float someFloat = 42.f;
objc_msgSendTyped(param, sel_getUid("Test"), someFloat);
}
Just change your myCode.c to myCode.m :P
Don't be afraid to put C code in an Objective-C file.