Category for obj-c class derived from swift - ios

I am running project with lots of legacy code with objc and swift.
I've been using objc MPOldKeychainManager, which is now deprecated and swift's NewKeychainManager is to be used.
The problem is following: MPOldKeychainManager had some categories written and I don't want to rewrite them as swift extensions.
What I've done is:
naming the class derived from the NewKeychainManager (visible in "myTarget-Swift.h") to "MPOldKeychainManager"
removing the objc declaration of MPOldKeychainManager
...hoping that the categories will still work.
objc(MPOldKeychainManager)
class NewKeychainManager: KeychainManager {
}
Unfortunately, old extensions can't see the MPOldKeychainManager (derived from swift), even though I've updated the imported header to myTarget-Swift.h
#import "myTarget-Swift.h" //previously - objc "MPOldKeychainManager.h"
#interface MPOldKeychainManager (Authentication)
Question: is it possible to use categories for objc classes derived from swift?
I have already tried totally new naming
I have already tried loads of clean-builds

In case you haven't seen it, here is a useful resource for migrating from Objective-C to Swift: https://developer.apple.com/documentation/swift/migrating_your_objective_c_code_to_swift . Among other things, it states that one cannot subclass a Swift class in Objective-C. What you are trying to do is specify a different Objective-C name, MPOldKeychainManager, for the NewKeychainManager Swift class.
This will, actually, work if you add an ampersand before objc, like so:
#objc(MPOldKeychainManager)
class NewKeychainManager: KeychainManager {
}
You can then use all your existing categories in Objective-C. You will, however, have a problem using them in Swift, because to be usable in Swift they need to be available in the bridging header, and you won't be able to use the class' Objective-C name (MPOldKeychainManager) in the bridging header.
You can, however, write an Objective-C wrapper class that will have a method corresponding to each category method and also taking a NewKeychainManager pointer. A wrapper method can then delegate to the category method, which is available to Objective-C code, so you won't have to re-implement your category methods in Swift.
Let's say an Objective-C category has method authenticateUser::
#interface MPOldKeychainManager (Authentication)
-(void)authenticateUser:(int32_t)uid;
#end
The method could be wrapped as follows:
#interface OldKCMWrapper : NSObject
+(void)authenticateUser:(int32_t)uid withManager:(NewKeychainManager*)inst;
#end
This interface declaration must be available, directly or indirectly, via the bridging header. Then, somewhere in your Objective-C code, the wrapper could be implemented thus:
#implementation OldKCMWrapper
+(void)authenticateUser:(int32_t)uid withManager:(MPOldKeychainManager*)inst {
[inst authenticateUser:uid];
}
#end
The wrapper can then be used in Swift code, e.g.:
let kcm = NewKeychainManager()
OldKCMWrapper.authenticateUser(321, with: kcm)
In fact, the wrapper could be used in a Swift extension of NewKeychainManager. You would still have a Swift extension with equivalents of all the Objective-C category methods, but you would not have to re-implement their code in Swift: methods in the extension would simply delegate to the wrapper.
Hopefully this is helpful. There are other ways of implementing this idea, possibly more elegant.

Related

Question about Importing Swift code into objective c code

I read this documentation for importing swift code into objective c.
https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_swift_into_objective-c
I have a few questions.
Can I put #objc annotation for a Struct?
Do I need to inherit all the class that I want to export to obj to be child of NSobject ? I am getting error 'error: only classes that inherit from NSObject can be declared #objc'
When I export a swift class with #objc, I need to add #objc to all its parent classes, protocol and interface and also class and structure in all its methods, is that correct?
No. Objective-C cannot see a native Swift struct.
Yes. Objective-C classes must basically be derived from NSObject. Objective-C can be made aware of the existence of other classes, but it cannot do anything useful with them.
You can mark the class with #objcMembers, in which case you will give everything within it full visibility to Objective-C.

Accessing Objective-C header properties from Swift subclasses

I came late to the party and just switched to Swift. I will, however, continue using my libraries written in Objective-C but I'm having trouble accessing parent classes' properties from Swift. A typical structure looks like:
A.swift -> B.h -> C.h
Where A is part of a project that uses an Objective-C library. I've added the B and C header files in my bridging header and everything works fine. The only issue I'm having is accessing the public properties of my header files. For instance,
// C.h
#interface C
#property(nonatomic, readonly) Foo *foo
#end
Would normally be accessed with dot syntax from any subclass written in Objective-C. In Swift, instances of A do not appear to have members of type Foo. The only solution I have found so far is to manually add getters to all my properties but I would rather not modify my libraries' source code. Is there another approach or am I missing something?

Can I have a class that's partially implemented in Swift, and partially implemented in Objective C?

I want to extend my class which I wrote in Swift. I want to write the extension in Objective C because I need to put some Objective C code which I can't port into Swift. I know that I can create a .h and .m and then include the .h at the bridging header. But in the .h, I need to include the original .swift class file right?
How can I solve this? Can I include using myclass-swift.h? Thanks.
The following documentation may be helpful: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-ID122. You've probably already looked at that, but just in case. The following question may also help: Extending a Swift class with Objective C category.
As you know, to use Objective-C code in Swift there is the bridging header. To go the other way around, there is the auto-generated *-Swift.h header that should be imported in .m (and .mm) implementation files. The documentation says it should not be imported into .h files. In fact, the compiler won't let you import it into a .h file that is included, directly or indirectly, in the bridging header. However, with some care you can import it into other .h files.
Now, suppose your Swift class is called SwiftClass and you want to add to it a method called ocFunction() implemented in Objective-C. One approach, essentially presented in the aforementioned answer, is to implement a category in an Objective-C source file (.m):
#implementation SwiftClass (OCExtension)
-(void)ocFunction {...}
#end
Then modify your SwiftClass to include the following:
class SwiftClass : NSObject
{
...
#nonobjc func ocFunction()
{
self.perform(Selector(("ocFunction")))
}
...
}
The referenced answer suggests doing this in an extension, but since you have full control of the SwiftClass source, you can just do it in the class directly. BTW, the Objective-C category function could be named something other than the SwiftClass's function, thus eliminating the need for #nonobjc.
Another approach might be to define an Objective-C wrapper interface like this:
#interface SwiftClassOC : NSObject
+(void)ocFunction:(SwiftClass*)sc;
#end
and make it available to Swift via the bridging header. The implementation would go into a .m file. Then your ocFunction() in SwiftClass would look like
func ocFunction()
{
SwiftClassOC.ocFunction(self)
}
Please note that SwiftClassOC is stateless, so it's essentially a set of helper functions that take a SwiftClass pointer. SwiftClass is the one maintaining the state.
You can extend an Objective-C class with Swift, but you cannot extend a Swift class with Objective-C.
I want to write the extension in Objective C because I need to put some Objective C code which I can't port into Swift
You can do that, but not as part of the Swift class. What I usually do is make a "helper" class in Objective-C and import it into Swift.

Is it possible for Swift to inherit from an lightweight generic Objective-c Class?

I have two classes, the base class is in Obj-c and the subclass is in Swift.
The Obj-c class uses new XCode 7's lightweight generic feature defined as follows:
#interface BaseController<T: SFObject *> : UIViewController
#property(nonatomic, strong) T model;
#end
That works fine for Obj-c subclasses.
Now for Swift if I define the generic type as I usually do I get the following error:
Cannot specialize non-generic type 'BaseObjcController'.
My second class is defined as follows:
class SwiftController: BaseObjcController<SFUser> {
}
Any help would be appreciated.
Thanks.
Update: As of Swift 3, Objective-C lightweight generics are imported into Swift. For more information, see
SE-0057 Importing Objective-C Lightweight Generics
Using Imported Lightweight Generics in Swift
Old answer: From Interacting with Objective-C APIs:
NOTE
Aside from these Foundation collection classes, Objective-C
lightweight generics are ignored by Swift. Any other types using
lightweight generics are imported into Swift as if they were
unparameterized.
So this is not possible at present. As you can see from the
"Generated Interface" assistant view, your Objective-C class is imported
to Swift as
public class BaseController : UIViewController {
public var model: SFObject!
}
This was also discussed in the Apple Developer Forum:
Objective-C generics not visible from Swift?
Feel free to file an enhancement bug report!

Short-circuit when using a Swift protocol

As known, it is not possible to include the interface header file from a file in the -Header.h.
My actual problem is that I have the definition of a class one protocol of which is a Swift one:
#protocol arrivingDelegate;
#interface palettaTraffic : NSObject<MKMapViewDelegate, arrivingDelegate> {
}
If I import the *-Swift.h file I get into the ugly cycle when the file is included in another one that is included in the header file.
This is what happens when I use the #protocol directive: it is a warning, but quite a disturbing one.
This is how the swift protocol is defined:
#objc public protocol arrivingDelegate {
func submitManualBusLine(busStripe:StripeProtocol)
}
I also found a similar post:
Swift protocol in Objective-C class
But none of the suggestions seem to apply.
If I import the *-Swift.h file I get into the ugly cycle when the file is included in another one that is included in the header file.
Okay, but that is what you have to do. I don't see you doing it the screen shot above, which is why your protocol is not being seen.
The solution to the "ugly cycle" should be just a matter of tweaking the order in which things are imported in your various Objective-C files.
Adopting swift protocols in Objective-c is a tricky process. I fixed the issue by porting the adopting class to Swift too.
What I tend to do in my projects is putting the protocol conformance of the ObjC class in a Swift file, to avoid this error. Usually the file where the protocol is defined.
extension PalettaTraffic: ArrivingDelegate {}
Why? We're migrating our codebase from ObjC to Swift, but we cannot migrate every class at the same time. Because of this we have a large 'seem' between Swift & ObjC where Swift types need ObjC and vice versa. For me, this is the solution that causes the least amount of work right away.

Resources