π 2019.01.04 (SAT)
WWDC2016 | Session : 403 | Category : Swift
π Swift API Design Guidelines - WWDC 2016 - Videos - Apple Developer
Every language has its own feel
Feel of everyday APIs
DispatchQueue.main.sync {
self.listDocumentsViewController?.present(signedOutController, animated: true)
}
Swift rendering an opinion about certain things here.
- It uses trailing closures. So the control flow works with your libraries and your APIs nicely.
- It has optional. So you have to consider the possibility of nil everywhere.
- Clarity at the point of use
- Clarity is more important than brevity
- Concise code is a consequence of using contextual cues
- Design APIs to make uses clear and concise
- Use of APIs always have surrounding context
- Don't optimize for bad code
βββ
if let c = a.index(of: b) {
a.remove(at: c)
}
β
β
β
if let completedPosition = tasks.index(of: completed) {
tasks.remove(at: completedPosition)
}
friends.removeItem(ted)
friends.removeObject(ted)
friends.removeElement(ted)
organicCompounds.removeElement(caffeine)
// Omit needless words.
friend.remove(ted)
friend.remove(ted)
- Brevity itself is not a worthwhile goal
- Concise code is consequence of using contextual cues
let end: [Ingredient].Index = list.index(list.startIndex, offsetBy: 3, limitedBy: list.endIndex)!
let shortList: ArraySlice<Ingredient> = list[0..<end]
That verbosity is all of these explicit type annotations. These aren't adding to readability. You can get a sense of what the types are just by reading the APIs. And indeed, in Swift you probably wouldn't write the code this way.
let end = list.index(list.startIndex, offsetBy: 3, limitedBy: list.endIndex)!
let shortList = list[0..<end]
You would probably align that type information and let the static type inference do it for you leading to more concise code that you can still read through just as well.
- Write out a use case
mainView.addChild(sideBar, atPoint: origin)
β¬οΈβ¬οΈβ¬οΈ
mainView.addChild(sideBar, at: origin)
Does each word contribute to understanding?
- Clarify parameter role
- Don't restate type information
friends.remove(ted)
β friends.remove(positionOfFormerFriend)
β
friends.remove(at: positionOfFormerFriend)
Notice how we've clarified the behavior of the API by putting in this first argument label to describe the relationship of the argument to the method.
Different APIs can be distinguished by argument label alone
friends.remove(ted) // remove(_:)
friends.remove(at: positionOfTed) // remove(at:)
Two APIs should share a compound name if they have the same semantics
text.append(aCharcter) // append(_:)
text.append(aString)
// If the first argument is part of a prepositional phrase, give it a label
truck.removeBoxes(withLabel: "WWDC 2016")
// If the first argumetn is not part of a grammatical phrase, give it a label
β viewController.dismiss(true)
β
viewController.dismiss(animated: true)
// Otherwise, don't use a first argument label
frineds.insert(michael, at: friends.startIndex)
Use a verb to describe the side effect
friends.reverse()
viewController.present(animated: true)
oragnicCompounds.append(caffeine)
Use a noun to describe the result
button.backgroundTitle(for: .disabled)
friends.suffix(3)
The "ed/ing" rule
"ed" rule
x.reverse() // mutating
let y = x.reversed() // non-mutating
"ing"rule
documentDirectory.appendPathComponent(".list") // mutating
let documentFile = documentDirectory.appendingPathCompnent(".list") // non-mutating
β control.addTarget(self, action: Selector("handleDragWithSender:for"),
for: [.touchDragInside, .touchDragOutside])
β
control.addTarget(self, action: #selector(handleDrag(sender:for:)),
for: [.touchDragInside, .touchDragOutside])
β album.addObserver(self, forKeyPath: "artist.name" options: .new, context: &artistNameContext)
β
album.addObserver(self, forKeyPath: #keyPath(Album.artist.name), options: .new, context: &artistNameContext)
Identify first argument labels
func saveToURL(_ url: NSURL)
func revertToContentsOfURL(_ url: NSURL)
// β¬οΈβ¬οΈβ¬οΈ Identify first argument labels
func save(toURL url: NSURL)
func revert(toContentsOfURL url: NSURL)
// β¬οΈβ¬οΈβ¬οΈ Remove words that restate type information
func save(to url: NSURL)
func revert(toContentsOf url: NSURL)
// β¬οΈβ¬οΈβ¬οΈ Introduced default arguments
func save(to url: NSURL, completionHandler: ((Bool) -> Void)? = nil)
// β¬οΈβ¬οΈβ¬οΈ Use bridged value types
func save(to url: URL)
func revert(toContentsOf url: URL)
Names don't go far enough
// Objective-C
typedef NSString *NSCalendarIdentifier;
NSCalenarIdeintifier NSCalendarIdentifierGregorian;
// Generated Swfit Interface
typealias NSCalendarIdentifier = NSString
let NSCalendarIdentifierGregorian: NSCalendarIdentifier
let cal = NSCalendar(calendarIdentifier: NSCalendarIdeintifierGregorian)
let cal = NSCalendar(identifier: .gregorian)
let cal = Calendar(identifier: .gregorian)
Properties
// Generated Swift Interface
extension CGColor { static let white: CFString }
// Swift use
let color = CGColor.white
Initializers
// Generated Swift Interface
extension CGAffineTransform { init(translationX: CGFloat, y: CGFloat) }
// Swift use
let translate = CGAffineTransform(translationX: 1.0, y: 0.5)
Method
// Generated Swift Interface
extension CGContext { func fillPath() }
// Swift use
context.fillPath()
Computed Properties
// Generated Swift Interface
extension Artist { var name: CFString { get set} }