Extending generic objective c class in swift class - ios

So I am slowly adding more swift files to codebase.
Suppose I have class in Objective c:
ListData<Type : CSJSONData *> : ServerData <CSListData>
I want to subclass it in swift class like this:
#objc class ArticlesData: ListData<Article>
Compiler gives me error:
Generic subclasses of '#objc' classes cannot have an explicit '#objc' because they are not directly visible from Objective-C
Replace '#objc ' with ''
But I need to have it #objc to keep available in my objc part of code ! How to do it ?

From The Swift Programming Language (4.2), Attributes:
The compiler implicitly adds the objc attribute to subclasses of any class defined in Objective-C. However, the subclass must not be generic, and must not inherit from any generic classes. You can explicitly add the objc attribute to a subclass that meets these criteria, to specify its Objective-C name as discussed below.
Unfortunately for you this is pretty definitive.

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.

Category for obj-c class derived from swift

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.

Any way to use a plain Swift class in Objective C files?

This question isn't about bridging headers and all that. I've got all that working. My question is specifically about what I need to do to my Swift classes to get them to show up in Obj-C code.
Take, for example, this simple class:
class MyClass {
var value: String = ""
}
If I have this class in my Project, it doesn't get included in the MyProject-Swift.h file that gets auto-generated. My understanding is that in order to use a Swift class in Objective-C, my class needs to derive from a class that Objective-C knows about. This is where I start to doubt the actual requirements.
If my class were derived from a UIViewController, then no problem. But if this is just a model object, then it's not deriving from anything. While it is entirely possible to easily make my class derive from NSObject, and thus, it gets properly imported into the Obj-C code, deriving from NSObject can cause other issues down the road.
So if I don't want to make my class derive from NSObject, what can I do to make it visible to my Obj-C files? Is there a doc I just couldn't find that explains how to do this?
As far as I am aware currently, Only Swift classes that inherit from NSObject can be declared #objc and bridged into an Objective-C project.
Without that conformance/inheritance, you'll end up missing some crucial functionality to Objective-C like message sending.
All of that being said, an Objective-C class has to inherit from a parent class and the default root class is NSObject. You almost definitely want to just inherit and make your class a PONSO.

When #objc and #nonobjc write before method and variable in swift?

When I declare static parameter in extension of class then I have to write #nonobjc before variable like:
#nonobjc static let test = "test"
and sometimes I have to write #objc before method, so what is use of #objc and #nonobjc in Swift.
Can anyone help me for this problem?
This is explained in the Apple's official documentation about Objective-C - Swift interoperability:
When you use the #objc(name) attribute on a Swift class, the class is
made available in Objective-C without any namespacing. As a result,
this attribute can also be useful when migrating an archivable
Objective-C class to Swift. Because archived objects store the name of
their class in the archive, you should use the #objc(name) attribute
to specify the same name as your Objective-C class so that older
archives can be unarchived by your new Swift class.
Conversely, Swift also provides the #nonobjc attribute, which makes a
Swift declaration unavailable in Objective-C. You can use it to
resolve circularity for bridging methods and to allow overloading of
methods for classes imported by Objective-C. If an Objective-C method
is overridden by a Swift method that cannot be represented in
Objective-C, such as by specifying a parameter to be a variable, that
method must be marked #nonobjc.
To summarize, use #objc when you want to expose a Swift attribute to Objective-C without a namespace . Use #nonobjc if you want to keep the attribute available and accessible only in Swift code.
(Addendum/additional official details to #bontoJR well summarizing answer)
From the Swift Language Reference - Attributes [emphasis mine]:
objc
Apply this attribute to any declaration that can be represented in
Objective-C — for example, non-nested classes, protocols, nongeneric
enumerations (constrained to integer raw-value types), properties and
methods (including getters and setters) of classes and protocols,
initializers, deinitializers, and subscripts. The objc attribute tells
the compiler that a declaration is available to use in Objective-C
code.
...
nonobjc
Apply this attribute to a method, property, subscript, or initializer
declaration to suppress an implicit objc attribute. The nonobjc
attribute tells the compiler to make the declaration unavailable in
Objective-C code, even though it is possible to represent it in
Objective-C.
...
Here you can find more details in this Swift Documentation : InteractingWithObjective-C
As an answer of your question, overview from attached link is as below.
#objc : You can use attribute to change the name of a class, property, method, enumeration type, or enumeration case declaration in
your interface as it’s exposed to Objective-C code.
Example : if the name of your Swift class contains a character that isn’t supported by Objective-C, you can provide an alternative name to use in Objective-C.
#nonobjc : It makes a swift declaration unavailable in Objective-C. You can use it to resolve circularity for bridging
methods and to allow overloading of methods for classes imported by
Objective-C.

Objective-C call Swift function

Swift function defined in MySwift.swift File:
func SomeSwift()
{
}
SomeSwift() is not defined in any Swift class, it is just a pure function.
After CMD + B to build the project, open Project-Swift.h, the SomeSwift() isn't show in there.
Does the function in Swift have to be defined in some Swift class? and with #objc marked?
like the following:
#objc class SomeSwift: NSObject {
func SomeSwift()
{
}
}
Referring to Apple Documentation about Using Swift from Objective-C:
A Swift class must be a descendant of an Objective-C class to be
accessible and usable in Objective-C
Means that your class should be #objc class SomeSwift: NSObject (You're right!), but you CANNOT access the whole thing in Swift file:
When you create a Swift class that descends from an Objective-C class,
the class and its members—properties, methods, subscripts, and
initializers—that are compatible with Objective-C are automatically
available from Objective-C. This excludes Swift-only features, such as
those listed here:
Generics
Tuples
Enumerations defined in Swift without Int raw value type
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
Reference.
So, you cannot use the SomeSwift top-level function.
Even if you tried to add #objc before its declaration, the compiler will tell that:
#objc can only used when with memebers of classes, #objc protocols,
and concrete extensions of classes.
with a suggestion to remove #objc.

Resources