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
How to Add a Show More/Show Less Uibutton to Control Uitextview
How to Move Application's Window Between Virtual Desktops in Os X
Is There a Github Markdown Language Identifier for Swift Code
Zoom to Fit Current Location and Annotation on Map
How to Create a Hotspot Network in iOS App Using Swift
Firebase Crashlytics Not Showing Crash Report in Console Dashboard Swift
Manually Disposing a Disposebag in Rxswift
Switching a @State Property to a @Binding Property Interferes with Animation
How to Replace the Values of Labels in iOS-Charts
Swift Sprite Kit in App Purchase
Troubles Using Cgpathcontainspoint Swift
API Violation - Multiple Calls Made to -[Xctestexpectation Fulfill]
How to Sum the Numbers(Int16) of Stored Core Data - Swift 3
How to Synchronize Coredata with Webservices in Swift