SNOOZE LOG

iOS中心のプログラミング関連技術メモ


スポンサードリンク

【Swift3】Swift API Design Guidelines を詳読する

原文を自分で読んでポイントをメモしてみようと思いましたが、
割とまるまる翻訳するかたちになってしまいました。
原文:Swift.org - API Design Guidelines
の併読もおすすめいたします。

Fundamentals(基本)

・使用する時にわかりやすいことが最も重要な目標です。
メソッドやプロパティは繰り返し使われるので、用途を明確かつ簡潔にしましょう。

・わかりやすさは、簡潔さよりも重要です。
Swiftのコードはすごく短く書くことも出来ますが、わかりやすさを犠牲にしないようにしましょう。

・すべての宣言についてのドキュメンテーションコメントを書きましょう。
書くことによって良いAPI設計の洞察が得られます。
もしそのAPIの機能が簡単に説明できないとしたら、API設計が間違ってるかもしれません。

SwiftにおけるMarkdown記法を使って書きましょう。

APIのコメントはサマリー(概要)から書きます。

/// Returns a "view" of `self` containing the same elements in
/// reverse order.
/// 自身を逆順にしたものを返します。
func reversed() -> ReverseCollection



・サマリーにフォーカスしましょう。最も重要です。優れたドキュメンテーションコメントの多くは、わかりやすいサマリーだけで書かれています。

・完全な文章でなく、出来れば一行ぐらいで説明しましょう。文章の最後にはピリオドをつけます。

・メソッドや関数が何をして、何を返すのかを書きましょう。戻り値がVoidの説明は省略します。

/// Inserts `newHead` at the beginning of `self`.
/// `self`の先頭に` newHead`を挿入します。
mutating func prepend(_ newHead: Int)

/// Returns a `List` containing `head` followed by the elements
/// of `self`.
/// `head`とそれに続く` self`の要素を含む `List`を返します。
func prepending(_ head: Element) -> List

/// Removes and returns the first element of `self` if non-empty;
/// returns `nil` otherwise.
/// 空でなければ `self`の最初の要素を削除して返します。そうでなければ `nil`を返します。
mutating func popFirst() -> Element?


Note: popFirstのような稀なケースでは、サマリーをセミコロンで区切った複数の文章を書きます。

・subscriptが何にアクセスするかを記述しましょう:
補足 : The Swift Programming Language (Swift 4): Subscripts

/// Accesses the `index`th element.
/// `index`番目の要素にアクセスします。
subscript(index: Int) -> Element { get set }


・イニシャライザの作成内容を記述しましょう:

/// Creates an instance containing `n` repetitions of `x`.
/// n回、xの反復を含むインスタンスを作成します。
init(count n: Int, repeatedElement x: Element)


他のすべての宣言については、宣言されたエンティティが何であるかを記述しましょう。:

/// A collection that supports equally efficient insertion/removal
/// at any position.
/// 任意の位置で、同程度の効率性で挿入/除去が出来るコレクションです。
struct List {

  /// The element at the beginning of `self`, or `nil` if self is
  /// empty.
  /// selfの先頭のエレメント。空の場合はnilです。
  var first: Element?

・必要に応じて、段落と箇条書きの項目を続けます。段落は空行で区切り、詳細な説明を書き加えます。

/// Writes the textual representation of each    ← Summary(概要)
/// element of `items` to the standard output.
/// 標準出力にitems各要素のテキスト表現を書き込みます。
///                                              ← Blank line(空行)
/// The textual representation for each item `x` ← Additional discussion(追加のディスカッション)
/// is generated by the expression `String(x)`.
/// 各アイテムXのテキスト表現は String(X)によって生成されます。
///
/// - Parameter separator: text to be printed    ⎫
///   between items.                                             ⎟
///                                                                         ⎟
/// - Parameter terminator: text to be printed   ⎬ Parameters section
///   at the end.                                                    ⎟
///                                                                         ⎭
///  パラメータセパレータ:アイテム間にプリントされます。                                                                       ⎭
///  パラメータターミネータ:テキストは最後にプリントされます。
/// 
/// - Note: To print without a trailing                        ⎫
///   newline, pass `terminator: ""`                           ⎟
///                                                                                ⎬ Symbol commands
/// - SeeAlso: `CustomDebugStringConvertible`,   ⎟
///   `CustomStringConvertible`, `debugPrint`.      ⎭
///  注釈:末尾を改行せずにプリントするには、terminator: ""を渡します。                                                                       ⎭
///  参考:CustomDebugStringConvertible, CustomStringConvertible,  debugPrint。

public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

・サマリー以上の情報を追加するにはシンボルドキュメンテーションマークアップを使用します 。

シンボルコマンドシンタックスを使用しましょう 。Xcodeなどのポピュラーな開発ツールでは次のキーワードで始まる項目に特別な処理を行います。

Attention Author Authors Bug
Complexity Copyright Date Experiment
Important Invariant Note Parameter
Parameters Postcondition Precondition Remark
Requires Returns SeeAlso Since
Throws Todo Version Warning


Naming(命名)

・命名にはあいまいさを避けるために必要な全ての単語を入れましょう。

例えば、コレクション内の特定の位置にある要素を削除するメソッドを考えてみます。

/* OK */
extension List {
  public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)

もし、次のようにこのメソッドのシグネチャからatを省略してしまうと「リストのx位置の要素を削除する」のではなく「リストの中からxと一致するものを削除する」メソッドのように見えてしまいます。

/* NG */
employees.remove(x) // unclear: are we removing x?

Promote Clear Usage(明確に使用されるように)

・不必要な言葉は捨てましょう。すべての単語はっきりとした情報を伝えている必要があります。

意図や意味​​を明確にするために、多くの単語が必要に思うかもしれませんが、読む人が既に持っている情報と重複するものは省略してください。特に、型情報を繰り返すだけの単語は除きます 。

/* NG */
public mutating func removeElement(_ member: Element) -> Element?

allViews.removeElement(cancelButton)

この場合のElementは何の情報ももたらしません。
次のようにすると、より良いでしょう:

/* OK */
public mutating func remove(_ member: Element) -> Element?

allViews.remove(cancelButton) // より明確

時には、曖昧さを避けるために繰り返される型情報が必要になることもありますが、仮引数名には型ではなく引数の役割を記述した方が良いです。詳細については、次の項目を参照してください。

・変数、引数、associatedtypeの命名には、型名をそのまま付けるのではなく役割名を付けましょう

/* NG */
var string = "Hello"
protocol ViewController {
  associatedtype ViewType : View
}
class ProductionLine {
  func restock(from widgetFactory: WidgetFactory)
}

このようにタイプ名を再利用すると、明確さと表現力が最適化されません。代わりに、エンティティの役割を示す命名に努めます。

/* OK */
var greeting = "Hello"
protocol ViewController {
  associatedtype ContentView : View
}
class ProductionLine {
  func restock(from supplier: WidgetFactory)
}

(ただし以下のように)associated typeがそのプロトコル制約に強い結びつきがあり、プロトコル名が役割である場合、associated typeに"Type"を付加したすることで衝突を避けます :

protocol Sequence {
  associatedtype IteratorType : Iterator
}

・引数は役割を明確にするように曖昧な型情報は補いましょう。

引数の型が、NSObject、Any、AnyObject、もしくは、基本型のInt、Stringの時は特に、使う時点での型情報とコンテキストが意図を完全に伝えていない可能性があります。この例では、宣言を見ると明確かもしれませんが、呼び出し箇所では曖昧です。

/* NG */
func add(_ observer: NSObject, for keyPath: String)
grid.add(self, for: graphics) // 曖昧

明確さを取り戻すには、曖昧な型の各引数の頭に、その役割を説明する名詞を付けます。

/* OK */
func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics) // 明確

Strive for Fluent Usage(流暢な呼び出し方を目指す)

・呼び出しが文法的な英語のフレーズを形成するような、メソッド名と関数名を優先します。

/* OK  */
x.insert(y, at: z)          “x, insert y at z”
x.subViews(havingColor: y)  “x's subviews having color y”
x.capitalizingNouns()       “x, capitalizing nouns”
/* NG */
x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()

それらの引数が呼び出しの意味の主要でない時、第一引数または第二引数の後では流暢さが落ちてもかまいません。:

AudioUnit.instantiate(
  with: description, 
  options: [.inProcess], completionHandler: stopProgressBar)

・ファクトリメソッドの名前は"make"で始めましょう。例 x.makeIterator()

*・イニシャライザとファクトリメソッドの呼び出しは、第一引数を含まないフレーズを構成しましょう。x.makeWidget(cogCount:47)

例えば、これらの呼び出しで暗示されるフレーズには、第一引数は含まれません。

/* OK  */
let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)

以下では、API作成者が第一引数で文法的連続性を作成しようとしました。

/* NG  */
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)

実際には、このガイドラインと引数ラベルのガイドライン は、呼び出しが値の型変換を実行している場合を除いて、第一引数にラベルが付いていることを意味します 。

let rgbForeground = RGBColor(cmykForeground)

・関数とメソッドは副作用に応じた命名をしましょう。

  ∟副作用のないものは名詞句として読めなければなりません。例 x.distance(to: y)i.successor()

  ∟副作用のあるものは動詞句として読めるべきです。例 print(x)x.sort()x.append(y)

  ∟可変(mutating)/不変(nonmutating)メソッドのペアは一貫した命名をしましょう。可変メソッドは同じセマンティックスで不変型の変数を持つことが良くありますが、インスタンスをそこで更新するのではなく、新しい値を返すようにしましょう。
  ∟操作が動詞での表現が自然な場合、可変メソッドには動詞が不可欠であり、接尾辞"ed"または “ing"を付けて不変メソッドを命名します。

可変(Mutating) 不変(Nonmutating)
x.sort() z = x.sorted()
x.append(y) z = x.appending(y)

  ∟動詞の過去分詞を使用して、不変型を命名するようにしましょう。 (通常は “ed"を追加します):

/// Reverses `self` in-place.
/// in-placeでselfを逆順にします。
mutating func reverse()

/// Returns a reversed copy of `self`.
/// selfを逆順にしたコピーを返します。
func reversed() -> Self
...
x.reverse()
let y = x.reversed()

  ∟動詞に直接目的語があるため"ed"を文法的に追加しない場合に、動詞の現在分詞を使用して不変型の名前に “ing"を追加します。

/// Strips all the newlines from `self`
/// selfから全ての改行を取り除きます。
mutating func stripNewlines()

/// Returns a copy of `self` with all the newlines stripped.
/// 全ての改行を取り除いたselfのコピーを返します。
func strippingNewlines() -> String
...
s.stripNewlines()
let oneLine = t.strippingNewlines()

操作が名詞での表現が自然な場合、不変メソッドには名詞を使用し、また可変に対する接頭辞"form"を適用します。

不変(Nonmutating) 可変(Mutating)
x = y.union(z) y.formUnion(z)
j = c.successor(i) c.formSuccessor(&i)

・Booleanメソッドとプロパティの使用は、不変の呼び出しの場合、レシーバーに関するアサーション(表明)として読み取れる必要があります。x.isEmptyline1.intersects(line2)

・それが何であるかを示すプロトコルは名詞にしましょう。(例 Collection

・何が出来るかを示すプロトコルは接尾辞 able、ible、ing を使用した命名をしましょう。 (例 EquatableProgressReporting

・その他の型、プロパティ、変数、定数の名前は名詞にしましょう。

Use Terminology Well(専門用語の良い使い方)

専門用語でのアート 名詞 - 特定の分野や職業で、正確で特別な意味を持つ語句。

・より一般的な言葉で意味が伝わる場合は、よくわからない専門用語を避けましょう。「皮膚(skin)」でも良い場合「表皮(epidermis)」とは言わないでください。専門用語はコミュニケーションに欠かせないツールですが、重要な意味が失われてしまわないようにする時だけ使用してください。

・専門用語を使うなら、正確な意味にこだわりましょう。

・より一般的な言葉ではなく、専門用語を使用する唯一の理由は、そうでなければ曖昧であるか不明瞭なものを正確に表現することです。したがって、APIはその意味に従って厳密にこの用語を使用すべきでしょう。

  ∟エキスパートをびっくりさせないようにしましょう:その用語に慣れ親しんだ人にとって、違う意味で使われていればびっくりして怒ってしまいます。

  ∟初心者をびっくりさせないようにしましょう:誰もがウェブ検索を行って、その用語の伝統的な意味を知ることができます。

単語を略しないようにしましょう。略語、特に非標準的なものは、専門用語でのアートするに効果的でしょう。なぜなら、理解するには、略語を非省略形に正しく直すことに依存しているからです。

あなたが使用する省略形の意味は、Web検索で簡単に見つかるはずです。

先例を受け入れましょう。既にある文化への適合性を無駄にしてまで、初心者向けに用語を最適化しないでください

初心者が Listの意味をより簡単に理解したとしても、 Listなどの単純化された用語を使用するより、連続したデータ構造はArrayと名付けるほうが良いです。数学のような特定のプログラミング領域で、sin(x)のような一般に先例のある用語は、verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)のような説明フレーズよりもベターです。 この場合「先例の受け入れ」が「略語を避けるガイドライン」よりも優先であることに注意してください。完全な単語はsine(正弦)ですがsin(x)は何十年もの間、プログラマ、そして数世紀にわたって数学者の間で共通して使用されていました。

Conventions(慣例)

一般的慣例

・O(1)でないcomputed propertyは複雑さについてコメントを書きましょう。人々はよく、プロパティアクセスのメンタルモデルとしてstored propertyの認識を持っているため、大した計算を伴わないと仮定します。 その前提にそぐわない可能性があるときは、必ず警告してください。

・free functionよりメソッドとプロパティを使用しましょう。free functionは、特別な場合にのみ使用されます。

1.明確なselfが無いような場合:

min(x, y, z)

2.関数に縛りがなく汎用的な場合:

print(x)

3.関数構文が特定領域の表記法の一部である場合:

sin(x) // 数学でsinは一般的


case規約を守りましょう。型名とプロトコル名はUpperCamelCaseです。それ以外はすべてlowerCamelCaseです。

アクロニム(例 NATO ナトー等)とイニシャリズム(例 FBI エフ・ビー・アイ)など頭字語(とうじご、略語)は、ふつうアメリカ英語ですべて大文字ですが、case規約に従って一様にup-もしくはdown-caseにすべきでしょう。 :

var utf8Bytes: [UTF8.CodeUnit] // 補足:イニシャリズム"UTF8"は変数名の先頭では小文字なのでutf8、型名は大文字なのでUTF8と記述
var isRepresentableAsASCII = true // 補足:アクロニム"ASCII"は変数名の先頭以外では大文字なのでASCIIと記述
var userSMTPServer: SecureSMTPServer // 補足:イニシャリズム"SMTP"は変数名の途中では大文字。型名の途中でもSMTPと記述。

・他の略語(アクロニム)は日常的な単語として扱われるべきでしょう。:

var radarDetector: RadarScanner // Radar(radio detection and rangingの略)レーダー、電波探知機
var enjoysScubaDiving = true // Scuba(self‐contained underwater breathing apparatus)スキューバ

・同じ基本的な意味を共有する場合、または異なるドメインで操作する場合、メソッドは基底名を共有できます

たとえば、メソッドが本質的に同じことを実行するため、以下のことが推奨されます。

/* OK */
extension Shape {
  /// Returns `true` iff `other` is within the area of `self`.
  /// other が self の範囲内にある場合は true を返します。
  func contains(_ other: Point) -> Bool { ... }

  /// Returns `true` iff `other` is entirely within the area of `self`.
  /// other が完全に self の範囲内にある場合は "true"を返します。
  func contains(_ other: Shape) -> Bool { ... }

  /// Returns `true` iff `other` is within the area of `self`.
  /// other が self の範囲内にある場合は true を返します。
  func contains(_ other: LineSegment) -> Bool { ... }
}

ジオメトリック型とコレクションは別々のドメインなので、これは同じプログラムでもうまくいきます:

/* OK */
extension Collection where Element : Equatable {
  /// Returns `true` iff `self` contains an element equal to
  /// `sought`.
  /// self に sought に等しい要素が含まれている場合は true を返します。
  func contains(_ sought: Element) -> Bool { ... }
}

しかし、これらのindexメソッドは異なるセマンティクスを持ち、異なる名前を付ける必要があります。:

/* NG */
extension Database {
  /// Rebuilds the database's search index
  /// データベースの検索インデックスを再構築します。
  func index() { ... }

  /// Returns the `n`th row in the given table.
  /// 与えられたテーブルの n 番目の行を返します。
  func index(_ n: Int, inTable: TableID) -> TableRow { ... }
}

最後に、型推論するときに曖昧さを引き起こすため「戻り値型のオーバーロード」を避けてください。

/* NG */
extension Box {
  /// Returns the `Int` stored in `self`, if any, and
  /// `nil` otherwise.
  /// self が格納されている場合は Int を、そうでない場合は nil を返します。
  func value() -> Int? { ... }

  /// Returns the `String` stored in `self`, if any, and
  /// `nil` otherwise.
  /// self に格納されている String を返します。それ以外の場合は nil を返します。
  func value() -> String? { ... }
}


Parameters(引数)

func move(from start: Point, to end: Point)

説明的なパラメータ名にしましょう。関数やメソッドを使用する時にパラメータ名は見えませんが、パラメータ名は重要な説明的役割を果たします。

ドキュメントを読みやすくするために、こんな命名をしてください。 たとえば、これらの名前はドキュメントを自然に読めるようにします。:

/* OK */
/// Return an `Array` containing the elements of `self`
/// that satisfy `predicate`.
/// predicateの条件を満たす self要素を含んだ Array を返します。
func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]

/// Replace the given `subRange` of elements with `newElements`.
/// 指定された subRange の要素を newElements に置換します。
mutating func replaceRange(_ subRange: Range, with newElements: [E])

以下の例は、ドキュメンテーションが扱いづらく、文法的でもありません。

/* NG */
/// Return an `Array` containing the elements of `self`
/// that satisfy `includedInResult`.
/// containedInResult を満たす self要素を含んだ Array を返します。
func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]

/// Replace the range of elements indicated by `r` with
/// the contents of `with`.
/// r で指定された範囲の要素を with の内容に置換します。
mutating func replaceRange(_ r: Range, with: [E])


一般的な利用を簡素化する時は、デフォルトパラメータを活用しましょう。 一般的に使用される値が1つの引数は、デフォルト引数の候補になります。

デフォルト引数は、無関係な情報を隠すことで可読性を向上させます。 例えば:

パラメータリストの最後にデフォルトでパラメータを配置することを推奨します。デフォルトを持たないパラメータは、通常、メソッドのセマンティクスにとってより重要であり、メソッドが呼び出されるときに安定した初期の使用パターンを提供します。

/* NG */
let order = lastName.compare(
  royalFamilyName, options: [], range: nil, locale: nil)

とてもシンプルにできます。

/* OK */
let order = lastName.compare(royalFamilyName)

**・デフォルト引数は、APIを理解しようとする人に認知的負担をかけることが少ないため、一般的にはメソッドファミリを使用するよりも好ましい方法です。
(補足:メソッドファミリ・・・ARCによってオーナーシップが発生するものと解釈される特定の文字列の名前がついたメソッド。)

/* OK */
extension String {
  /// ...description...
  public func compare(
     _ other: String, options: CompareOptions = [],
     range: Range? = nil, locale: Locale? = nil
  ) -> Ordering
}

上記はシンプルではないかもしれませんが、これよりかは遥かにシンプルでしょう。

/* NG */
extension String {
  /// ...description 1...
  public func compare(_ other: String) -> Ordering
  /// ...description 2...
  public func compare(_ other: String, options: CompareOptions) -> Ordering
  /// ...description 3...
  public func compare(
     _ other: String, options: CompareOptions, range: Range) -> Ordering
  /// ...description 4...
  public func compare(
     _ other: String, options: StringCompareOptions,
     range: Range, locale: Locale) -> Ordering
}

メソッドファミリのすべてのメンバは、ユーザによって個別に文書化され、理解される必要があります。その中から決定するためには、ユーザーはすべてのメンバを理解している必要があり、時には驚くべき関係もあります。–たとえばfoo(bar: nil)foo()は必ずしも同義ではありません。 例えば、これはほとんど同じ文書から小さな違いを見つけ出すような面倒なプロセスです。 デフォルト引数を使用したシングルメソッドを使用すると、プログラマーエクスペリエンスが大幅に向上します。

引数リストの最後にデフォルトパラメータを設定しましょう。 デフォルトを持たないパラメータは、通常、メソッドのセマンティクスにとってより重要であるため、メソッドを呼び出して使用するときの安定したイニシャルパターンを提供します。

Argument Labels(引数ラベル)

func move(from start: Point, to end: Point)
x.move(from: x, to: y) 

引数が有効に区別できない場合は、すべてのラベルを省略します。min(number1, number2)zip(sequence1, sequence2)

値の型変換を実行するイニシャライザでは、最初の引数のラベルを省略します。Int64(someUInt32)

最初の引数は常に変換元でなければなりません。

extension String {
  //  x を与えられた radix のテキスト表現に変換します。
  init(_ x: BigInt, radix: Int = 10)   ← Note the initial underscore
}

text = "The value is: "
text += String(veryLargeNumber)
text += " and in hexadecimal, it's"
text += String(veryLargeNumber, radix: 16)

ただし、「ナローイング」タイプの変換では、そのナローイングを表すラベルを使用することをお勧めします。

extension UInt32 {
  /// valueを持つインスタンスを作成します。
  init(_ value: Int16)            ← ワイドニング変換なのでラベル無し
  /// 最低32ビットの source を持つインスタンスを作成します。
  init(truncating source: UInt64)
  /// valueToApproximateの最も近く表現可能な近似値を持つインスタンスを作成します。
  init(saturating valueToApproximate: UInt64)
}

値の型変換はモノモーフィズムであり、つまり、ソースの値の違いがすべて結果の値の違いになります。 たとえば、Int8からInt64への変換では、すべてのInt8値がInt64値に変換できるため、値が保持されます。 しかし、他の方向への変換は、値を保持することができません。:Int64Int8で表現できる値よりも多くの可能な値を持ちます。 Note:元の値を取得する能力は、変換が値の保存であるかどうかには関係しません。

第一引数が前置詞句の一部を構成するときは、引数ラベルを付けましょう。引数ラベルは通常、前置詞で始まるべきです。例 x.removeBoxes(havingLength: 12)

最初の2つの引数がひとつの抽象化の一部を表すときは、例外が発生します。

/* NG */
a.move(toX: b, y: c)
a.fade(fromRed: b, green: c, blue: d)

そのような場合は、前置詞の後に引数ラベルを開始して、抽象化を明確にしてください。

/* OK */
a.moveTo(x: b, y: c)
a.fadeFrom(red: b, green: c, blue: d)

それ以外の場合、第一引数が文法的なフレーズの一部を構成する場合は、そのラベルを省略し、先行する単語を基底名に追加します。 例 x.addSubview(y)

このガイドラインは、第一引数が文法的なフレーズの一部を構成しない場合は、ラベルを持つ必要があることを意味します。

/* OK */
view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

フレーズが正しい意味を伝えることが重要であることに注意してください。次は文法的であるが、間違ったことを表現しています。

/* NG */
view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

デフォルト値の引数は省略することができ、その場合は文法的なフレーズの一部を構成しないので、常にラベルを付ける必要があることにも注意してください。

その他のすべての引数には、ラベルを付けます。

Special Instructions(特例)

APIにはclosure引数とtupleメンバをラベルしましょう。.

これらの名前は説明力があり、ドキュメンテーションコメントから参照したり、tupleメンバへの表現力豊かなアクセスを提供します。

/// 最小限 requestedCapacity 要素に対して一意に参照される記憶域を保持します。
///
/// さらに多くの記憶域が必要な場合は、割り当てられる最大アライメントのバイト数に等しい byteCount で allocate が呼び出されます。
///
/// - Returns:
///   - reallocated: `true` iff a new block of memory
///     was allocated.
///   - capacityChanged: `true` iff `capacity` was updated.
mutating func ensureUniqueStorage(
  minimumCapacity requestedCapacity: Int, 
  allocate: (byteCount: Int) -> UnsafePointer<Void>
) -> (reallocated: Bool, capacityChanged: Bool)

クロージャには技術的な引数ラベルが用いられますが、これらのラベルを選択して、それらが引数名であるかのようにドキュメントに使用する必要があります。 関数本体からのクロージャ呼び出しは、基底名で始まるフレーズの第一引数が含まれていない関数で、一貫して読み込まれます。

allocate(byteCount: newCount * elementSize)

オーバーロードセットの曖昧さを避けるため、制約のないポリモーフィズム(例 AnyAnyObject、および制約のない一般的なパラメータ)に注意しましょう。

たとえば、このオーバーロードセット。

/* NG */
struct Array {
  /// newElement を self.endIndex に挿入します。
  public mutating func append(_ newElement: Element)

  /// newElements の内容を self.endIndex に順に挿入します。
  public mutating func append(_ newElements: S)
    where S.Generator.Element == Element
}

これらのメソッドはセマンティックファミリを形成し、第一引数型はとてもはっきりします。ただし、ElementAnyの場合、1つのエレメントは一連のエレメントと同じ型を持つことができます。

/* NG */
var values: [Any] = [1, "a"]
values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?

曖昧さを解消するには、2番目のオーバーロードをより明示的に指定します。

/* OK */
struct Array {
  /// newElement を self.endIndex に挿入します。
  public mutating func append(_ newElement: Element)

  /// InewElements の内容を self.endIndex に順に挿入します。
  public mutating func append(contentsOf newElements: S)
    where S.Generator.Element == Element
}

新しい名前がドキュメンテーションコメントと、どうなふうによりマッチしたかに注目してください。 このケースでは、ドキュメンテーションコメントを書く行為が、実際のAPIの著者のissueへの着眼をもたらしました。

@snoozelagの感想

自分で訳してみると、けっこう発見や気づきが得られたように思います。後半は訳をすることに必死になって、要約は少し息切れしてしまってもいますが、原文に近い方が意味が取れやすい部分もあるかなとも思いました。ぎこちない部分には適宜修正更新を入れて行こうと思っています。
あと公式に、このような明確でわかりやすいAPI設計の指針が文書化されていることが大変ありがたいなと思いました。例えばJavaについて検索してしましたが、Java API Design Guidelines(個人の方の文書)といった感じでした。Swiftのサポート具合って最高かな、と思いました。 個人的な動機としては、英語が得意な訳でもないし、メソッド名に前置詞が付くパターン、ラベルに前置詞がつくパターン、メソッド名に型が付くパターン、引数名には役割を記述するなどなど、色々あるけど整理が付かなかった部分がきちんと資料の中で言及されていてことが嬉しかったです。 最初からよりよいメソッドの記述ができることによって、修正や間違いに気づきやすくして、コーディングのスピードアップも期待出来ると思いますね。
APIを記述する上で迷いやすい部分のプラクティスを学び、より良い記述を目指したいですね^^。


追記

ちなみにObjective-CAPI Design Guidelines 相当の資料になるようです。

https://developer.apple.com/jp/documentation/CodingGuidelines.pdf

Introduction to Coding Guidelines for Cocoa

【iOS】Universal Links と Custom URL Scheme についてのメモ

はじめに

「Custom URL Scheme (たぶんiOS4〜)」と「Universal Links (iOS9〜)」について。どちらも関連するアクションにより「アプリの起動」もしくは「ディープリンク(アプリを起動した上に特定の画面ヘ直接遷移すること)」に使用される技術です。
今秋はiOS11リリースが控えており、そろそろiOS8のサポートを打ち切ってiOS9以降のAPIの使用、切替へ本腰を入れる頃かと思い調査してみました。
参考になったURLと、ポイントと思った部分を箇条書きで抽出してみました。

Custom URL Scheme (たぶんiOS4〜)

<概要・実装手順>
[iOS] ディープリンク(Custom URL Scheme)でアプリを起動する | Developers.IO

ディープリンクを実装する時におすすめなライブラリ>
GitHub - joeldev/JLRoutes: URL routing library for iOS with a simple block-based API

Appleの公式ドキュメント>
About Apple URL Schemes

<iOS9以降での注意点、問題の解決法>
iOS9でカスタムURLスキームの遷移に失敗するときの注意点 - Qiita
iOS9のSafariにてiframe内でストアへリダイレクトさせると白画面で止まる問題 - 人間になりたい類人猿

・「Web ページからのアプリ遷移(後述の独自ディープリンクに応用可能)」「アプリ間の遷移」「異なる端末間の遷移」に使用可能。
・例えば、Web ページ等に URL Scheme を使ったリンクを設置しておき、タップされると iOSAndroid が対応するアプリを起動するなど。
・URL Scheme は myapp:// のような形式でアプリごとに設定。
・myapp:// が他のアプリと重複すると呼び出すアプリが不定となることがある。
・myapp://events/123/ のようにして, (引数として)追加の情報を URL に付加することができる。
ディープリンクに応用する場合、引数に対応してどのアプリ内のどのコンテンツに遷移するか、などが後述するUniversal Linksと違ってWebサイトのURL構成と違ってくるので、定義・独自に実装する。
Androidも同じCustom URL Scheme形式でアプリ起動などが実装できるので、サーバー側は両OSで同じような実装で対応が出来そうなところはメリット。

・以下のような挙動をさせたい場合は、WebページのJavaScript等で制御する必要がある模様。
  ∟アプリがインストール済み:アプリに自動遷移(iOS9以降は標準アプリ以外への遷移は「アプリで開く」確認ダイアログが表示される仕様)
  ∟アプリが未インストール: 通常通りWebページが開く
参考: カスタムURLスキームでアプリを起動させ、アプリが無ければストアに遷移させる - Qiita
JavaScriptは、URLスキーマ起動を試みて、500ms待ってもダメだったらストアに遷移するというような実装内容)

Universal Links (iOS9〜)

<概要>
URLスキーム・独自ディープリンク実装に代わる、Universal Links(iOS 9で導入)でより良いUXを実現 - Qiita

<実装手順>
Universal Linksを試してみました。関連づけファイル(apple-app-site-association)はS3に置きました。 | Developers.IO

Apple の公式ドキュメント>
App Searchプログラミングガイド:ユニバーサルリンクへの対応

・「ディープリンク」がAPIとして公式にサポートされた。
・アプリ起動の起因を、Web URL形式1つに統一できる。
iPhoneSafariで特定のHTTPのリンクをタップされた時(サーバー側にUniversal Linkの設定があれば)アプリが起動される。
・デフォルトで以下の挙動となる
  ∟アプリがインストール済み: アプリにシームレスに遷移(プッシュ遷移)
  ∟アプリが未インストール: 通常通りWebページが開く
・(設定が重複するようながことがなく)呼び出すアプリが不定となるようなことがない。
・使用方法:apple-app-site-associationというファイルをWebサイトに配置。アプリ側は設定と、遷移をハンドリングするコードを書く。
・問題点
  ∟iOS専用(カスタムURLスキーム形式はAndroidで使用可能)
  ∟iOS 9でしか使えない(現在もうこのデメリットはなくなりつつある)
  ∟WebサイトがSSL対応している必要がある(ATS対応で済ませていることと思う)

<ハマりどころ>
Universal Links の実装と嵌まりどころなど: シーサー渋谷ではたらく技術担当のブログ(SSGブログ)
・同ドメインのページ間をUniversal Links遷移しようとした場合にうまく機能しない場合がある。
・SFSafariViewController から Universal Links で呼び出すとステータスバーに[戻る]が出てこない。
・SFSafariViewController では Universal Links が必ず有効になりアプリに遷移してしまうのでWEBブラウズし続けられない。

まとめ

ディープリンクする時は、Custom URL Schemeでは確認ダイアログが出てしまうのでUniversal Linksの方がUXは良くなる。
・Custom URL SchemeAndroidにもあるが、Universal Linksのような仕組みはAndroidにはなくiOSのみっぽい。
adjustのようなライブラリを使用すると同じように管理出来るのかも)
というところが現状は大きいかなと思いました。カスタムURLスキームはアプリ間での遷移呼び出しでも使用されるので、 あくまでUniversal Linksはディープリンクに特化したAPIなのだな、と改めて思いました。


その他参考URL

URLスキームはダイアログが出るからUniversal Linksを試してみた - zono blog
iOS・Android のディープリンク技術のまとめ (2017年2月更新) – ymyzk’s blog
Custom URL Schemeの処理をシンプルに書く - Qiita
【iOS9】Universal Links を試す | MUSHIKAGO APPS MEMO

AutoLayout時代のUIViewサブクラス作法

細かな要件をスマートでコンパクトに満たすため、UIViewのカスタムクラスを作成したいと思いました。
UIViewには様々な描画サイクルに関連したメソッドがありますがどういう場合にoverrideし、どういうコードを記述すれば良いのでしょう。詳細な日本語の記事が無かったので調べてみました。

UIViewの描画サイクル3つのステップ

(1)制約の更新   AutoLayoutを使用している場合、制約の更新があれば設定する段階です。
(2)フレームの更新
更新された制約をもとにframeが設定される段階です。
最終的なframeに、手動レイアウトによるカスタマイズが可能です。
(3)レンダリング
CoreGraphicsフレームワークと関連。UIViewが持つCALayerをカスタムする際に使用します。
今回はレイアウトまわりの実装にフォーカスするため、割愛いたします。

制約の更新


// overrideポイント
override func updateConstraints() {
  /*
 ・・・
 制約の更新コードを記述
 ・・・
 */
  super.updateConstraints() // 最後に実行
  // !!self.setNeesdsUpdateConstraints()!!実行ループになってしまうので、ここで呼び出すのは間違いです
}


・自作のUIViewサブクラス(カスタムビュー)でオーバーライドが可能です。 ・主にサブクラス自身の制約の更新コードを記述します。
overrideしたら、最後にsuper.updateConstraints()を必ず呼ぶ必要があります。(ないとクラッシュします。)
なぜ最後に呼ぶかというと、ビューの階層とこのメソッドの呼び出し順序との関連があるからかと思います。
・呼び出したビューの最も深い階層のサブビューから、最上位のスーパービュー(=呼び出したビューまで)の順で実行されます。
・「制約に変更がなかった」「ビューが最上位にある」場合にupdateConstraints()は呼ばれないことがあります。
・もし、カスタムビューの制約プロパティが変更されていないときにも、updateConstraints()実行のスケジューリングしたい時は、

// システムによるupdateConstraints()実行の確実なスケジューリング
setNeedsUpdateConstraints()


を呼びだします。制約の更新が確実に行われたことが確認できます。
・カスタムビューが持つ制約プロパティに更新があれば、updateConstraints()の実行スケジューリングは自動的に行われます。
・setNeedsUpdateConstraints()は、updateConstraints()内で呼び出すと実行ループになってしまい、クラッシュしますので間違いです。あえてsetNeedsUpdateConstraints()をコールする場合は、そのviewを参照しているオブジェクトがsetNeedsUpdateConstraints()を呼び出すことが多いでしょう。
・setNeedsUpdateConstraints()は、
∟確実にupdateConstraints()の実行させる。
∟updateConstraints()で制約の更新以外の副次的な処理を実装している。 場合に有効かなと思いました。
・システムがレイアウトを更新するタイミングでカスタムビューに実装したupdateConstraints()が実行されるようになります。
例えばUIViewControllerのサイクルの通過時や、superviewからの更新呼び出し時です。

システムが行うより早くupdateConstraints()の実行したい場合は、

// 任意のタイミングでupdateConstraints()を実行
updateConstraintsIfNeeded()


をコールします。
・updateConstraints()は直接コールしないことが推奨されています。(いつでもシステムを通して同期をとり、効率的にビュー更新を行うためでしょう。)

どんな時にupdateConstraints()をオーバーライドするか

・自作のUIViewサブクラス自身が持っているsubviewの制約を変えたい時
AppleAPIリファレンスでは、

制約の変更が遅すぎる(重たい変更)場合
ビューが多数の冗長な変更を生成している場合
としています。それ以外では、制約の変更を行う場所で変更しましょう、と書かれてあります。
・変更が影響を受けた直後に制約を更新するほうが、よりクリーンで簡単です。たとえば、ボタンタップに応じて制約を変更する場合は、ボタンのアクションメソッドで直接変更を行います。
つまり制約の追加・削除は必ずupdateConstraints()内に記述しないといけない、ということではないです。
一度だけ実行されるような制約の追加は、イニシャライザとともに実行されるようにも記述しても問題ありません。
また、推奨として、
・すべての制約を無効にするような変更はせず、変更が必要な制約のみを変更するようにして、効率的な実装をしましょう。 とのこと。

参考:
updateConstraints() - UIView | Apple Developer Documentation

また、WWDCでは、以下のように言及されています。要約ですが、

・実際にはupdateConstraints()の実装が必要ではないことがよくあります。
・すべての初期制約設定は、理想的にはInterface Builder内で実行する必要があります。または、プログラムで制約を割り当てる必要があることが実際に分かった場合は、viewDidLoad方がはるかに優れています。
・実際には定期的に繰り返す必要がある変更を、記述するためのもの。
・描画エンジンがこのパスで発生するすべての制約の変更をバッチとして処理できるため updateConstraints内部での制約の変更は、他の時点での制約の変更よりも実際に速い。

参考:
How to Use updateConstraints – Ole Begemann

フレームの更新


// overrideポイント
override func layoutSubviews() {
   super.layoutSubviews()  // 最初に呼び出す
 /*
 ・・・
  サブビューのframeの操作などをここに記述
 ・・・
  */
}


・AutoLayoutを使用している場合はsuper.layoutSubviewsを最初に呼び出す必要があります。(しないとクラッシュする) super.layoutSubviewsを最初に呼び出すことによって、制約をもとにサブビューのframeが決定され、操作できるようになります。
・最上位のスーパービュー(呼び出したビュー自身)からそこに追加されているサブビューの順で、最下層のビューまで実行されます。
AutoLayoutを使用してないならば、デフォルトではlayoutSubviews()は何も行われません

// 通常のレイアウト更新の際にlayoutSubviews()が実行されるようにマーク
setNeedsLayout()  


でフラグを立てておき、通常のレイアウト更新の際にlayoutSubviews()が実行されます。
即座にlayoutSubviews()の実行したい場合は、

// 任意のタイミングでlayoutSubviews()を実行
layoutSubviewsIfNeeded()


のコールで間接的に実行します。システムによって、効率的なタイミングでlayoutSubviews()が実行されます。

どんな時にlayoutSubviews()をオーバーライドするか

・layoutSubviews時点では、すべての制約がframe値への置き換え計算が終わっているため、最終的なframeのアウトプットに手を加えたい時。
・手動Layout(frameを直接操作してレイアウトするような旧来の)での実装を行いたい場合。

APIリファレンスより

・デフォルト実装は、iOS 5.1以前では何も行いません。
・デフォルトの実装では、設定した制約を使用し、サブビューのサイズと位置が決定されます。 ・サブビューの自動サイズ調整および制約ベースの動作が必要な動作を提供しない場合にのみ、このメソッドをオーバーライドする必要があります。実装を使用して、サブビューのフレーム矩形を直接設定することができます。

参考:

layoutSubviews - UIView | Apple Developer Documentation

レンダリング

本記事ではレイアウトまわりに主眼を置いたため、割愛しました。

まとめ

・updateConstraints()・・・この中に制約の変更を記述することで、パフォーマンスの向上が期待できるが、書くとしたら定期的な制約の更新だろう。かなり重たい処理でなければ可読性の面から使わない方が良いことはAppleのリファレンスからわかった。例えば何らかのアクションがあった時にsetNeesdsUpdateConstraints()をコールし、離れた場所にあるupdateConstraints()を間接的に実行するよりも、制約の変更コードをそのままアクションが起こった場所(コールバック関数など)に記述することが推奨されていることがわかった。
・setNeedsUpdateConstraints()・・・updateConstraints()を後でシステムタイミングで実行したい時。
・updateConstraintsIfNeeded()・・・updateConstraints()を即時に実行したい時。
・layoutSubviews()・・・overrideすることで手動レイアウトでframeをカスタマイズ出来ることがわかった。
・setNeedsLayout()・・・layoutSubviews()を後でシステムタイミングで実行したい時。
・layoutSubviewsIfNeeded()・・・layoutSubviews()を即時に実行したい時。

UIViewのupdateConstraints()やlayoutSubviews()は、UIViewControllerのviewDidLoad()、viewWillAppear()と、雰囲気が似ている感じがしていましたが、単に実行タイミングだけの感覚でオーバーライドするメソッドとは少し性質の違うもののように思いました。もやもやしていたのが、少しはっきりしたように思います。

参考

UIViewControllerのライフサイクル - Qiita
iOSエンジニア必見!!iOSのレイアウトで押さえておきたいこと【総集編】 | eureka tech blog
詳解 iOS SDK 第4版 ―ワンランク上のiPhone/iPadプログラミング
他、本文中にURLを記載


スポンサードリンク