Swift doesn't see Objective-C methods with variable arguments - ios

I created Objective-C class with two methods. Both of them works with variable list of arguments:
#interface TestClass : NSObject
- (void)methodWithVariableArguments:(NSString *)string arguments:(va_list)arguments;
- (void)methodWithVariableArguments:(NSString *)string, ...;
#end
When I try to use methods of this class in Swift code, it allows me to use only first method:
var testObject = TestClass()
testObject.methodWithVariableArguments("String", arguments: pointer)
The second one Swift can't recognize.
If I try to navigate to TestClass from Swift code, it shows me this:
class TestClass : NSObject {
func methodWithVariableArguments(string: String!, arguments: CVaListPointer)
}
It just ignored my second method.
NSString has similar methods and both of them work in Swift.
Does anybody know how to fix it?
Update
I sent bug report to Apple. They confirmed that they have such problem.
Ticket id is 21848465. It was marked as duplicate to ticket with id 15935999. The last on has "Open" status.

It is simply impossible now. Swift can't do that. Workaround is to create a wrapper method with (va_list) arguments and pass them as arguments. If it is not your class, you could create a category or else just add it to your source.

Related

Add Swift protocol conformance to Objective-C header and make it public

I've read along here and here about conforming to Swift protocols in Objective-C headers, and I'm not quite getting the behaviour I want - I'm also looking for a better understanding of how this all works. Here's my scenario.
I have a protocol in Swift:
PersonalDetailsProtocol.swift
#objc
protocol PersonalDetailsProtocol {
func doSomeWork()
}
I then have an Objective-C class with a header and implementation file
RegistrationPersonalDetails.h
#protocol PersonalDetailsProtocol; // Forward declare my Swift protocol
#interface RegistrationPersonalDetails : NSObject <PersonalDetailsProtocol>
#end
RegistrationPersonalDetails.m
#import "RegistrationPersonalDetails.h"
#implementation RegistrationPersonalDetails
- (void)doSomeWork {
NSLog(#"Working...");
}
#end
At this point everything compiles, although there is a warning in the RegistrationPersonalDetails.h file stating Cannot find protocol definition for 'PersonalDetailsProtocol'. Other than that warning, the issue I'm facing is I can't publicly call the doSomeWork method on an instance of RegistrationPersonalDetails.
The call site in Swift would look something like:
let personalDetails = RegistrationPersonalDetails()
personalDetails.doSomeWork()
but I get an error stating:
Value of type 'RegistrationPersonalDetails' has no member 'doSomeWork'
I get that the method isn't public, because it's not declared in the header file. But I didn't think it should have to be as long as the protocol conformance is public i.e. declared in the header file.
Can anyone point me on the right path here and offer an explanation? Or is this even possible? I can obviously rewrite the protocol in ObjC, but I just always try to add new code as Swift.
In pure objective-c, you cannot make a class conform to a protocol without importing it in the header. To make the use of a protocol private, the conformance shouldn't be declared in the header. It remains possible to call such a method using a cast however, but it should be done with caution because it's a little bit unsafe :
if ([anInstance respondToSelector:#selector(myProtocolMethod)])
[(id<MyProtocol>)anInstance myProtocolMethod];
I'm not familiar with Swift, but I think you can do the same this way (or something close to it) :
if let conformingInstance = anInstance as? MyProtocol
conformingInstance.myProtocolMethod
EDIT : To complete my first assertion, forward declarations can still be used in the header when you need to declare a method receiving or returning an instance conforming to that protocol :
#SomeProtocol;
// This is not possible
#interface MyClass : NSObject <SomeProtocol>
// But this is possible
#property (nonatomic) id<SomeProtocol> someProperty;
-(void) someMethod:(id<SomeProtocol>)object;
#end
In this document Apple clearly said that :
Forward declarations of Swift classes and protocols can be used only
as types for method and property declarations.
So it seems that the rule is the same whatever the protocol is an Objective-c protocol or a Swift protocol.

gomobile: binding callbacks for ObjC

I have a Go interface
type GetResponse interface { OnResult(json string) }
I have to subscribe on that event OnResult from ObjC using this interface.
func Subscribe( response GetResponse){ response.OnResult("some json") }
ObjC bind gives me a corresponding protocol and a basic class
#interface GetResponse : NSObject <goSeqRefInterface, GetResponse> {
}
#property(strong, readonly) id _ref;
- (instancetype)initWithRef:(id)ref;
- (void)onResult:(NSString*)json;
#end
So, I need to get this json in my ObjC env. How can I do that?
Subclassing If I subclass this GetResponse or just use it as is and pass to Subscribe routine, it crashes
'go_seq_go_to_refnum on objective-c objects is not permitted'
Category if I create struct on Go side with the protocol support, I can't subclass it but at least it's not crashes:
type GetResponseStruct struct{}
func (GetResponseStruct) OnResult(json string){log.Info("GO RESULT")}
func CreateGetResponse() *GetResponseStruct{ return &GetResponseStruct{}}
I have a solid object without obvious way to hook up my callback. If I make a category and override the onResult routine, it's not called. Just because overriding existing methods of class is not determined behavior according to AppleDoc. Anytime OnResult called from Go, the default implementation invokes and "GO RESULT" appears.
Swizzling I tried to use category and swizzle (replace method's implementation with mine renamed method) but it only works if I call onResult from ObjC env.
Any way to solve my issue? Or I just red the doc not very accurately? Please help me
I ran into a similar issue today. I thought it was a bug in gomobile but it was my misunderstanding of swift/objc after all. https://github.com/golang/go/issues/35003
The TLDR of that bug is that you must subclass the protocol, and not the interface. If you are using swift then you can subclass the GetResponseProtocol:
class MyResponse: GetResponseProtocol
If in objc, then you probably want to directly implement the GetResponse protocol, rather than subclassing the interface.

How to bridge Objective-C initWithError: method into Swift

I have a class defined in Objective-C, whose initializer is -initWithError: (the initializer can fail due to a dependence on an outside resource). I want this to bridge into Swift as init() throws. The regular initializer inherited from NSObject, -init, can be marked unavailable, as I don't want it to be used.
In Objective-C, I have:
#interface Foo : NSObject
- (instancetype _Nullable)initWithError:(NSError **)error;
#end
This works fine in Objective-C, of course.
In Swift, -initWithError gets bridged as init(error: ()) throws. This is presumably because removing withError: from the method name results in init() which conflicts with the inherited plain -init initializer. This can be called from Swift like so:
let foo = try Foo(error: ())
This is strange looking, as the error parameter is void. It would certainly be better if this were imported as init() throws. The obvious solution is to mark -init using NS_UNAVAILABLE in the Objective-C header. Unfortunately, this doesn't work. -initWithError: still gets bridged as init(error: ()), and trying to call try Foo() results in a compiler error saying that init() is unavailable in Swift.
Is there a more elegant solution to this so that try init() just works?
You can rename the function using NS_SWIFT_NAME. In this case:
- (instancetype _Nullable)initWithError:(NSError **)error NS_SWIFT_NAME(init());
That said, this feels like a compiler bug. I'd suggest opening a defect.

How to access an internal Swift class in Objective-C within the same framework?

Working on a mixed framework. imported inside the Obj-C file but the internal classes are not visible, only the public ones.
The documentation clearly states the internal clasees should be available between Swift and Obj-C:
Importing Swift into Objective-C To import a set of Swift files in the same framework target as your Objective-C code, you don’t
need to import anything into the umbrella header for the framework.
Instead, import the Xcode-generated header file for your Swift code
into any Objective-C .m file you want to use your Swift code from.
Because the generated header for a framework target is part of the
framework’s public interface, only declarations marked with the public
modifier appear in the generated header for a framework target. You
can still use Swift methods and properties that are marked with the
internal modifier from within the Objective-C part of your framework,
as long they are declared within a class that inherits from an
Objective-C class. For more information on access-level modifiers, see
Access Control in The Swift Programming Language (Swift 2).
Code Sample (Create a new project with a framework)
// SwiftObject.swift
public class SwiftObject: NSObject {
public class func doSomething() {}
}
internal class YetAnotherSwiftObject: NSObject {
internal class func doSomething() {}
}
// SomeObject.m file
#implementation SomeObject
- (void)someMethod {
[SwiftObject doSomething];
}
- (void)someOtherMethod {
[YetAnotherSwiftObject doSomething]; // Use of undeclared identifier
}
#end
As indicated in the docs, declarations marked with internal modifier don't appear in the generated header, so the compiler does not know about them and thus complaints. Of course, you could send messages using performSelector approach, but that's not convenient and bug-prone. We just need to help the compiler know that those declarations are there.
First, we need to use #objc attribute variant that allows you to specify name for your symbol in Objective-C:
// SwiftObject.swift
#objc(SWIFTYetAnotherSwiftObject)
internal class YetAnotherSwiftObject: NSObject {
internal class func doSomething() {}
}
And then you just need to create #interface declaration with the methods you want to use in your code - so the compiler will be happy, and also apply SWIFT_CLASS macro with the symbol name you've specified earlier - so the linker would pick the actual implementation:
// SomeObject.m file
SWIFT_CLASS("SWIFTYetAnotherSwiftObject")
#interface YetAnotherSwiftObject : NSObject
+ (void)doSomething;
#end
#implementation SomeObject
- (void)someOtherMethod {
[YetAnotherSwiftObject doSomething]; // Should work now !!!
}
#end
I've used the interface declaration in .m file just for clarity, the better option would be to combine such declarations in .h file, and include it.
By declaring methods in that interface we're making a promise to compiler, and it won't complain if you'll put there a method that does not exist (or with wrong signature, etc.) Obviously, you'll crash in runtime in that case - so be cautious.
For me it just worked by checking: "Allow app extension API only". You find it by going to the project setting, select your target and then it is in the General tab under Deployment Info.
Can someone explain to me, why this does solve the problem?
While the above solution works (https://stackoverflow.com/a/33159964/5945317), it seems overly complicated and unintuitive:
Complicated, because it seems to add more things than necessary – I will provide a smoother solution below.
Unintuitive, because the objc macro SWIFT_CLASS resolves to SWIFT_RUNTIME_NAME, and the provided value is not actually the runtime name – nor is the objc class name in the header matching the Swift attribute param in #objc. Still, surprisingly, the solution works – but to me it is not clear why.
Here is what we have tested in our own project, and believe to be the better solution (using the example above):
// YetAnotherSwiftObject.swift
#objc(OBJCPREFIXYetAnotherSwiftObject)
internal class YetAnotherSwiftObject: NSObject {
#objc internal class func doSomething() {}
}
// OBJCPREFIXYetAnotherSwiftObject.h
#interface OBJCPREFIXYetAnotherSwiftObject : NSObject
+ (void)doSomething;
#end
That's it. The interface looks like a regular objc interface. This gives the added benefit that you can include it in other header files (which you cannot do if you use the SWIFT_CLASS macro, as it comes from the autogenerated Swift header file, which in turn you cannot include in an objc header, due to circular dependency).
On the Swift side, the only thing relevant is that you provide the class with the proper objc name. Mind that I only used the name prefix for language consistency – you can even just use YetAnotherSwiftObject everywhere (i.e., in the objc header and in the #objc attribute in Swift – but you need to keep this attribute with explicit naming in any case, and need to keep it consistent with the class name in the header).
This also makes your life easier if you're in the process of converting your objc framework step by step to Swift. You just keep the objc header as before, and now provide the implementation in Swift.
Methods and properties that are marked with the internal modifier and declared within a class that inherits from an Objective-C class are accessible to the Objective-C runtime.
so let's make use of that:
class MyInternalClass: NSObject {
#objc var internalProperty = 42
}
#interface MyPublicClass()
#end
#implementation MyPublicClass
+ (void) printValue {
Class myInternalClass = NSClassFromString(#"MyPackageNameIfAny.MyInternalClass");
id myInternalClassInstance = [myInternalClass new];
int value = [myInternalClassInstance performSelector:#selector(internalProperty)];
NSLog(#"Value is %d ", value); // "value is 42"
}
#end
Using the SWIFT_CLASS macro and #objc class attribute could easily lead to errors when archiving. This approach is safer.

When to declare Class Extension Methods

I've made use of Class Extensions in the .m as a way to have "private" methods and variables. I've read that since Xcode 4.4, the compiler no longer needed the private methods declared.
For example this would compile even though helperMethodC is not declared:
in .h
#interface MyClass : NSObject
-(void)publicMethodA;
#end
in .m
#interface MyClass ()
- (void) pseudoPrivateMethodB;
#end
#implementation MyClass
- (void)publicMethodA
{
//Do Something
}
- (void)pseudoPrivateMethodB
{
[self helperMethodC];
}
- (void) helperMethodC
{
// Do something
}
While private methods no longer have to be declared to compile (helperMethodC), is there a style guide, historical reason, or rule that all private methods (i.e. helperMethodC) should still be declared? Or a "rule" for when to declare and not declare private methods?
Declare them if they help you. From a documentation point of view they are very useful. The compiler will also tell you if you have specified that a method will exist and then not implemented it. There is no rule, but its a good idea to add them. Consider how you'll feel if you have to come back in 6 months and edit the class - will having the methods listed there help you?
While private methods no longer have to be declared to compile (helperMethodC), is there a style guide, historical reason, or rule that all private methods (i.e. helperMethodC) should still be declared? Or a "rule" for when to declare and not declare private methods?
There are multiple conventions, but no standard.
You really should have them when/if you need to support older toolchains -- GCC or older versions of Clang.
Once that restriction is removed, I think it's best if you just phase the (redundant) declarations out where they are not needed. High warning levels and ARC semantics can guide you here.
If you introduce types:
Something * s = [array objectAtIndex:i];
s.string = #"string";
// rather than: [array objectAtIndex:i].string = #"string";
And name your selectors uniquely for the parameter/return types:
// BAD: May be ambiguous.
// Solution: Choose a more descriptive selector name.
// class A
- (void)setX:(int)x;
// class B
- (void)setX:(double)x;
then the compiler can inform you of ambiguities.

Resources