Generic class inheritance in Swift - ios

I have the following class:
class BaseCache<T: Equatable>: NSObject {
var allEntities = [T]()
// MARK: - Append
func appendEntities(newEntities: [T]) {
....
}
}
Now I want to subclass it, but I get annoying error, that my type "does not conform to protocol 'Equatable'":
It seems generics in Swift are real pain-in-the-ass.

Your class definition of TrackingCache is wrong. It repeats the generic parameter:
class TrackingCache<AftershipTracking>: BaseCache<AftershipTracking> { }
It should be left out:
class TrackingCache: BaseCache<AftershipTracking> { }
This triggers the underlying swift error Classes derived from generic classes must also be generic. You can work around this issue by specifying a type parameter that is required to be or inherit from AftershipTracking:
class TrackingCache<T: AftershipTracking>: BaseCache<AftershipTracking> { }
Full example:
class BaseCache<T: Equatable>: NSObject {
var items: [T] = []
func appendItems( items: [T]) {
self.items += items
didAppendItems()
}
func didAppendItems() {} // for overriding
}
class AftershipTracking: NSObject {
var identifier: Int
init( identifier: Int) {
self.identifier = identifier
super.init()
}
}
extension AftershipTracking: Equatable { }
func ==( lhs: AftershipTracking, rhs: AftershipTracking) -> Bool {
return lhs.identifier == rhs.identifier
}
class TrackingCache<T: AftershipTracking>: BaseCache<AftershipTracking> {
override func didAppendItems() {
// do something
}
}
let a = TrackingCache<AftershipTracking>()
let b = TrackingCache<AftershipTracking>()
a.appendItems( [AftershipTracking( identifier: 1)])
b.appendItems( [AftershipTracking( identifier: 1)])
let result = a.items == b.items // true

this should work: < swift 4 >
class TrackingCache<T: AftershipTracking>: BaseCache<T>
Another example:
protocol P {
}
class C: P {
}
class CS: C {
}
class L<T:P> {
let c: T
init(_ c: T) {
self.c = c
}
}
class LS<T:CS>:L<T> {
}
let i = LS(CS())
i.c
c is CS now.

Related

is it possible to use Generic dynamic in Swift

protocol Base {
associatedtype M
var data:M { get set }
func update(data:M)
}
class ViewA : Base {
var data: String = ""
func update(data: String) {}
}
class ViewB : Base {
var data: Int = 2
func update(data: Int) {}
}
var dataArr : [Any] = ["1",2]
var viewArr : [Any] = [ViewA(), ViewB()]
func updatData() {
func update<C, T>(view:C, data:T) where C : Base, C.M == T {
view.update(data: data)
}
for i in 0..<2 {
let view = viewArr[i]
let data = dataArr[I]
// ! there is a errr here, Protocol 'Any' as a type cannot conform to 'Base'
update(view: view, data: data)
}
}
My Views conform to this 'Base' protocol which define what type of data my view use
And I want to implement this updatData function dynamic tell if data can be send to view (base on viwe.m type is same to data's type)
But it is seems to be impossible in Swift?
Array of Any object pass as Base Protocol create conflict.
so add one more class for Base class of ViewA and ViewB
protocol Base {
associatedtype M
var data: M { get set }
func update(data: M)
}
class Base1: Base {
typealias M = Any
var data: Any = ""
func update(data: Any) {
print("Udate \(data)")
}
}
class ViewA: Base1 {
//typealias M = String
//var data: String = ""
override func update(data: Any) {
print("test")
}
}
class ViewB: Base1 {
//typealias M = Int
//var data: Int = 2
override func update(data: Any) {
print(data)
print("Udate")
}
}
var dataArr: [Any] = ["1", 2]
var viewArr: [Any] = [ViewA(), ViewB()]
func updatData() {
func update<C, T>(view: C, data: T) where C: Base, C.M == T {
view.update(data: data)
}
for i in 0..<2 {
let view = viewArr[i]
let data = dataArr[i]
update(view: view as! Base1, data: data)
}
}
You want to create a type erased Base concrete type here: AnyBase:
public final class AnyBase: Base {
fileprivate let _boxed: _Box<M>
public init<Concrete: Base>(_ concrete: Concrete) where Concrete.M == M {
self._boxed = _ConcreteBox(concrete)
}
// Base conformance
public var data: M { _boxed.data }
public func update(data: M) {
_boxed.update(data: data)
}
// Type erasure
fileprivate class _Box<T>: Base {
init() {
guard type(of: self) != _Box.self else { fatalError("Can't create _Box instances, create a sub class instance instead ") }
}
// Base conformance
var base: M {
get { fatalError("Must override") }
set { fatalError("Must override") }
}
func update(data: M) {
fatalError("Must override")
}
}
fileprivate final class _ConcreteBox<Concrete: Base>: _Box<Concrete.M> where M == Concrete.M {
let _concrete: Concrete
init(_ concrete: Concrete) {
self._concrete = concrete
super.init()
}
// Base conformance
override var data: {
get { _concrete.data }
set { _concrete.data = newValue }
}
override func update(data: M) {
_concrete.update(data: data)
}
}
}
Now you can store different concrete instances of Base<M> in an array by adopting the type erased AnyBase<M> instead of using Any in your arrays.

Instantiating classes stored in metatype Dictionary

I've followed the solution at Make a Swift dictionary where the key is "Type"? to create dictionaries that can use a class type as keys.
What I want to do is: I have one dictionary that should store class types with their class type (aka metatype) as keys, too:
class MyScenario {
static var metatype:Metatype<MyScenario> {
return Metatype(self)
}
}
var scenarioClasses:[Metatype<MyScenario>: MyScenario.Type] = [:]
Then I have methods to register and execute scenarios:
public func registerScenario(scenarioID:MyScenario.Type) {
if (scenarioClasses[scenarioID.metatype] == nil) {
scenarioClasses[scenarioID.metatype] = scenarioID
}
}
public func executeScenario(scenarioID:MyScenario.Type) {
if let scenarioClass = scenarioClasses[scenarioID.metatype] {
let scenario = scenarioClass()
}
}
... Problem is in the last line:
Constructing an object of class type 'MyScenario' with a metatype
value must use a 'required' initializer.
It looks like the compiler is confused at that point since I cannot use 'required' at that assignment. Does anyone have an idea how I would have to instantiate the scenarioClass in executeScenario()?
This must do the job.
import Foundation
struct Metatype<T> : Hashable
{
static func ==(lhs: Metatype, rhs: Metatype) -> Bool
{
return lhs.base == rhs.base
}
let base: T.Type
init(_ base: T.Type)
{
self.base = base
}
var hashValue: Int
{
return ObjectIdentifier(base).hashValue
}
}
public class MyScenario
{
var p: String
public required init()
{
self.p = "any"
}
static var metatype:Metatype<MyScenario>
{
return Metatype(self)
}
}
var scenarioClasses:[Metatype<MyScenario>: MyScenario.Type] = [:]
public func registerScenario(scenarioID:MyScenario.Type)
{
if (scenarioClasses[scenarioID.metatype] == nil)
{
scenarioClasses[scenarioID.metatype] = scenarioID
}
}
public func executeScenario(scenarioID:MyScenario.Type)
{
if let scenarioClass = scenarioClasses[scenarioID.metatype]
{
let scenario = scenarioClass.init()
print("\(scenario.p)")
}
}
// Register a new scenario
registerScenario(scenarioID: MyScenario.self)
// Execute
executeScenario(scenarioID: MyScenario.self)
// Should print "any"

Swift restrict generic type to Type

How can I restrict a generic type to be a type, not an instance of a type?
If I have a class:
class SomeClass<T: SomeProtocol> {}
how can I ensure that T is only an instance of AnyClass (which is just AnyObject.Type)
My protocol only has static methods and in order to call those methods I have to do instance.dynamicType.protocolMethod whereas I want to do someType.protocolMethod
AFAIK, Swift does not allow you to use a metatype as a generic type. (I believe this is along the lines of what Sam Giddins wished for in Swift 3.)
You can, however, use it as a value. Instead of making T a type parameter, make it a property:
protocol SomeProtocol {
static func foo()
}
struct Concrete: SomeProtocol {
static func foo() {
print("I am concrete")
}
}
class SomeClass {
let T: SomeProtocol.Type
init(T: SomeProtocol.Type) {
self.T = T
}
func go() {
T.foo() // no need for dynamicType
}
}
SomeClass(T: Concrete.self).go()
If, as you say, your protocol contains only static methods, then this is sufficient. However, if you need to tie a generic parameter to the type, that’s possible too:
class SomeClass<U: SomeProtocol> {
let T: U.Type
init(T: U.Type) {
self.T = T
}
func go(value: U) {
T.foo()
}
}
SomeClass(T: Concrete.self).go(Concrete())
protocol P {
static func foo()
}
class A : P {
static func foo() {
print("A class")
}
}
class B : P {
static func foo() {
print("C class")
}
}
var type1 = A.self
var type2 = B.self
func check(cls: AnyClass)->Void {
switch cls {
case is A.Type:
A.foo()
case is B.Type:
B.foo()
default:
break
}
}
check(type1) // A class
check(type2) // C class
let i = Int.self // Int.Type
let ao = AnyObject.self // AnyObject.Protocol
let ac = AnyClass.self // AnyClass.Protocol
let p = P.self // P.Protocol
let f = check.self // (AnyClass)->Void
Edit
protocol P {
static func f()
}
class A : P {
static func f() {
print("A")
}
}
class B : P {
static func f() {
print("B")
}
}
func f(cls: P.Type) {
cls.f()
}
f(A) // A
f(B) // B
class Test<T: P> {
func foo() {
T.f()
}
}
Test<A>().foo() // A
Test<B>().foo() // B

Swift generics: EXC_BAD_ACCESS when trying to access class property

It seems a class, which uses generics in swift, sometimes cannot properly determine object type.
Consider the following model structure:
class BaseModel: NSObject, Equatable, Printable {
var id: String = ""
override var description: String {
return "id: \(id)"
}
override func isEqual(object: AnyObject?) -> Bool {
if let object = object as? BaseModel {
return object.id == id
}
else {
return super.isEqual(object)
}
}
}
class Image: BaseModel {
var image: UIImage!
}
I also have parsers, which should parse/serialize objects:
class AbstractParser<T: BaseModel where T: Equatable>: NSObject {
func convertFromParseObject(object: NSObject) -> T {
var entity = T()
......
return updateEntityWithParseObject(object, entity: entity)
}
func updateEntityWithParseObject(object: NSObject, entity: T) -> T {
fatalError("This method must be overridden")
}
}
class ImageParser<T: Image>: AbstractParser<Image> {
override func updateEntityWithParseObject(object: NSObject, entity: Image) -> Image {
println("\(entity)")
println("\(entity.id)")
// The line below outputs BaseModel, shouldn't it be Image instead?
println("\(NSStringFromClass(entity.classForCoder))")
// EXC_BAD_ACCESS here:
println("\(entity.image)")
return entity
}
}
The app crashes when I try to access entity.image.
For some reasons Swift thinks that entity object is BaseModel, not Image.
Playground file: https://drive.google.com/file/d/0B6agzpK_lR6JQUlhMFoxaGw1akU/view?usp=sharing

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