Is it possible to call methods defined in a protocol extension in Swift from Objective-C?
For example:
protocol Product {
var price:Int { get }
var priceString:String { get }
}
extension Product {
var priceString:String {
get {
return "$\(price)"
}
}
}
class IceCream : Product {
var price:Int {
get {
return 2
}
}
}
The price string of an instance of IceCream is '$2' and can be accessed in Swift, however the method is not visible in Objective-C. The compiler throws the error 'No visible #interface for 'IceCream' declares the selector ...'.
In my configuration, if the method is defined directly in the Swift object's implementation, everything works as expected. i.e.:
protocol Product {
var price:Int { get }
var priceString:String { get }
}
class IceCream : Product {
var price:Int {
get {
return 2
}
}
var priceString:String {
get {
return "$\(price)"
}
}
}
I am nearly certain the answer to this is "no", although I haven't found official Apple documentation that spells it out.
Here is a message from the swift-evolution mailing list discussing the proposal to use dynamic dispatch for all method calls, which would provide calling semantics more like Objective-C:
Again, the sole exception to this is protocol extensions. Unlike any other construct in the language, protocol extension methods are dispatched statically in a situation where a virtual dispatch would cause different results. No compiler error prevents this mismatch. (https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001707.html)
Protocol extensions are a Swift-only language feature, and as such are not visible to objc_msgSend().
If it is ok to remove the priceString from the protocol, and only have it in your extension, you can call the protocol extension by casting IceCream to a Product in a helper extension.
#objc protocol Product {
var price:Int { get }
}
extension Product {
var priceString:String {
return "$\(price)"
}
}
// This is the trick
// Helper extension to be able to call protocol extension from obj-c
extension IceCream : Product {
var priceString:String {
return (self as Product).priceString
}
}
#objc class IceCream: NSObject {
var price: Int {
return 2
}
}
Protocol extension does not work with #objc protocol, however you can extend the class in swift as a workaround.
#objc protocol Product {
var price: NSNumber? { get }
var priceString:String { get }
}
...
// IceCream defined in Objective-C that does not extend Product
// but has #property(nonatomic, strong, nullable) (NSNumber*)price
// to make it confirm to Product
...
extension IceCream: Product {
var priceString:String {
get {
return "$\(price ?? "")"
}
}
}
This code is not clean at all, but it works.
Related
I have API request to get json string, something like that:
{
"data":[
{
"id":123,
"name":"Felix"},
{
"id":122,
"name":"Mary"},
{
"id":111,
"name":"Jason"},
]
}
and in my Obj-C NSDictionary+user is
-(NSString *)usrId
{
return self[#"id"];
}
-(NSString *)usrName
{
return self[#"name"];
}
...
and when I use this I import the category class, and access it like dict.name...
I can't find a good answer related to this question. How can I achieve this in Swift?
UPDATED [2017.07.21]
I've chose to use SwiftyJSON library to settle all the json-dictionary-object-mapping works. Save tons of time.
SwiftyJSON
Best and safest way to parse json response to nsdictionary and use it in swift is:
var responseRecord<datastruct> = Array<datastruct>()
struct datastruct
{
var id: String?
var name:String?
init(add: NSDictionary)
{
id = add["id"] as? String
name = add["name"] as? String
}
}
And to access it in program:
print(self.responseRecord.id)
print(self.responseRecord.name)
Simply create a empty Swift class with required properties (Swift 2.2):-
class myClass {
var id:Int?
var name:String?
init(id:Int,name:String)
{
self.id= id
self.name = name
}
}
Then to access in required VC :-
var requiredData:[String]?
var data:[myClass]?
override fun viewDidLoad()
{
super.viewDidLoad()
requiredData = data!.enumerate().map({ (index, category) in
return ("\(category.name!)")
})
print(requiredData)
}
Note: I am assuming you have already populated data in the mentioned "data" variable of myClass type. If not, then you can do so as follows:-
data = [myClass(id:1,name:"aaa"),myClass(id:2,name:"bob"),myClass(id:3,name:"ccc")]
I have a setup like this:
#interface Model: NSManagedObject
...
#end
And a Swift protocol like this:
#objc protocol Syncable {
var uploadURL: String { get }
var uploadParams: [String: AnyObject]? { get }
func updateSyncState() throws
}
extension Syncable where Self: NSManagedObject {
func updateSyncState() throws {
... /* default implementation */ ...
}
}
In a new Swift file, I try to do this:
extension Model: Syncable {
var uploadURL: String {
return "a url"
}
var uploadParams: [String: AnyObject]? {
return [:]
}
}
I keep getting an error, with Xcode saying "type 'Model' does not conform to protocol 'Syncable'". Xcode also keeps suggesting that I put an #objc somewhere in my extension but it can't seem to figure out where it should go.
Is what I'm doing impossible? (It seems to work under simple conditions in a playground - but with my Objective-C class being written in Swift, obviously).
If it is impossible, help in understanding why would be appreciated.
The problem is the attempt to mix Objective-C and Swift features. This pure Swift code compiles just fine (note that I've eliminated NSManagedObject from the story, as it has nothing to do with the issue):
class MyManagedObject {}
class Model: MyManagedObject {}
protocol Syncable {
var uploadURL: String { get }
var uploadParams: [String: AnyObject]? { get }
func updateSyncState()
}
extension Syncable where Self: MyManagedObject {
func updateSyncState() {
}
}
extension Model: Syncable {
var uploadURL: String {
return "a url"
}
var uploadParams: [String: AnyObject]? {
return [:]
}
}
That's because Swift knows what a protocol extension is. But Objective-C doesn't! So as soon as you say #objc protocol you move the protocol into the Objective-C world, and the protocol extension has no effect - and thus Model doesn't conform, as it has no updateSyncState implementation.
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.
I am working on developing an application in Swift. I wanted to design a system for the application that allowed for loose coupling between objects, and one strategy (which I have used successfully in other languages) was to create something I call an instance factory. It is pretty simple and here is the basic implementation I came up with in Swift:
import Foundation
private var typeGenerators = Dictionary<String, InstanceFactory.GeneratorCallback>()
public class InstanceFactory: NSObject {
public typealias GeneratorCallback = () -> AnyObject!
public class func registerGeneratorFor(typeName: String, callback: GeneratorCallback) {
typeGenerators[typeName] = callback
}
public class func instanceOf(typeName: String) -> AnyObject! {
return typeGenerators[typeName]?()
}
}
The idea is that when an object instance needs access to another object instance, rather than creating that instance outright which would more tightly couple the two objects, the first object would defer to the factory to provide the needed instance by calling the instanceOf method. The factory would know how to provide various instance types because those types would register with the factory and provide a closure that could generate the instance.
The trick is how to get the classes to register with the factory. I had previously made a similar factory in Objective-C and the way I got registration to work was to override the +load method for each class that needed to register with the factory. This worked great for Objective-C, and I figured it could work for Swift as well since I would be restricting the factory to only provide objects that are derived from NSObject. It appeared I got this to work and I spent a significant about of effort designing classes to make use of the factory.
However, after upgrading to Xcode 6.3, I discovered Apple has disallowed the usage of the load class method in Swift. Without this, I am unaware of a mechanism to allow classes to automatically register themselves with the factory.
I am wondering if there some other way to get the registration to work.
What alternatives are available that could allow classes to register with the factory, or what other techniques could be use to accomplish the same kind of loose coupling the factory provides?
I've found a possible solution to your problem after I wanted to register all ViewControllers that would be implementing a certain Protocol in my application and I ran into both this question and a possible answer.
The original was posted here: How to list all classes conforming to protocol in Swift?
I adapted it to Swift 3 and made it a bit more Swift-y and generic:
import UIKit
class ContextRoute: NSObject {
}
#objc protocol ContextRoutable {
static var route: ContextRoute { get }
}
class ContextRouter: NSObject {
private static var storedRoutes: [ContextRoute]?
static var routes: [ContextRoute] {
get {
if let storedRoutes = storedRoutes {
return storedRoutes
}
let routables: [ContextRoutable.Type] = classes(implementing: ContextRoutable.self)
let newRoutes = routables.map { routable in routable.route }
storedRoutes = newRoutes
return newRoutes
}
}
private class func classes<T>(implementing objcProtocol: Protocol) -> [T] {
let classes = classList().flatMap { objcClass in objcClass as? T }
return classes
}
private class func classList() -> [AnyObject] {
let expectedClassCount = objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)
var classes = [AnyObject]()
for i in 0 ..< actualClassCount {
if let currentClass: AnyClass = allClasses[Int(i)],
class_conformsToProtocol(currentClass, ContextRoutable.self) {
classes.append(currentClass)
}
}
allClasses.deallocate(capacity: Int(expectedClassCount))
return classes
}
}
I tried it in my application and it works. I clocked it in the simulator and it takes 0.05s for an application that has about 12000 classes.
Consider taking the Swift approach using a protocol instead. I think the solution is actually simpler than the Objective-C approach. There are variations of this with Self constraints which are even better if you have more control over the classes.
// define a protocol to create an instance of a class
protocol FactoryInstantiable {
static func makeFactoryInstance() -> AnyObject
}
// Factory for generating new instances
public class InstanceFactory: NSObject {
public class func instanceOf(typeName: String) -> AnyObject! {
if let ProductType = NSClassFromString(typeName) as? FactoryInstantiable.Type {
return ProductType.makeFactoryInstance()
} else {
return nil
}
}
}
// your class which generally could be defined somewhere else
class MyClass {
var counter : Int
init(counter: Int) {
self.counter = 0
}
}
// extension of your class to conform to the FactoryInstantiable protocol
extension MyClass : FactoryInstantiable {
static func makeFactoryInstance() -> AnyObject {
return MyClass(counter: 0)
}
}
I can´t get the object properties when retrieving an object from Parse Data Browser. This happened after I changed from "#NSManaged var friends" to "dynamic var friends". Even "name" show nil in User.logInWithUsernameInBackground block which is crazy because the login succeeds. The ACL for User is set to "public read".
User object:
class User : PFUser, PFSubclassing {
dynamic var friends:[User]!
dynamic var name:String!
override class func load() {
self.registerSubclass()
}
}
Retrieving the User along with the friends. println showing nil
var query = User.query()
query.includeKey("friends")
query.getObjectInBackgroundWithId(currentUser.objectId) {
(pfObject: PFObject!, error: NSError!) -> Void in
if pfObject != nil {
var user = pfObject as User
var friends = user.friends as [User]
println("friends: \(friends)") //nil
} else {
println(error)
}
}
Login. println showing nil
User.logInWithUsernameInBackground(USERNAME, password:PASSWORD) {
(user: PFUser!, error: NSError!) -> Void in
if user != nil {
println("Logged in with user: \(user.name)") //nil
} else {
println(error)
}
}
Looking a little deeper for you, it seems the hurdle is a misunderstanding of what the dynamic modifier in Swift does. Apparently, dynamic in Swift is used for Key-Value observing, not declaring a variable's accessors to be defined at runtime (what #dynamic does in Objective-C)
See this for a description of dynamic in Swift https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-XID_8
and this for the description of why #NSManaged works the way #dynamic does in Objective-C
https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/BuildingCocoaApps/WritingSwiftClassesWithObjective-CBehavior.html#//apple_ref/doc/uid/TP40014216-CH5-XID_66
With xCode 6.1.1 I was able to get this working without the bridging header but I did need to use #NSManaged. Here's how... Just:
import Parse
at the top of the calling module. For the class declaration .swift file dynamic didn't work so I needed to use #NSManaged for the variable types to get them to link to the Parse class variables successfully. Like this:
class PSCategory : PFObject, PFSubclassing {
override class func load() {
self.registerSubclass()
}
class func parseClassName() -> String! {
return "Category"
}
#NSManaged var Name: String
}
Then in my query all the names are dynamically linked:
var query = PSCategory.query() // PFQuery(className: "Category")
query.cachePolicy = kPFCachePolicyCacheElseNetwork // kPFCachePolicyNetworkElseCache
query.maxCacheAge = 60 * 60 * 24 // One day, in seconds.
query.findObjectsInBackgroundWithBlock {
(categories: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for abstractCategory in categories {
let category = abstractCategory as PSCategory
NSLog("Category Name: %#", category.Name)
}
} else {
NSLog("Unable to retrieve categories from local cache or network")
}
}