Here's my setup:
protocol Client {
var identifier: NSUUID { get }
}
class PeripheralClient: NSObject, Client {
var identifier: NSUUID {
get {
return peripheral.identifier
}
}
}
protocol NetworkManager {
var clients: [Client] { get set }
}
class CentralNetworkManager: NSObject, NetworkManager {
var clients = [Client]()
var peripheralClients: [PeripheralClient] {
get {
return clients as [PeripheralClient]
}
}
}
I get this runtime error when the peripheralClients Array is accessed for the first time: array element cannot be bridged to Objective-C.
From this answer to a question with a similar error, it looks like swift requires the items in an Array to be AnyObject compatible when converting to NSArray. So that means Swift's Array typecasting is using NSArray, thus making it impossible for me to downcast from an Array whose type is a protocol.
Anyone have a good suggestion for getting around this?
Did you try declaring Client as an Objective-C protocol?
#objc protocol Client {
var identifier: NSUUID { get }
}
Yeah, it seems, Swift itself does not have Array<T> to Array<U> casting functionality, it uses Obj-C facility.
Instead, to avoid that, you can cast each elements, using map for example:
var clients = [Client]()
var peripheralClients: [PeripheralClient] {
get {
return clients.map { $0 as PeripheralClient }
}
}
Related
Scenario
I am working on an SDK that will be used in two separate projects. Both projects will be using CoreData and the SDK needs to be fed data that is present in both project's managed object model. To do this I am employing a protocol-oriented design to get the SDK the data it needs.
The project will start out with a NSManagedObject base class that contains it's properties...
// Project
class Tag: NSManagedObject {
var name: String!
var code: String!
var items: [Any]!
...
}
These names are basic and simple. It would be nice to not have to change these when we conform this class to the protocol.
The SDK declares two protocols. The first protocol will be the data source the SDK will need to populate it's views. Some class in the project will conform to this and be the delegate...
// SDK
protocol SDKDataSource {
func getTaggableObjects() -> [Taggable]
}
var delegate: SDKDataSource!
The second protocol will be the ultimatum the SDK will make to it's project that the NSManagedObject it will be fed should conform to...
// SDK
protocol Taggable {
var name: String { get }
var code: String { get }
var items: [Any] { get }
}
In the project I will create an extension of the class and agree to conform that class to the Taggable protocol...
// Extension (in Project w/ SDK included)
extension Tag: Taggable {
var name: String {
get {
self.name
}
}
var code: String {
get {
self.code
}
}
var items: [Any] {
get {
self.items
}
}
}
This way when the SDK asks it's datasource to getTaggableObjects() the SDK will receive the objects from the Project's CoreData model that it can understand.
Question
Does the name of property in the protocol extension have to be different than the name of the property that is in the base class, or will doing the above work? It would be nice if I knew this is okay before I implement my design.
I can confirm that using the same name is not allowed.
I tried and it's yes.
protocol MyArray {
var count: Int {get}
}
extension Array: MyArray {
}
But you can't make different implementations for different protocols that have the same function or property.
Update
If protocol and base class have property with the same name but different type. You can make different implementations.
protocol MyArray {
var count: Float {get}
}
extension Array: MyArray {
var count: Float {
get {
return 0.0
}
}
}
I have a lot of Object subclasses which all of them conforms to some protocol (Transport for ex.) and I don't know which type of objects (Car or Bus for ex.) will be displayed, it depends on some parameters and therefore I want to have some generic computed value which can return Results of Objects based on Type enum value. Can I do that without casting Results to array?
protocol Transport { }
class Car: Object, Transport { }
class Bus: Object, Transport { }
// #1
var array: Array<Transport>? {
return [Car(), Bus()]
}
// #2
var results: Results<Object>? {
if displayCars {
realm?.objects(Car.self)
} else {
realm?.objects(Bus.self)
}
}
№1 will be fine but №2 will not compile because:
Cannot convert return expression of type 'Results<Car>?' to return type 'Results<Object>?'
Update
// Another example.
var objects: Results<Object>?
override func viewDidLoad() {
super.viewDidLoad()
if displayCars {
objects = realm.objects(Car.self)
}
}
Compile error: Cannot assign value of type 'Results<Car>?' to type 'Results<Object>?'
Unfortunately, you will probably have to turn Results<Car> into [Car] in order for this to work. The built-in Swift collections are covariant, but any user-defined Swift generic type (including user-defined collections) is invariant. There's no way right now to specify otherwise.
The following code should explain what I want to do reasonably clearly. Of course, this produces a compile time error.
What is the correct way to do what the below code intends?
protocol FilterableDataSource {
var dataClass: AnyClass { get }
var data: [dataClass.dynamicType] { get }
}
You can't use generics with protocols, but if you try, Swift gives you a hint - associated types. Code rarely explains intent clearly, but I'm guessing what you want to achieve is along the lines of...
protocol FilterableDataSource {
typealias T
var data: [T] { get }
}
class MyData { }
class MyClass: FilterableDataSource {
typealias T = MyData
var data: [MyData] { return [MyData]() }
}
print(MyClass().data.count)
I would use AnyObject instead of AnyClass. Then you can just cast it into whatever you want.
I believe what you want to do is define a typealias requirement in your protocol.
protocol FilterableDataSource
{
typealias DataClass : AnyObject
var data: [DataClass] { get }
}
class PersonDataSource:FilterableDataSource
{
typealias DataClass = Person
var data[Person] = []
}
So instead of requiring your filterable data sources to devine a variable containing the class, you require them to define a typealias for the elements of the data[] variable. Ideally your data classes have a common ancestor other than AnyObject which you can use as the typealias restriction.
I both do, and don't get why the last line of this playground throws a compiler error:
protocol Model { }
struct Post: Model {
var content = "Hello"
}
struct Posts: Model {
var allPosts: [Post] = [Post(), Post(), Post()]
}
func handler(items: [Model]) { }
var posts = Posts()
handler(posts.posts)
If you're reading between the lines, my goal is to be able to invoke a function with an argument that is an array of structs that conform to a protocol. The function should be able to deal with arrays of different types of structs. Would love to know what I'm missing, and if you have a suggestion for a better solution.
Thanks!
It seems to be Swift limitations. But you can do some workaround like this using Generics:
func handler<T: Model>(items: [T]) { }
or else make your protocol a #objc protocol which you can only apply to class type:
#objc protocol Model { }
class Post: Model {
var content = "Hello"
}
I want to typealias a dictionary of String keys and values of objects/structs that implements the Equatable protocol. So I wrote this line of code but it gave me error that I didn't know how to go on to fix.
typealias Storage = [String: Equatable]
I want to use the type [String: Equatable] as a variable in a protocol, e.g:
protocol StorageModel {
var storage: Storage { get set }
init(storage: Storage)
}
Error:
Protocol 'Equatable' can only be used as a generic constraint because
it has Self or associated type requirements
Can anyone suggest a solution?
Generally speaking, the protocol tag isn't required, protocol names are first-class type names and can be used directly:
typealias Storage = [String:Equatable]
In this case, what the error is telling you is that because Equatable includes func == (lhs:Self, rhs:Self) -> Bool and specifically lhs:Self, Equatable can't be used except as a constraint on a generic:
class Generic<T:Equatable> { ... }
Without more details about what you're trying to achieve and how you're trying to use StorageModel, the best I can come up with is:
protocol Matches {
typealias T
func matches(t:T) -> Bool
}
protocol StorageModel {
typealias T
var storage: [String:T] { get set }
init(storage:[String:T])
}
extension Int : Matches {
func matches(target:Int) -> Bool {
return self == target
}
}
class MyClass <T:Matches> {
var model = [String:T]()
}
Another possibility is to use a generic instead of a protocol:
class StorageModel <T:Equatable> {
var storage: [String:T]
init(storage:[String:T]) {
self.storage = storage
}
}
From there you'll need to do some research, dig into the Swift manual, do some googling and see what solves your problem.