How to Use a Protocol with Optional Class Methods in an Extension with Generic in Swift

How to use a protocol with optional class methods in an extension with generic in Swift?

It looks like you've found a (fairly obscure) bug in the Swift compiler that's causing it to crash.
Here's a reproduction that's all you need in a single file to crash swiftc:

import Foundation
@objc protocol P { optional class func f() }
func f<T: P>(t: T) { T.self.f?() }

(you don't need to call f for it to crash)

You should probably file a radar since the compiler crashing is never expected behavior no matter what your code.

If you tried to do this without the optional, it'll work (and you can even ditch the self). My guess is the implementation of generics doesn't currently account for the possibility of optional class-level functions.

You can do it without generics like this:

func f(p: MyProtocol) {
(p as AnyObject).dynamicType.foo?()
}

(there may even be a better way but I can't spot it).

You need the AnyObject cast because if you try to call .dynamicType.foo?() on p directly you get "accessing members of protocol type value 'MyProtocol.Type' is unimplemented". I wouldn't be surprised if the crash of the generic version is related to this.

I'd also say that its worth asking yourself whether you really need a protocol with optional methods (especially class-level ones) and whether there's a way to do what you want entirely statically using generics (as you're already semi-doing).

Wrapping a generic method in a class extension

You have to define the method for an array of optional elements, and the return type as the corresponding array of non-optionals. This can be done with a generic function:

extension Array {
public func removingNilElements<T>() -> [T] where Element == T? {
let noNils = self.compactMap { $0 }
return noNils
}
}

Example:

let a = [1, 2, nil, 3, nil, 4]   // The type of a is [Int?]
let b = a.removingNilElements() // The type of b is [Int]
print(b) // [1, 2, 3, 4]

In your code, $0 has the (non-optional) type Element, and it just wrapped by the compiler into an optional in order to match the argument type of compactMap().

Conform class extension to generic protocol function

It looks like you're conflating associated types with generic functions.

Generic functions allow you to provide a type to replace a given generic placeholder at the call site (i.e when you call the function).

Associated types allow types conforming to protocols to provide their own type to replace a given placeholder type in the protocol requirements. This is done per type, not at the call site of any function. If you wish to enforce a conformance requirement for an associatedtype, you should do so directly on its declaration (i.e associatedtype PageItemType : Pageable).

If I understand your requirements correctly, your itemAt(index:) function should be non-generic (otherwise the associatedtype in your protocol is completely redundant). The type that it returns is defined by the implementation of the type that conforms to Page, rather than the caller of the function. For example, your People class defines that the PageItemType associated type should be a Person – and that is what itemAt(index:) should return.

protocol Pageable {/* ... */}

protocol Page {

// any conforming type to Page will need to define a
// concrete type for PageItemType, that conforms to Pageable
associatedtype PageItemType : Pageable

// returns the type that the implementation of the protocol defines
// to be PageItemType (it is merely a placeholder in the protocol decleration)
func itemAt(index: Int) -> PageItemType
}

class Person : Pageable {/* ... */}

class People {
var people: [Person]?
}

extension People : Page {

// explicitly satisfies the Page associatedtype requirement.
// this can be done implicitly be the itemAt(index:) method,
// so could be deleted (and annotate the return type of itemAt(index:) as Person)
typealias PageItemType = Person

// the itemAt(index:) method on People now returns a Person
func itemAt(index: Int) -> PageItemType {

// I would advise against force unwrapping here.
// Your people array most likely should be non-optional,
// with an initial value of an empty array
// (do you really need to distinguish between an empty array and no array?)
let person = self.people![index]
return person
}
}

In regards to your implementation of a PagedCollection – as the PageItemType associated type in your Page protocol conforms to Pageable, I see no need for an ItemType generic parameter. This can simply be accessed through the associated type of the given PageType generic parameter.

As an example:

class PagedCollection<PageType:Page> {

// PageType's associatedtype, which will conform to Pageable.
// In the case of the PageType generic parameter being People,
// PageType.PageItemType would be of type Person
var foo : PageType.PageItemType

init(foo: PageType.PageItemType) {
self.foo = foo
}
}

// p.foo is of type Person, and is guarenteed to conform to Pageable
let p = PagedCollection<People>(foo: Person())

How to add default implementation using generic protocol extension?

Maybe this would be simpler.

extension Comparable {
func limit(from minValue: Self, to maxValue: Self) -> Self {
return Swift.max(Swift.min(self, maxValue), minValue)
}
}

how to extend optional chaining for all types

is there no way to make this generic function work as a protocol method?

No, you must "explicitly apply the protocol to the types I care about".

However, you are in fact reinventing the wheel. This is the use case of flatMap/map. If both foo and bar are optional, you can write:

bar.flatMap { foo?.doSomething($0) }

Note the lack of ? after bar. You are calling flatMap on Optional, rather than bar's type. If doSomething returns T, the above expression will return T?.

If only bar is optional, use map:

bar.map { foo.doSomething($0) }

Swift extension of generic type that is Optional

You can create a generic T where T? == Value. This means that Value is an optional (and T is the wrapped value of the optional, even though we don't need this type directly).

Code:

extension Binding {
func filter<T>(_ predicate: @escaping (Value) -> Bool) -> Binding<Value> where T? == Value {
Binding<Value>(
get: {
if predicate(wrappedValue) { return nil }
return wrappedValue
},
set: {
wrappedValue = $0
}
)
}
}

How to extend protocol Optional, where Wrapped item is Array of Equatable generic elements?

Cristik provided a good solution. An alternative is to write an extension to an optional collection of equatable elements:

extension Optional where Wrapped: Collection, Wrapped.Element: Equatable {
func foo() { }
}

This would be applicable to arrays, array slices, and other collections.

Depending on what the extension does, you may want to use a combination of Collection, MutableCollection, BidirectionalCollection, RandomAccessCollection.

Swift 4 extension function on generic protocol which takes a generic function as parameter

You cannot use Source as a concrete return type for map because it is a protocol with an associated type requirement.

To solve this, you can have the map function return Mapping<X, Self>:

extension Source {
func map<Result>(_ transform: @escaping (T) -> Result) -> Mapping<Result, Self> {
return Mapping(self, transform)
}
}

The function now has a Self requirement. The resulting Mapping type has a generic type parameter Self that is replaced by the concrete implementation of Source, e.g. Mapping or TextSource.

How to create extension for a generic structure that will be available only for Optional generic parameters in Swift 4.1?

In the answer provided by Rakesha Shastri I found out that I can use protocol to refactor my code. However to be able to use this solution I need to rewrite other parts of my application. I tried to add the solution by Rakesha Shastri to my application and compile it with Swift 3, Swift 4 or Swift 4.2 (all Swift versions available in Xcode 10.0), but every time I received a compiler error. It means that currently there are no ways how to solve the problem that I described in this question.



Related Topics



Leave a reply



Submit