Deprecation warning for Objective-C dependencies - objective-c

Making Swift var backwards compatible with Objective-C
I have an Objective-C class I've converted to Swift. All my tests pass, but I'd like to further optimize it by adding a deprecation warning to update to notify users to update downstream dependencies to the Swift version of the var (Decimal) if they can. Whether they can depends upon whether the class they're using is an Objective-C class (which can only "see" NSDecimalNumber) or a Swift class. Is there a way to do this? This is what I've got so far.
#available(swift, introduced: 5.0)
public var mySwiftDecimal: Decimal?
#available(*, deprecated, renamed: "mySwiftDecimal")
public var myObjCDecimal: NSDecimalNumber? {
get {
return mySwiftDecimal as NSDecimalNumber?
} set {
mySwiftDecimal = newValue as Decimal?
}
}

You can annotate the member as deprecated in Swift:
#available(swift, deprecated: 5.0, renamed: "mySwiftDecimal")
#objc public var myObjCDecimal: NSDecimalNumber? {
get {
return mySwiftDecimal as NSDecimalNumber?
} set {
mySwiftDecimal = newValue as Decimal?
}
}
Then using it from Swift gives a warning:
let foo = Foo()
print(foo.myObjCDecimal)
// 'myObjCDecimal' is deprecated: renamed to 'mySwiftDecimal'
but using it from Objective-C does not:
Foo *foo = [[Foo alloc] init];
NSDecimalNumber *dec = foo.myObjCDecimal;

Related

Invalid redeclaration of rawValue in Release build

I have a mixed project and came across an interesting issue.
There's an enum, defined in obj-c
typedef NS_ENUM (NSUInteger, ABCCategory) {
ABCCategoryFirst,
ABCCategorySecond
};
Next, there's a swift file where an extension is defined
extension ABCCategory: RawRepresentable {
public typealias RawValue = String
public init(rawValue: RawValue) {
switch rawValue {
case "first":
self = .first
case "second":
self = .second
default:
self = .first
}
}
public var rawValue: RawValue {
get {
switch self {
case .first:
return "first"
case .second:
return "second"
}
}
}
}
Everything works fine in the Debug configuration, but when I switch to Release it does not build, saying: Invalid redeclaration of 'rawValue'
I've tried removing typealias, replacing RawValue with String (so the protocol could implicitly guess the value), making constructor optional as in the protocol (and implicitly unwrapped optional also) - no go.
I do understand that extending an Int enum with string is a bit weird, but why it stops building in Release and working absolutely perfect in Debug?
Is there some different mechanism of treating enums/classes/extensions for Release configuration?
The raw value syntax for enums in Swift is “just” a shorthand for conformance to the RawRepresentable protocol. It’s easy to add this manually if you want to use otherwise unsupported types as raw values.
Source
I'm not sure why it works in debug because when you create a typed enum you are already 'conforming' to RawRepresentable. So when you create an NS_ENUM it is imported in to swift like so:
public enum ABCCategory : UInt {
case first
case second
}
Meaning that it already conforms to RawRepresentable. The fix can be achieved two ways, one in Swift and in Objective-C
In Swift we just remove the RawRepresentable and change rawValue to stringValue, and RawValue to String:
extension ABCCategory {
var stringValue: String {
switch self {
case .first: return "first"
case .second: return "second"
}
}
init(_ value: String) {
switch value {
case "first":
self = .first
case "second":
self = .second
default:
self = .first
}
}
}
Or you could just change the Objective-C to use NS_TYPED_ENUM. Some info here. However this will change your enum to a struct
.h
typedef NSString *ABCCategory NS_TYPED_ENUM;
extern ABCCategory const ABCCategoryFirst;
extern ABCCategory const ABCCategorySecond;
.m
ABCCategory const ABCCategoryFirst = #"first";
ABCCategory const ABCCategorySecond = #"second";
This will be imported by swift like so:
public struct ABCCategory : Hashable, Equatable, RawRepresentable {
public init(rawValue: String)
}
public static let first: ABCCategory
public static let second: ABCCategory

Using Swift Class and Enum from Objective-C

I've included a set of swift classes and their swift dependencies into my Objective-C project. I've already done this for other swift libraries, so things like the Obj-C Generated Interface Header are already present.
This is the class I wish to use:
#objc public class StatusBarNotificationBanner: BaseNotificationBanner
{
override init(style: BannerStyle) {
super.init(style: style)
bannerHeight = 20.0
titleLabel = MarqueeLabel()
titleLabel?.animationDelay = 2
titleLabel?.type = .leftRight
titleLabel!.font = UIFont.systemFont(ofSize: 12.5, weight: UIFontWeightBold)
titleLabel!.textAlignment = .center
titleLabel!.textColor = .white
addSubview(titleLabel!)
titleLabel!.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.left.equalToSuperview().offset(5)
make.right.equalToSuperview().offset(-5)
make.bottom.equalToSuperview()
}
updateMarqueeLabelsDurations()
}
public convenience init(title: String, style: BannerStyle = .info) {
self.init(style: style)
titleLabel!.text = title
}
public convenience init(attributedTitle: NSAttributedString, style: BannerStyle = .info) {
self.init(style: style)
titleLabel!.attributedText = attributedTitle
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
This is how one would use the class in swift:
let banner = StatusBarNotificationBanner(title: title, style: .success)
banner.show()
How in Obj-C would I instantiate StatusBarNotificationBanner and call its show() method?
Also, how do I pass the enum parameter style?
This is the enum:
public enum BannerStyle {
case danger
case info
case none
case success
case warning
}
I guess that the enum needs to take the form:
#objc public enum BannerStyle: Int {
case danger
case info
case none
case success
case warning
}
But I still don't know how to pass it as a param in Obj-C and I'm not understanding why the Int has to be specified? Isn't the enum implicitly Int?
Isn't the enum implicitly Int?
Not really. Objective-C cannot see a Swift enum at all. In Swift, an enum is an object type. Objective-C has no knowledge whatever of any such object type; its only objects are classes. (In Objective-C, enums are just numbers with names.) Therefore, neither a Swift enum type, nor a method that takes or produces a Swift enum, nor a Swift enum property, is exposed to Objective-C
However, in the special case where you say #objc enum BannerStyle: Int, it is translated into an Objective-C enum for you. So, in Objective-C, names such as BannerStyleDanger and BannerStyleInfo will spring to life. But they will just be integers.

UIActivityType – Property cannot be an #objc override because its type cannot be represented in Objective-C

While updating to Xcode 8 Beta 6, from what I saw a new type got introduced: UIActivityType
So I tried to do somewhere like this in my UIActivity custom class:
class FooActivity: UIActivity {
func retrieveActivityType() -> String {
return "someStringDescribingActivityType"
}
override open var activityType: UIActivityType? {
#objc(retrieveActivityType)
get {
return UIActivityType(rawValue: "someStringDescribingActivityType")
}
}
}
where retrieveActivityType() is the Objective-C equivalent since UIActivityType is only defined in Swift. But no luck so far, still having two errors:
Property cannot be an #objc override because its type cannot be represented in Objective-C
'#objc' getter for non-'#objc' property
Is there something obvious that I'm missing?
Found a quick fix by just making the return type as non-optional.
I guess there is no real workaround until beta 7 gets released.
class FooActivity: UIActivity {
override open var activityType: UIActivityType {
get {
return UIActivityType(rawValue: "someStringDescribingActivityType")
}
}
Sources:
https://bugs.swift.org/browse/SR-2344
https://github.com/apple/swift/pull/4360

Call generic function from non-generic function in Swift

MMCondition is a protocol defined in Swift, but interoperates with Objective-C (annotated with #objc).
#objc public protocol MMCondition {
static var name: String { get }
static var isMutuallyExclusive: Bool { get }
}
I have the following code:
// addCondition cannot be generic as I want it to be accessible from Objective-C as well.
public func addCondition(condition: MMCondition) {
// How do I initialize OperationConditionImplementer here?
let operationCondition = OperationConditionImplementer(condition: condition) // doesn't compile
// Error: Cannot invoke initializer for type 'OperationConditionImplementer<T>' with an argument list of type '(condition: MMCondition)'
// Can I use condition.dynamicType to init OperationConditionImplementer somehow?
}
struct OperationConditionImplementer<T: MMCondition> {
let condition: T
static var name: String {
return "Silent<\(T.name)>"
}
static var isMutuallyExclusive: Bool {
return T.isMutuallyExclusive
}
init(condition: T) {
self.condition = condition
}
}
From Objective-C, you can't use generics as stated in the documentation.
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics
...
So you need to remove completely the generics code. One possible solution might be:
#objc protocol MMCondition {
static var name: String { get }
static var isMutuallyExclusive: Bool { get }
}
struct OperationConditionImplementer {
let condition: MMCondition
var name: String {
return "Silent<\(condition.dynamicType.name)>"
}
var isMutuallyExclusive: Bool {
return condition.dynamicType.isMutuallyExclusive
}
init(condition: MMCondition) {
self.condition = condition
// Here decide comparing types
if condition.dynamicType === ExampleCondition.self {
print(condition.dynamicType.name)
}
}
}
So for instance, if you try it out in a playground:
class ExampleCondition: NSObject, MMCondition {
static var name: String = "ExampleCondition"
static var isMutuallyExclusive: Bool = false
}
let example = OperationConditionImplementer(condition: ExampleCondition())
You'll see "ExampleCondition" printed.
If you eventually switch to pure Swift, you need to specify T when initializing a OperationConditionImplementer.
You can achieve that defining the addCondition method as:
func addCondition<T: MMCondition>(condition: T) {
let a = OperationConditionImplementer<T>(condition: condition)
}
Since Swift 2.0 instances of generic classes can implement Objective-C protocols. What won't be possible I believe is having a struct implement the protocol. In fact I expect that your protocol may need to inherit from NSObjectProtocol to be usable in Objective-C which would then prevent you from implementing the protocol with structs or enums.
You also rightly mention that you can't access generic functions from Objective-C.
For a concrete example of using a generic to fulfil an Objective-C protocol have a look at this blog post.

Objective-C calling parameterized Swift method crashes Swift compiler

I have a simple Swift extension on NSManagedObject, in which I have a parametrized method for finding a single object - the signature looks like:
public class func findFirst<T:NSManagedObject>(inContext context : NSManagedObjectContext? = .None) -> T?
I'm trying to call this from Objective-C, but it seems like it cannot be seen. If I create a non-parameterized version I can see and call it just fine from Objective-C:
public class func findFirstUntypedWithPredicate(predicate:NSPredicate?, inContext context : NSManagedObjectContext? = .None) -> NSManagedObject?
Is there any way for ObjectiveC to be able to reach the parameterized version of the call?
I would use Self like so:
public class func findFirst(inContext context : NSManagedObjectContext? = .None) -> Self?
using the technique found here:
How can I create instances of managed object subclasses in a NSManagedObject Swift extension?
However, that causes the Swift compiler to segfault when compiling the code (Xcode 6.3.1, or Xcode 6.4 beta 2).
Edit: Here's a link with the full source of the framework I'm trying to build, including bonus Swift compiler crashes caused by templated methods:
https://www.dropbox.com/s/fixaj9ygdoi4arp/KiGiCoreData.zip?dl=0
Generic methods are not visible from Objective-C. However you can use
the ideas from How to use generic types to get object with same type to define a findFirst() class method
which returns Self? (the Swift equivalent of instancetype) without
being generic:
// Used to cast `AnyObject?` to `Self?`, `T` is inferred from the context.
func objcast<T>(obj: AnyObject?) -> T? {
return obj as! T?
}
extension NSManagedObject
{
class func entityName() -> String {
let classString = NSStringFromClass(self)
// The entity is the last component of dot-separated class name:
let components = split(classString) { $0 == "." }
return components.last ?? classString
}
// Return any matching object, or `nil` if none exists or an error occurred
class func findFirst(context : NSManagedObjectContext, withPredicate pred : NSPredicate?) -> Self? {
let name = entityName()
let request = NSFetchRequest(entityName: name)
request.predicate = pred
var error : NSError?
let result = context.executeFetchRequest(request, error: &error)
if let objects = result {
return objcast(objects.first)
} else {
println("Fetch failed: \(error?.localizedDescription)")
return nil
}
}
}
This can be used from Swift
if let obj = YourEntity.findFirst(context, withPredicate: nil) {
// found
} else {
// not found
}
and from Objective-C:
YourEntity *obj = [YourEntity findFirst:context withPredicate:nil];