问题
I have a protocol as well as a protocol extension and I'd like to implement a function in the protocol extension to sort an array defined by the protocol with custom objects, but it's not working.
protocol MyProtocol {
var myArray: [MyObject] { get set }
}
extension MyProtocol {
func sortArrayByCreationTime() {
myArray.sort {
$0.created > $1.created
}
}
}
Xcode is telling me that 'sort' has been renamed to 'sorted(by:)', but if im using this a new array gets created, but I need the old array to be sorted, not a new one.
What am I doing wrong?
回答1:
It's a misleading error – the problem is that you need to mark your sortArrayByCreationTime()
method as mutating in order to tell the compiler that it's mutating a property (as protocols can be adopted by both value and reference types):
extension MyProtocol {
mutating func sortArrayByCreationTime() {
myArray.sort {
$0.created > $1.created
}
}
}
回答2:
I created a Minimal, Complete, and Verifiable example (MCVE) out of the original code, and I made it work in a Swift 3 playground
import UIKit
import XCTest
extension Date
{
init?(year: Int, month: Int, day: Int) {
var dateComponents = DateComponents()
dateComponents.day = day
dateComponents.month = month
dateComponents.year = year
guard let date = Calendar.current.date(from: dateComponents)
else { return nil }
self = date
}
}
struct MyObject {
var created: Date
}
protocol MyProtocol {
var myArray: [MyObject] { get set }
}
extension MyProtocol {
mutating func sortArrayByCreationTime() {
myArray.sort {
$0.created > $1.created
}
}
}
struct ArrayContainer: MyProtocol {
var myArray: [MyObject]
}
let objects = [
MyObject(created: Date(year: 2016, month: 9, day: 1)!),
MyObject(created: Date(year: 2016, month: 11, day: 1)!),
MyObject(created: Date(year: 2016, month: 4, day: 1)!),
MyObject(created: Date(year: 2016, month: 8, day: 1)!),
]
var container = ArrayContainer(myArray: objects)
var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMdd"
XCTAssertEqual(["0901", "1101", "0401", "0801"],
container.myArray.map { dateFormatter.string(from: $0.created) })
container.sortArrayByCreationTime()
XCTAssertEqual(["1101", "0901", "0801", "0401"],
container.myArray.map { dateFormatter.string(from: $0.created) })
Let me give two options:
Option 1: Improved
In Option 1, extend the Array struct, so that you can create mutating method sortByCreationDate
. There is a trick that needs to be pulled out of a hat in order to extend [MyObject] (an array of MyObject)
protocol MyObjectProtocol { var created: Date { get set } }
extension MyObject: MyObjectProtocol { }
extension Array where Element: MyObjectProtocol {
mutating func sortByCreationTime() {
self.sort {
$0.created > $1.created
}
}
}
var container2 = ArrayContainer(myArray: objects)
XCTAssertEqual(["0901", "1101", "0401", "0801"],
container2.myArray.map { dateFormatter.string(from: $0.created) })
container2.myArray.sortByCreationTime()
XCTAssertEqual(["1101", "0901", "0801", "0401"],
container2.myArray.map { dateFormatter.string(from: $0.created) })
Option 2: Better
In Option 2, extend Sequence. A sequence will allow you to sort objects that are Arrays, and other types, e.g., Dictionaries. Better yet, it does not create a mutating method. But honestly MyProtocol
is a confusing API. With this method, MyProtocol
is no longer necessary.
extension Sequence where Iterator.Element == MyObject {
func sortedByCreationTime() -> [Iterator.Element] {
return self.sorted {
$0.created > $1.created
}
}
}
var container3 = ArrayContainer(myArray: objects)
XCTAssertEqual(["0901", "1101", "0401", "0801"],
container3.myArray.map { dateFormatter.string(from: $0.created) })
XCTAssertEqual(["1101", "0901", "0801", "0401"],
container3.myArray.sortedByCreationTime().map { dateFormatter.string(from: $0.created) })
来源:https://stackoverflow.com/questions/40811214/sort-in-protocol-extension-is-not-working