Imagine you had to join together attributed strings with a separator in between them. Which of the following methods would you use?
An extension on SequenceType with a function that takes in a separator. Can't use any optionals in the array.
extension SequenceType where Generator.Element: NSAttributedString {
func join(withSeparator separator: NSAttributedString) -> NSAttributedString {
var shouldAddSeparator = true
return self.reduce(NSMutableAttributedString()) {(element, sequence) in
if shouldAddSeparator {
shouldAddSeparator = false
}
else {
element.appendAttributedString(separator)
}
element.appendAttributedString(sequence)
return element
}
}
}
A private function that takes in an array of optional NSAttributedString and a separator.
private func join(attributedStrings strings: [NSAttributedString?], withSeparator separator: NSAttributedString) -> NSAttributedString? {
let unwrappedStrings = strings.flatMap{0ドル} as [NSAttributedString]
guard unwrappedStrings.count == strings.count else { return nil }
let finalString = NSMutableAttributedString()
for (index, string) in unwrappedStrings.enumerate() {
if index == 0 {
finalString.appendAttributedString(string)
}
else {
finalString.appendAttributedString(separator)
finalString.appendAttributedString(string)
}
}
return finalString
}
Extension on _ArrayType that can take in an array of options NSAttributedString
extension _ArrayType where Generator.Element == NSAttributedString? {
private func join(withSeparator separator: NSAttributedString) -> NSAttributedString? {
let unwrappedStrings = flatMap{0ドル} as [NSAttributedString]
var shouldAddSeparator = false
return unwrappedStrings.reduce(NSMutableAttributedString(), combine: { (string, element) in
if shouldAddSeparator {
string.appendAttributedString(separator)
}
else {
shouldAddSeparator = true
}
string.appendAttributedString(element)
return string
})
}
}
1 Answer 1
The second and third approach combine two tasks into one:
- Extract the non-nil elements from the given array of optional attributed strings, and
- concatenate these with a given separator.
My suggestion is to separate these concerns. This rules out #2 and #3
and leaves us with your #1 approach, the extension method on SequenceType
.
Another disadvantage of the extension _ArrayType
is that it uses an internal type,
so that might break in a future version of Swift.
The flatMap()
method from the Swift standard library already provides a method for the first task:
let attributedStrings = optionalAttributedStrings.flatMap { 0ドル }
and this can be combined with your extension method by the caller:
let joined = optionalAttributedStrings.flatMap { 0ドル }.join(withSeparator: separator)
A user might also want to concatenate non-optional strings, which works with the extension method:
let joined = attributedStrings.join(withSeparator: separator)
but not with the other two approaches.
Another argument for approach #1 is that it resembles the existing method to join strings:
extension SequenceType where Generator.Element == String {
public func joinWithSeparator(separator: String) -> String
}
The method itself can be improved. The reduce()
method creates a new
NSMutableAttributedString
in each iteration. Better create only one
and append all elements (similar as on your join()
function):
extension SequenceType where Generator.Element: NSAttributedString {
func join(withSeparator separator: NSAttributedString) -> NSAttributedString {
let finalString = NSMutableAttributedString()
for (index, string) in enumerate() {
if index > 0 {
finalString.appendAttributedString(separator)
}
finalString.appendAttributedString(string)
}
return finalString
}
}
Update: The Swift language changes constantly. For the convenience of future readers, here is an update to Swift 4 of the above code:
extension Sequence where Element: NSAttributedString {
func join(withSeparator separator: NSAttributedString) -> NSAttributedString {
let finalString = NSMutableAttributedString()
for (index, string) in enumerated() {
if index > 0 {
finalString.append(separator)
}
finalString.append(string)
}
return finalString
}
}
Also flatMap
has been renamed to compactMap
:
let attributedStrings = optionalAttributedStrings.compactMap { 0ドル }