Make a swift protocol conform to Hashable - ios

I'm going around in circles trying to get Hashable to work with multiple struct that conform to the same protocol.
I have a protocol SomeLocation declared like this:
protocol SomeLocation {
var name:String { get }
var coordinates:Coordinate { get }
}
Then I create multiple objects that contain similar data like this:
struct ShopLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
init(from decoder: Decoder) throws {
...
}
}
struct CarLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
init(from decoder: Decoder) throws {
...
}
}
I can later use these in the same array by declaring:
let locations: [SomeLocation]
The problem is, I create an MKAnnotation subclass and need to use a custom Hashable on the SomeLocation objects.
final class LocationAnnotation:NSObject, MKAnnotation {
let location:SomeLocation
init(location:SomeLocation) {
self.location = location
super.init()
}
}
override var hash: Int {
return location.hashValue
}
override func isEqual(_ object: Any?) -> Bool {
if let annot = object as? LocationAnnotation
{
let isEqual = (annot.location == location)
return isEqual
}
return false
}
This gives me 2 errors:
Value of type 'SomeLocation' has no member 'hashValue' Binary operator
'==' cannot be applied to two 'SomeLocation' operands
So I add the Hashable protocol to my SomeLocation protocol:
protocol SomeLocation: Hashable {
...
}
This removes the first error of hashValue not being available, but now I get an error where I declared let location:SomeLocation saying
Protocol 'SomeLocation' can only be used as a generic constraint because it has Self or associated type requirements
So it doesn't look like I can add Hashable to the protocol.
I can add Hashable directly to each struct that implements the SomeLocation protocol, however that means I need to use code like this and keep updating it every time I might make another object that conforms to the SomeLocation protocol.
override var hash: Int {
if let location = location as? ShopLocation
{
return location.hashValue
}
return self.hashValue
}
I have tried another way, by making a SomeLocationRepresentable struct:
struct SomeLocationRepresentable {
private let wrapped: SomeLocation
init<T:SomeLocation>(with:T) {
wrapped = with
}
}
extension SomeLocationRepresentable: SomeLocation, Hashable {
var name: String {
wrapped.name
}
var coordinates: Coordinate {
wrapped.coordinates
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(coordinates)
}
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.name == rhs.name && lhs.coordinates == rhs.coordinates
}
}
however when I try to use this in the LocationAnnotation class like
let location: SomeLocationRepresentable
init(location:SomeLocation) {
self.location = SomeLocationRepresentable(with: location)
super.init()
}
I get an error
Value of protocol type 'SomeLocation' cannot conform to 'SomeLocation'; only struct/enum/class types can conform to protocols
Is it possible to achieve what I am trying to do? Use objects that all conform to a protocol and use a custom Hashable to compare one to the other?

Deriving the protocol from Hashable and using a type eraser might help here:
protocol SomeLocation: Hashable {
var name: String { get }
var coordinates: Coordinate { get }
}
struct AnyLocation: SomeLocation {
let name: String
let coordinates: Coordinate
init<L: SomeLocation>(_ location: L) {
name = location.name
coordinates = location.coordinates
}
}
You then can simply declare the protocol conformance on the structs, and if Coordinate is already Hashable, then you don't need to write any extra hashing code code, since the compiler can automatically synthesize for you (and so will do for new types as long as all their properties are Hashable:
struct ShopLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
struct CarLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
If Coordinate is also Codable, then you also can omit writing any code for the encoding/decoding operations, the compile will synthesize the required methods (provided all other properties are already Codable).
You can then use the eraser within the annotation class by forwardingn the initializer constraints:
final class LocationAnnotation: NSObject, MKAnnotation {
let location: AnyLocation
init<L: SomeLocation>(location: L) {
self.location = AnyLocation(location)
super.init()
}
override var hash: Int {
location.hashValue
}
override func isEqual(_ object: Any?) -> Bool {
(object as? LocationAnnotation)?.location == location
}
}

Related

Confirm to Equatable for Custom struct

I have below kind of response model, where the body is decided by another variable. How can i confirm equatable to this Model
public struct Model {
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType {
var items: [String]?
}
struct Response2: ResponseType {
var item: String?
}
What i want to achive:
extension Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
// How to equate the body?
}
}
When im trying to add Equatable to ResponseType protocol it says below error.
Protocol 'ResponseType' can only be used as a generic constraint because it has Self or associated type requirements
You need to implement == manually. swift doesn't know that body can only be two types, which are?
public struct Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
if lhs.type != rhs.type {
return false
}
if let lhsBody = lhs.body as? Response1, let rhsBody = rhs.body as? Response1 {
return lhsBody == rhsBody
} else if let lhsBody = lhs.body as? Response2, let rhsBody = rhs.body as? Response2 {
return lhsBody == rhsBody
} else {
return false
}
}
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType, Equatable {
var items: [String]?
}
struct Response2: ResponseType, Equatable {
var item: String?
}
It might be easier if you change Model into an enum:
enum Model: Codable, Equatable {
case type1(items: [String]?)
case type2(item: String)
var type: String {
switch self {
case .type1: return "type1"
case .type2: return "type2"
}
}
}
You probably need to change the Codable implementation so that it encodes and decodes the way you want to.

Swift - Making a type Hashable that has instance variable of protocol type

How to make a struct/class confirm to H.ashable/Equatable that has an instance variable of a protocol type. Let me provide an example of this:
protocol Filter {
associatedtype Input
func satisfies(input: Input) -> Bool
}
protocol FilterProcessor {
func satisfies(input: String) -> Bool
}
struct SelectionFilter: Filter {
let identifier: String
let processor: FilterProcessor
func satisfies(input: String) -> Bool {
return processor.satisfies(input: input)
}
}
struct ProcessorOne: FilterProcessor {
let uuid: String
let type: String
func satisfies(input: String) -> Bool {
// Some logic based on uuid and type
return false
}
}
struct ProcessorTwo: FilterProcessor {
let data: Int
func satisfies(input: String) -> Bool {
// Some logic based on data
return false
}
}
This works fine as long as you don't need Hashable/Equatable confirmance on SelectionFilter but things quickly get very complicated as soon as you do, like try to make a Set of SelectionFilter:
Set([
SelectionFilter(identifier: "abc", processor: ProcessorOne(uuid: "1234", type: "size")),
SelectionFilter(identifier: "def", processor: ProcessorTwo(data: 1234))
])
For the above to work SelectionFilter needs to be Hashable. We can do that taking only identifier into account but that would be wrong as actual hashing/equality depends upon the processor's data also. For that FilterProcessor needs to be Hashable and as soon as we do that the SelectionFilter can't use the FilterProcessor directly as it is now a generic protocol.
One solution is to use a type eraser for the FilterProcessor as AnyFilterProcessor, but then this AnyFilterProcessor needs to confirm Hashable. How do you do that since all this AnyFilterProcessor will have is a closure that points back to the actual satisfies method.
So there are two alternatives I could make one is to make SelectionFilter an enum and the other is to eliminate the FilterProcessor and make SelectionFilter a class and other subclasses:
Option1:
enumm SelectionFilter: Filter, Hashable {
case filterOne(identifier: String, processor: ProcessorOne)
case filterTwo(identifier: String, processor: ProcessorTwo)
}
extension ProcessorOne: Hashable {}
extension ProcessorTwo: Hashable {}
This works neatly but the problem is that enums are not scalable. We have some 20 odd processors, which means 20 cases and for doing anything we need to switch all 20 cases.
Option2:
class SelectionFilter: Filter, Hashable {
let identifier: String
init(identifier: String) {
self.identifier = identifier
}
func satisfies(input: String) -> Bool {
return true
}
static func == (lhs: SelFilter, rhs: SelFilter) -> Bool {
lhs.identifier == rhs.identifier
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
}
class FilterOne: SelectionFilter {
let uuid: String
let type: String
init(identifier: String, uuid: String, type: String) {
self.uuid = uuid
self.type = type
super.init(identifier: identifier)
}
override func satisfies(input: String) -> Bool {
return true
}
}
But the issue here is a lot of duplicate code as each SelectionFilter subclass will need to implement == and hash functions. Also, it is error-prone as any developer might forget to implement these functions in a subclass.
Both of these options work but I am really not liking them. Is there is another way to do it?

iOS - Error with Protocols and generics when used as a dependency

I have a protocol ContainerService whose sessionService object is used in our network layer. I'm trying to inject the sessionService into an object that encapsulates the network layer. I used a base protocol to avoid the error
Protocol `SessionService` can only be used as a generic constraint because it has Self or associated type requirements
This issue was fixed by adding
var sessionService: SessionServiceBase { get set }
as a requirement for ContainerService
However when I try to use sessionService as a property of TestClass, I come across the same error. Also I ran into another error
Member connectSession cannot be used on value of Protocol typeSessionService; use a generic constraint instead
Here is a sample code
protocol NetworkService: AnyObject {
var sessionService: SessionServiceBase { get set }
}
protocol SessionServiceBase: AnyObject { }
protocol SessionService: SessionServiceBase {
associatedtype T: Tokenable
func connection(credential: T)
}
protocol Tokenable {
var token: String { get set }
var key: String { get set }
}
struct CustomToken: Tokenable {
var token: String
var secretKey: String
}
class CustomSessionService: SessionService {
func connection(credential: CustomToken) {
print(credential.token)
}
}
class CustomNetworkService: NetworkService {
var sessionService: SessionServiceBase = CustomSessionService()
}
class ConsumerClass {
var networkService: NetworkService?
init(networkService: NetworkService) {
self.networkService = networkService
}
func test(){
let tokens = CustomToken(token: "", key: "")
guard let sessionService: SessionService = networkService?.sessionService as? SessionService else {
return
}
sessionService.connection(credential: tokens)
}
}
Is there a work around this generic error issue? If this code is run on a playground there will be two error inside test func.
1.
Protocol SessionService can only be used as a generic constraint because it has Self or associated type requirements
2.
Member `connection` cannot be used on value of protocol type `SessionService`; use a generic constraint instead.
The SessionService protocol can only be used a generic type constraint because the associated type implies that there is not one specific type of SessionService but any number, analogous to how there is not one Array type in Swift. See my answer here for some more details about this.
For instance, we could not guarantee that the sessionService instance actually connected with a CustomToken because its associated type is not specified. Hence, Swift does not allow it.
There are some alternatives though. At some point you have to make something concrete. To implement what you may be looking for with generics, observe the following:
// Protocols
protocol SessionService: AnyObject {
associatedtype T: Tokenable
func connection(credential: T)
}
protocol NetworkService: AnyObject {
associatedtype Service: SessionService
var sessionService: Service { get set }
}
protocol Tokenable {
var token: String { get set }
var key: String { get set }
// Provide an initializer to require that a token can be made in this way
init(token: String, key: String)
}
// Custom types
class CustomSessionService: SessionService {
func connection(credential: CustomToken) {
print(credential.token)
print(credential.doSomething())
}
}
class CustomNetworkService: NetworkService {
var sessionService: CustomSessionService = CustomSessionService()
}
struct CustomToken: Tokenable {
var token: String
var key: String
var secretKey: String = ""
init(token: String, key: String) {
self.token = token
self.key = key
}
var myStr: String = "aaa"
func doSomething() -> String { myStr }
}
// Type erasure
class AnyNetworkService<ServiceType>: NetworkService where ServiceType: SessionService {
typealias Service = ServiceType
var sessionService: ServiceType
init(service: ServiceType) { self.sessionService = service }
convenience init<T: NetworkService>(networkService: T) where T.Service == ServiceType { self.init(service: networkService.sessionService) }
}
// Combines everything
class ConsumerClass<Service: SessionService> {
var networkService: AnyNetworkService<Service>?
init(networkService: AnyNetworkService<Service>) {
self.networkService = networkService
}
func test() {
let tokens = Service.T(token: "asd", key: "fff") // create a token for the specific Service passed in
guard let sessionService = networkService?.sessionService else { return }
sessionService.connection(credential: tokens)
}
}
// Example
let networkService = CustomNetworkService()
let consumer = ConsumerClass<CustomSessionService>(networkService: .init(networkService: networkService))
consumer.test()
The last line will call connection(credential:) in CustomSessionService.
The only potential drawback is that there is no longer a single ConsumerClass but instead many. Also, a Token created in test() cannot have its other properties assigned for the its type is not known to you. However, given its flexibility for using different Network and SessionService concrete types, it may be of use.

Swift 2 - Protocol conforming to Equatable issue

I have an issue with a protocol I've defined below. I've got two requirements:
I'd like to be able to use the protocol Peer as a type in other classes while keeping the concrete class private.
I'd like to store the protocol in an array and be able to determine the index of an instance.
In order to satisfy the second point, I need to make the protocol conform to the Equatable protocol. But when I do that, I can no longer use Peer as a type, since it needs to be treated as a generic. This means I cannot have the concrete implementation private anymore, and requirement 1 is broken.
Wondering if anyone else has encountered this problem and gotten around it somehow. Maybe I'm misinterpreting the error I'm getting at indexOf...
Group.swift
import Foundation
class Group {
var peers = [Peer]()
init() {
peers.append(PeerFactory.buildPeer("Buddy"))
}
func findPeer(peer: Peer) -> Bool {
if let index = peers.indexOf(peer) {
return true
}
return false
}
}
Peer.swift
import Foundation
protocol Peer {
var name: String { get }
}
class PeerFactory {
static func buildPeer(name: String) -> Peer {
return SimplePeer(name: name)
}
}
private class SimplePeer: Peer {
let name: String
init(name: String) {
self.name = name
}
}
Error at indexOf if Peer is not Equatable:
cannot convert value of type 'Peer' to expected argument type '#noescape (Peer) throws -> Bool'
So I found a solution to get around the Equatable requirement by extending CollectionType to define a new indexOf for elements are of Peer type, which takes advantage of the other closure-based indexOf. This is essentially a convenience function which saves me from using the closure indexOf directly. Code below:
extension CollectionType where Generator.Element == Peer {
func indexOf(element: Generator.Element) -> Index? {
return indexOf({ $0.name == element.name })
}
}
This of course assumes everything I need to test equality can be obtained from the Peer protocol (which is true for my specific use case).
EDIT: Update for Swift 3:
extension Collection where Iterator.Element == Peer {
func indexOf(element: Iterator.Element) -> Index? {
return index(where: { $0.name == element.name })
}
}
I would suggest you use public super class, so the class can conform to Equatable
class Peer: Equatable {
// Read-only computed property so you can override.
// If no need to override, you can simply declare a stored property
var name: String {
get {
fatalError("Should not call Base")
}
}
// should only be called from subclass
private init() {}
}
private class SimplePeer: Peer {
override var name: String {
get {
return _name
}
}
let _name: String
init(name: String) {
_name = name
super.init()
}
}
func == (lhs: Peer, rhs: Peer) -> Bool {
return lhs.name == rhs.name
}
class PeerFactory {
static func buildPeer(name: String) -> Peer {
return SimplePeer(name: name)
}
}

Find delegate in a swift Array of delegates

I want to check if I already have a delegate in my removeDelegate method before removing.
How do I do that?
Here's what I've got so far:
protocol LocationManagerDelegate {
func locationManagerDidUpdateLocation(
oldLocation: CLLocationCoordinate2D,
currentLocation: CLLocationCoordinate2D
)
}
class LocationManager: NSObject {
private var _delegates = [LocationManagerDelegate]()
func removeDelegate(delegate:LocationManagerDelegate) {
if contains(_delegates, delegate) {
// Remove delegate
}
}
}
However, this gives me the following error on the 'if contains' line:
cannot invoke 'contains' with an argument list of type '(#lvalue Array< LocationManagerDelegate >!, LocationManagerDelegate)'
Update for Swift 4.2:
Assuming that the delegates are actually instances of a class, you could require that in the protocol by "inheriting" from "class":
protocol LocationManagerDelegate: class {
// ...
}
and then use the firstIndex(where:) method, using the "identity operator
===:
class LocationManager: NSObject {
private var _delegates = [LocationManagerDelegate]()
func removeDelegate(delegate:LocationManagerDelegate) {
if let index = _delegates.firstIndex(where: { $0 === delegate }) {
_delegates.remove(at: index)
}
}
}
Old answer (Swift 1):
There are two slightly different contains() functions:
func contains<S : SequenceType where S.Generator.Element : Equatable>(seq: S, x: S.Generator.Element) -> Bool
func contains<S : SequenceType, L : BooleanType>(seq: S, predicate: (S.Generator.Element) -> L) -> Bool
You are using the first one, which requires that the sequence elements conform to
the Equatable protocol, i.e. they can be compared with ==.
Assuming that the delegates are actually instances of a class, you could require
that in the protocol by "inheriting" from "class":
protocol LocationManagerDelegate : class {
// ...
}
and then use the second, predicate-based version of contains() with the
identity operator ===:
func removeDelegate(delegate:LocationManagerDelegate) {
if contains(_delegates, { $0 === delegate }) {
// Remove delegate
}
}
To remove the object from the array you'll have to get its index, so you might use
the findIdenticalObject() function from https://stackoverflow.com/a/25543084/1187415:
func findIdenticalObject<T : AnyObject>(array: [T], value: T) -> Int? {
for (index, elem) in enumerate(array) {
if elem === value {
return index
}
}
return nil
}
and then find and remove from the array with
func removeDelegate(delegate:LocationManagerDelegate) {
if let index = findIdenticalObject(_delegates, delegate) {
_delegates.removeAtIndex(index)
}
}
The arguments to contains must implement the Equatable protocol since it is defined as:
public func contains<T:Equatable>(left:[T], right:T) -> Bool
Since there's no way to indicate that LocationManagerDelegate implements Equatable, I don't think you can use it. The obvious attempt would be:
protocol LocationManagerDelegate : Equatable {
...
}
But that will fail when you try to declare the array because Equatable uses Self.
The best option I can come up with is:
func removeDelegate(delegate:LocationManagerDelegate) {
_delegates = filter(_delegates) { return $0 !== delegate }
}
protocol LocationManagerDelegate {
// ...
var index_delegate:Int?{get set}
}
class LocationManager {
private var delegates:[LocationManagerDelegate] = []
func add(delegate: LocationManagerDelegate?){
if let d = delegate {
self.delegates.append(d)
let index = self.delegates.count - 1
self.delegates[index].index_delegate = index
}
}
func remove(delegate: LocationManagerDelegate) {
delegates = delegates.filter({ return $0.index_delegate != delegate.index_delegate })
}
}

Resources