Skip to content
This repository has been archived by the owner on Oct 27, 2021. It is now read-only.

Latest commit

 

History

History
1659 lines (1315 loc) · 40.4 KB

README_jp.md

File metadata and controls

1659 lines (1315 loc) · 40.4 KB

Note: This repository is no longer being maintained.

eureka

eureka Swift Style Guide

English | 日本語

このスタイルガイドは、弊社のiOSチームが1年以上Swiftのコードを書いて、レビューをして、検証を重ねて培ってきたものです。弊社がリリースしているアプリに適用して、優れていると判断したコーディングルールを反映しました。

ここに記されたルールは次の目的を達成もしくはサポートしようとするものです。

  • プログラマー自身のエラーを減らして、さらにエラーを見つけやすくする
  • コードの可読性と明快さを向上させる(他の人がコードをレビューもしくは書き換えると仮定して)
  • コードが不必要に肥大することを抑える
  • チームメンバーの多数が支持するコーディングの美学を守る

このガイドラインの多くの項目は批判されたり、もしかしたらSwiftコミュニティの一般的なルールとは真逆のことかもしれません。しかし、弊社はチーム開発においてこれらのプラクティスを試して、検証してきました。そして、弊社では上手くいっています。

このガイドラインは未完成であり、変更される可能性があります。弊社のアプリが成長したり、チームで改善をしたり、Swift自体が進化を遂げたりすることに従って、このガイドラインも必要に応じて更新していきます。

目次

スタイルと慣習

フォーマット

セミコロン

文末にセミコロン(;)は付けない。

OKNG
self.backgroundColor = UIColor.whiteColor()
self.completion = { 
    // ...
}
self.backgroundColor = UIColor.whiteColor();
self.completion = { 
    // ...
};

理由: 文末にセミコロンを付けることで実用的なメリットはありません。しかし、Objective-Cのコードをコピー&ペーストしたことが一目瞭然というメリットだけがあります。

スペース

タブで半角スペース4つ分を使用する。

XcodeのText Editingで以下のように設定します。

screen shot 2016-02-10 at 15 29 59

理由: 異なるテキストエディタ間でインデントを揃えるためです。

ソースファイルの文末に空行を1行だけ入れる。

OKNG
class Button {
  // ...
}
// <-- ここに1行を入れます
class Button {
  // ...
} // <-- 空行がありません
class Button {
  // ...
}
// <-- ここに1行を入れます
// <-- ここの1行は余分です

理由: 文末に空行がないことで起きるエラーを防ぎ、コミットのdiffのノイズを減らします。

メソッドとメソッドの間に1行以上の空行を入れる。

OK
class BaseViewController: UIViewController {
    // ...
override viewDidLoad() {
    // ...
}

override viewWillAppear(animated: Bool) {
    // ...
}

}

理由: コードのブロックとブロックの間に十分な余白を持たせるためです。

オペレーターの定義とコール時にオペレーターの周りに半角スペースを1つ入れる。

OKNG
func <| (lhs: Int, rhs: Int) -> Int {
    // ...
}
let value = 1 <| 2
func <|(lhs: Int, rhs: Int) -> Int {
    // ...
}
let value = 1<|2

理由: 可読性のためです。

メソッドのreturnの矢印(->)の周りに半角スペースを1つ入れる。

OKNG
func doSomething(value: Int) -> Int {
    // ...
}
func doSomething(value: Int)->Int {
    // ...
}

理由: 可読性のためです。

コンマ

コンマ(,)の前にはスペースを入れず、後ろには半角スペースを1つ入れるか、新しい行にする。

OKNG
let array = [1, 2, 3]
let array = [1,2,3]
let array = [1 ,2 ,3]
let array = [1 , 2 , 3]
self.presentViewController(
    controller,
    animated: true,
    completion: nil
)
self.presentViewController(
    controller ,
    animated: true,completion: nil
)

理由: コンマで分けたものが一目で分けられていると判別できるようにします。

コロン

型を示すコロン(:)は後ろに1つの半角スペースを入れて、前には半角スペースを入れない。

OKNG
func createItem(item: Item)
func createItem(item:Item)
func createItem(item :Item)
func createItem(item : Item)
var item: Item? = nil
var item:Item? = nil
var item :Item? = nil
var item : Item? = nil

理由: コロンは左側のオブジェクトを説明するためであり、右側を説明するためではありません。

case文のコロン(:)は前に半角スペースを入れず、後ろには半角スペースを1つもしくは改行を入れる。

OKNG
switch result {
case .Success:
self.completion()
case .Failure:
self.failure()
}
switch result {
case .Success :
self.completion()
case .Failure:self.reportError()
}

理由: 前項の理由と同様で、コロンは左側のオブジェクトを説明するためであり、右側を説明するためではありません。

括弧

始め波括弧({)は前の文字との間に半角スペースを1つ入れる。

OKNG
class Icon {
    // ...
}
class Icon{
    // ...
}
let block = { () -> Void in
    // ...
}
let block ={ () -> Void in
    // ...
}

理由: 宣言部分と実装部分を分けるためです。

型の宣言、メソッド、クロージャの始め波括弧({)の次に空行を1行入れる。1文だけのクロージャは1行で書くことができる。

OKNG
class Icon {
let image: UIImage
var completion: (() -> Void)

init(image: UIImage) {

    self.image = image
    self.completion = { [weak self] in self?.didComplete() }
}

func doSomething() {

    self.doSomethingElse()
}

}
class Icon {
    let image: UIImage
init(image: UIImage) {
    self.image = image
    self.completion = { [weak self] in print("done"); self?.didComplete() }
}

func doSomething() { self.doSomethingElse() }

}

理由: コードを読んでいる時に十分な余白を持たせるためです。

実装部分のない宣言は空の波括弧({})で書く。もしくは、実装部分のない理由をコメントで説明する。

OKNG
extension Icon: Equatable {}
extension Icon: Equatable {
}
var didTap: () -> Void = {}
override func drawRect(rect: CGRect) {}
@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
// 何もしていない。このデリゲートメソッドはNSFetchedResultsControllerのトラッキングモードを有効にするため。

}
var didTap: () -> Void = { }
override func drawRect(rect: CGRect) {
}
@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
}

理由: 宣言部分を意図的に空にしているのであって、TODOの書き忘れでないことを明確にするためです。

終わり波括弧(})の前に空行を入れない。1行で波括弧が閉じている場合は、終わり波括弧(})の前に半角スペースを1つ入れる。

OKNG
class Button {
var didTap: (sender: Button) -> Void = { _ in }

func tap() {

    self.didTap()
}

}
class Button {
var didTap: (sender: Button) -> Void = {_ in}

func tap() {

    self.didTap()
    
}

}

理由: コードを短くしつつ、宣言と宣言の間に余白を入れるためです。

終わり波括弧(})が対の始め波括弧({)と違う行にある場合、終わり波括弧(})は宣言部分の左端に揃える。

OKNG
lazy var largeImage: UIImage = { () -> UIImage in
let image = // ...
return image

}()
lazy var largeImage: UIImage = { () -> UIImage in
let image = // ...
return image
}()

理由: 終わり波括弧が左端に揃っていることで、視覚的にスコープを把握しやすいからです。このルールは下のフォーマットガイドラインに基づいています。

プロパティ

getsetとその終わり波括弧(})は全て左端で揃える。1行で記述できる場合、getsetの宣言は1行で記述する。

括弧のルールを適用しています。

OKNG
struct Rectangle {
// ...
var right: Float {

    get {
    
        return self.x + self.width
    }
    set {
    
        self.x = newValue - self.width
    }
}

}
struct Rectangle {
// ...
var right: Float {

    get
    {
        return self.x + self.width
    }
    set
    {
        self.x = newValue - self.width
    }
}

}
struct Rectangle {
// ...
var right: Float {

    get { return self.x + self.width }
    set { self.x = newValue - self.width }
}

}
struct Rectangle {
// ...
var right: Float {

    get { return self.x + self.width }
    set { self.x = newValue - self.width
        print(self) 
    }
}

}

理由: 括弧のルールと合わせて、このフォーマットは一貫性と可読性を向上させます。

読み取り専用のComputed propertyはgetを省略する。

OKNG
struct Rectangle {
// ...
var right: Float {

    return self.x + self.width
}

}
struct Rectangle {
// ...
var right: Float {

    get {
    
        return self.x + self.width
    }
}

}

理由: returnが読み取り専用であることを十分明確にしており、より簡潔な記述を可能にしています。

条件分岐

ifelseswitchdocatchrepeatguardforwhiledefer文は対になっている終わり波括弧(})と左端に揃える。

括弧のルールを適用しています。

OKNG
if array.isEmpty {
    // ...
}
else {
    // ...
}
if array.isEmpty {
    // ...
} else {
    // ...
}
if array.isEmpty
{
    // ...
}
else
{
    // ...
}

理由: 括弧のルールと合わせて、このフォーマットは一貫性と可読性を向上させます。条件分岐文と終わり波括弧が視覚的にスコープを分かりやすくしています。

case文はswitch文と左端で揃える。1行のcase 文を書くことも可能である。複数行のcase文はcase:の後でインデントを1つ下げ、空行1行で分ける。

括弧のルールを適用しています。

OKNG
switch result {
case .Success:
self.doSomething()
self.doSomethingElse()
case .Failure:
self.doSomething()
self.doSomethingElse()
}
switch result {
case .Success: self.doSomething()
self.doSomethingElse()
case .Failure: self.doSomething()
self.doSomethingElse()
}
switch result {
case .Success: self.doSomething()
case .Failure: self.doSomethingElse()
}
switch result {
case .Success: self.doSomething()
case .Failure: self.doSomethingElse()

}

理由: Xcodeのオートインデントで適用されるインデントです。また、複数行の場合、case文を空行で分けることは視認性を向上させます。

ifswitchforwhile文の条件式は丸括弧(())で囲まない。

OKNG
if array.isEmpty {
    // ...
}
if (array.isEmpty) {
    // ...
}

理由: Objective-Cではないからです。

出来る限りネストを避け、早い段階でreturnする。

OKNG
guard let strongSelf = self else {
return

}
// strongSelfを使用したコードをここに書きます
if let strongSelf = self {
// strongSelfを使用したコードをここに書きます

}

理由: 追うべきネストが多くなればなるほど、コードを追いかけることが困難になります。

命名

命名規則はそのほとんどがAppleの命名規則に基づいています。

大文字、小文字

classstructenumprotocolの型の名前はUpperCamelCaseにする。

OKNG
class ImageButton {
enum ButtonState {
	// ...
}

}
class image_button {
enum buttonState {
	// ...
}

}

理由: 統一性を持たせるためにAppleの命名規則を採用しています。

enumOptionSetTypeの値をUpperCamelCaseにする。

OKNG
enum ErrorCode {
case Unknown
case NetworkNotFound
case InvalidParameters

}
struct CacheOptions : OptionSetType {
static let None = CacheOptions(rawValue: 0)
static let MemoryOnly = CacheOptions(rawValue: 1)
static let DiskOnly = CacheOptions(rawValue: 2)
static let All: CacheOptions = [.MemoryOnly, .DiskOnly]
// ...

}
enum ErrorCode {
case unknown
case network_not_found
case invalidParameters

}
struct CacheOptions : OptionSetType {
static let none = CacheOptions(rawValue: 0)
static let memory_only = CacheOptions(rawValue: 1)
static let diskOnly = CacheOptions(rawValue: 2)
static let all: CacheOptions = [.memory_only, .diskOnly]
// ...

}

理由: 統一性を持たせるためにAppleの命名規則を採用しています。

変数名と関数名はlowerCamelCaseにする(静的なものや定数も含める)。頭文字語は例外としてUPPERCASEとする。

OKNG
var webView: UIWebView?
var URLString: String?
func didTapReloadButton() {
// ..
}
var web_view: UIWebView?
var urlString: String?
func DidTapReloadButton() {
// ..
}

理由: 統一性を持たせるためにAppleの命名規則を採用しています。頭文字語に関しては、Upper caseの読みやすさを重視するからです。

名前の付け方

1文字の名前を型、変数、関数に付けない。イテレータのインデックスの場所のみ1文字の変数を使用する。

OKNG
for (i, value) in array.enumerate() {
    // ... "i" is well known
}
for (i, v) in array.enumerate() {
    // ... what's "v"?
}

理由: 1文字の名前より良い名前は必ずあります。iに関しては、indexを使用するよりも可読性が高いです。

出来る限り省略された名前を付けない(ただし、こちらは使用して良い(例えば、min/max))。

OKNG
let errorCode = error.code
let err = error.code

理由: 僅かに短くなることより、明快さの方が重要です。

名前はそれが何であるかと何のためであるかを出来る限り表すものを付ける。

OKNG
class Article {
var title: String

}
class Article {
var text: String
// これは記事のタイトルかコンテンツか分からない

}
Better
class NewsArticle {
var headlineTitle: String

}

理由: 僅かに短くなることより、明快さの方が重要です。また、その特徴を表す名前を付けることで、他の名前と衝突することを避けることができます。

URLに限っては、文字列とNSURLを区別するために名前の末尾に~Stringを付ける。

OKNG
var requestURL: NSURL
var sourceURLString: String
func loadURL(URL: NSURL) {
// ...
}
func loadURLString(URLString: String) {
// ...
}
var requestURL: NSURL
var sourceURL: String
func loadURL(URL: NSURL) {
// ...
}
func loadURL(URL: String) {
// ...
}

理由: 正しい型を知るために宣言部分をチェックする時間を節約できます。

classstructenumprotocolなどを名前に含めない。

OKNG
class User {
    // ...
}
enum Result {
// ...
}
protocol Queryable {
// ...
}
class UserClass {
    // ...
}
enum ResultEnum {
// ...
}
protocol QueryableProtocol {
// ...
}

理由: 余分な接尾辞は無駄です。ただし、Objective-Cのクラスと同一名のプロトコルは~Protocolの接尾辞を付けてSwiftにブリッジングされることに注意してください(例えば、NSObjectNSObjectProtocol)。このことはSwiftのコンパイラが自動生成したものであり、このガイドラインとは関係ありません。

依存関係

Import文

import文はOS固有のフレームワークと外部フレームワークとの間に空行を1行入れて、アルファベット順に並べる。

OKNG
import Foundation
import UIKit
import Alamofire
import Cartography
import SwiftyJSON
import Foundation
import Alamofire
import SwiftyJSON
import UIKit
import Cartography

理由: ブランチ間で依存関係の記述が変更された時、マージコンフリクトを減らします。

宣言の順序

classstructenumextensionprotocolなどの全ての宣言は

// MARK: - <宣言の名前>を付ける。

OKNG
// MARK: - Icon
class Icon {
// MARK: - CornerType

enum CornerType {

    case Square
    case Rounded
}
// ...

}
// Icon
class Icon {
// MARK: CornerType

enum CornerType {

    case Square
    case Rounded
}
// ...

}

理由: XcodeのSource Navigatorを使用して、特定の型にジャンプすることが容易になります。

全てのプロパティとメソッドはスーパークラスやプロトコル毎にグループ分けをして、// MARK: <スーパークラス/プロトコルの名前>を付ける。その他はアクセスレベルに応じて、// MARK: Public// MARK: Internal// MARK: Privateをそれぞれ付ける。

OK
// MARK: - BaseViewController
class BaseViewController: UIViewController, UIScrollViewDelegate {
// MARK: Internal

weak var scrollView: UIScrollView?


// MARK: UIViewController

override func viewDidLoad() {
    // ...
}

override func viewWillAppear(animated: Bool) {
    // ...
}


// MARK: UIScrollViewDelegate

@objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
    // ...
}


// MARK: Private

private var lastOffset = CGPoint.zero

}

理由: ソースコード上のどこにプロパティやメソッドが宣言されているかを容易に特定できます。

// MARK:タグは上に2行、下に1行の空行を入れる。

OKNG
import UIKit
// MARK: - BaseViewController
class BaseViewController: UIViewController {
// MARK: Internal

weak var scrollView: UIScrollView?


// MARK: UIViewController

override func viewDidLoad() {
    // ...
}

override func viewWillAppear(animated: Bool) {
    // ...
}


// MARK: Private

private var lastOffset = CGPoint.zero

}
import UIKit
// MARK: - BaseViewController
class BaseViewController: UIViewController {
// MARK: Internal
weak var scrollView: UIScrollView?

// MARK: UIViewController
override func viewDidLoad() {
    // ...
}

override func viewWillAppear(animated: Bool) {
    // ...
}

// MARK: Private
private var lastOffset = CGPoint.zero

}

理由: コーディングの美学に尽きます。また、型の定義やメソッドのグループの間に十分な余白を与えます。

// MARK:タグによるグルーピングは次の順番にする:

  • // MARK: Public
  • // MARK: Internal
  • Classの継承 (最上位の親から子へ)
    • // MARK: NSObject
    • // MARK: UIResponder
    • // MARK: UIViewController
  • Protocolの継承 (最上位の親から子へ)
    • // MARK: UITableViewDataSource
    • // MARK: UIScrollViewDelegate
    • // MARK: UITableViewDelegate
  • // MARK: Private

理由: ソースコード上のどこにプロパティやメソッドが宣言されているかを容易に特定できます。publicinternalの宣言はAPI利用者に最も参照される部分であるため、一番上に配置しています。

上記のグルーピングの内部では以下の順序にする:

  • @が付くプロパティ(@NSManaged, @IBOutlet, @IBInspectable, @objc, @nonobjcなど)
  • lazy var
  • Computed property(var)
  • Stored property(var)
  • letプロパティ
  • @が付く関数(@NSManaged, @IBAction, @objc, @nonobjcなど)
  • その他の関数

理由: @が付くプロパティと関数は最も参照されやすいため(KVCのキーやSelectorの文字列をチェックしたり、Interface Builderと相互に参照し合うなど)、上部に定義しています。

ベストプラクティス

基本的に、**全てのXcodeのWarningsは無視すべきではありません。**例えば、出来る限りvarよりもletを使用する、使用していない変数は_を使用するなどです。

コメント

コメントは「なぜ?」という問いに答えるものであり、それ以外のことはコード自体が説明すべきである。

OKNG
let leftMargin: CGFloat = 20
view.frame.x = leftMargin
view.frame.x = 20 // left margin
@objc dynamic func tableView(tableView: UITableView,
 heightForHeaderInSection section: Int) -> CGFloat {
return 0.01 // tableViewは0を無視してしまうため

}
@objc dynamic func tableView(tableView: UITableView,
 heightForHeaderInSection section: Int) -> CGFloat {
return 0.01 // 小さい数字を返す

}

理由: 最も良いコメントは書いた人自身は必要のないものです。もしコメントを書く必要があるならば、そのコードを書いた理由を説明するべきであり、単に明らかなことを説明するべきではありません。

一時的にローカライズされていない文字列は// TODO: localizeを付ける。

OKNG
self.titleLabel.text = "Date Today:" // TODO: localize
self.titleLabel.text = "Date Today:"

理由: 実装した機能をデバッグしてテストをする場合、大抵はネイティブの言語を使用します。そして、翻訳された文字列は分けてテストされます。このガイドラインにより、全ての未翻訳の文字列が説明され、後で見つけることが容易になります。

ダイナミックと安全性

Objective-Cのprotocolの実装部分(プロパティとメソッドの両方とも)は@objc dynamicを最初に付ける。

OKNG
@objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
    // ...
}
func scrollViewDidScroll(scrollView: UIScrollView) {
    // ...
}

理由: コンパイラの最適化による解決困難なバグを防ぎます。

IBActionIBOutletdynamicを付ける。

OK
@IBOutlet private dynamic weak var closeButton: UIButton?
@IBAction private dynamic func closeButtonTouchUpInside(sender: UIButton) {
// ...
}

理由: Swiftコンパイラはたまにdynamicとして扱ってくれません。dynamicを書くことで、privateで宣言していたとしても安全性を保証します。

KVCやKVOに使用するプロパティとSelectorとして使用するメソッドはdynamicを付ける。

OK
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UITapGestureRecognizer(target: self, action: "tapGestureRecognized:")
self.view.addGestureRecognizer(gesture)

}
private dynamic func tapGestureRecognized(sender: UITapGestureRecognizer) {
// ...
}

理由: 1つ前のルールと同じ理由です。Swiftコンパイラはたまにdynamicとして扱ってくれません。dynamicを書くことで、privateで宣言していたとしても安全性を保証します。

@IBOutletweakで宣言して、型はImplicitlyUnwrappedOptional(!)ではなくOptional(?)とする。

OKNG
@IBOutlet dynamic weak var profileIcon: UIImageView?
@IBOutlet var profileIcon: UIImageView!

理由: サブクラスがその@IBOutletのビューを生成しなかったとしても安全性を保証します。また、viewDidLoad(_:)の前にプロパティにアクセスすることでクラッシュすることを防ぎます。

アクセス修飾子

privateとして宣言することをデフォルトとして、必要なときだけinternalまたはpublicとして外部に公開する。

理由: Xcodeの補完を必要のないプロパティやメソッドで汚してしまうことを防ぐことができます。また、理論的には、コンパイラが最適化をさらにして、ビルドが速くなるでしょう。

ライブラリのモジュール:全ての宣言は明示的にpublicinternalprivateのいずれかを付ける。

</tab

理由: APIの使用者にとって意図したことを明確にすることができます。

アプリケーションのモジュール:publicはプロトコルで必要な場合を除いて使用しない。internalは書いても書かなくても良い。privateは必ず書く。

OKNG
private let defaultTimeout: NSTimeInterval = 30
internal class NetworkRequest {
// ...
}
let defaultTimeout: NSTimeInterval = 30
class NetworkRequest {
// ...
}

OKNG
private let someGlobal = "someValue"
class AppDelegate {
// ...
private var isForeground = false
}
public let someGlobal = "someValue"
public class AppDelegate {
// ...
var isForeground = false
}

理由: Appバンドルでpublicは役に立ちません。すなわち、アクセス修飾子はinternalprivateのいずれかと推測できます。この場合、privateを明示するだけで十分です。

アクセス修飾子は@以外の修飾子の前に付ける。

OKNG
@objc internal class User: NSManagedObject {
    // ...
    @NSManaged internal dynamic var identifier: Int
    // ...
    @NSManaged private dynamic var internalCache: NSData?
}
internal @objc class User: NSManagedObject {
    // ...
    @NSManaged dynamic internal var identifier: Int
    // ...
    private @NSManaged dynamic var internalCache: NSData?
}

理由: 宣言の順序のルールと合わせて、縦方向にコードを流し読みしている時に、可読性が増します。

型推論

必要な場合を除いて、変数やプロパティの型は宣言文の左側か右側のいずれか片側から推測できるようにする。

OKNG
var backgroundColor = UIColor.whiteColor()
var iconView = UIImageView(image)
var backgroundColor: UIColor = UIColor.whiteColor()
var iconView: UIImageView = UIImageView(image)
var lineBreakMode = NSLineBreakMode.ByWordWrapping
// or
var lineBreakMode: NSLineBreakMode = .ByWordWrapping
var lineBreakMode: NSLineBreakMode = NSLineBreakMode.ByWordWrapping

理由: 冗長になることを防ぐためです。また、ジェネリクスの型にバインドされるときの曖昧さを減らします。

リテラルの型(StringLiteralConvertibleNilLiteralConvertibleなど)が関係するときは、asで直接キャストするよりも明示的に型を宣言する。

OKNG
var radius: CGFloat = 0
var length = CGFloat(0)
var radius: CGFloat = CGFloat(0)
var length = 0 as CGFloat // キャストするよりもイニシャライザの方が良い

理由: 冗長になることを防ぐためです。また、ジェネリクスの型にバインドされるときの曖昧さを減らします。

Collections / SequenceTypes

.countはその値自体が必要な場合のみ使用する。

OK
let badgeNumber = unreadItems.count

空かどうかをチェックするとき:

OKNG
if sequence.isEmpty {
// ...
if sequence.count <= 0 {
// ...

最初か最後の要素を取得するとき:

OKNG
let first = sequence.first
let last = sequence.last
let first = sequence[0]
let last = sequence[sequence.count - 1]

最初か最後の要素を削除するとき:

OKNG
sequence.removeFirst()
sequence.removeLast()
sequence.removeAtIndex(0)
sequence.removeAtIndex(sequence.count - 1)

全てのインデックスでイテレートするとき:

OKNG
for i in sequence.indices {
    // ...
}
for i in 0 ..< sequence.count {
    // ...
}

最初か最後のインデックスを取得するとき:

OKNG
let first = sequence.indices.first
let last = sequence.indices.last
let first = 0
let last = sequence.count - 1

最後のn個以外全てのインデックスでイテレートするとき:

OKNG
for i in sequence.indices.dropLast(n) {
    // ...
}
for i in 0 ..< (sequence.count - n) {
    // ...
}

最初のn個以外全てのインデックスでイテレートするとき:

OKNG
for i in sequence.indices.dropFirst(n) {
    // ...
}
for i in n ..< sequence.count {
    // ...
}

基本的に、countの値に足したり、引いたりして使用したい場合は、Swiftらしい書き方でもっと良い書き方があるでしょう。

理由: 意図していることが明確になり、ミスを減らすことができます。特にOff-by-oneエラーを減らすことができます。

循環参照を防ぐ

特に、このルールはこれまでずっと議論されてきたselfを使用するか否かをカバーする内容です。

インスタンスのプロパティとメソッドはクロージャ内を含めて、必ずselfを付ける。

(この意図は次のルールで説明します)

OKNG
self.animatableViews.forEach { view in
self.animateView(view)

}
animatableViews.forEach { view in
animateView(view)

}

理由: selfを付けるか付けないかを判断するよりも、必ずselfを付けるようにすることで、ミスを少なくすることができることが分かりました。すなわち、このルールは循環参照に関するルールが必要であることを意味しています。詳細は下のルールをご覧ください。

@noescape・関数・スタティックメソッドのクロージャ以外のクロージャ内でselfにアクセスする場合、必ず[weak self]を使用する。

OKNG
self.request.downloadImage(
    url,
    completion: { [weak self] image in
    self?.didDownloadImage(image)
}

)
self.request.downloadImage(
    url,
    completion: { image in
    self.didDownloadImage(image) // 循環参照
}

)

理由: 上記のselfが必須となるルールと合わせて、循環参照が起きうる箇所を簡単に特定できるようになります。selfにアクセスしているクロージャを探し、[weak self]が抜けていないかを確認するだけです。

クロージャ内で参照をキャプチャするのにunownedを使用しない。

OKNG
self.request.downloadImage(
    url,
    completion: { [weak self] image in
    self?.didDownloadImage(image)
}

)
self.request.downloadImage(
    url,
    completion: { [unowned self] image in
    self.didDownloadImage(image)
}

)

理由: weakよりもunownedの方が便利ですが(Optionalとして扱う必要がないため)、クラッシュを起こしやすくなります。誰もゾンビオブジェクトは好ましくないでしょう。

クロージャ内でweakなselfをアンラップする必要がある場合、`self`に対してバインドする。

OKNG
self.request.downloadImage(
    url,
    completion: { [weak self] image in
    guard let `self` = self else { 
    
        return
    }
    self.didDownloadImage(image)
    self.reloadData()
    self.doSomethingElse()
}

)
self.request.downloadImage(
    url,
    completion: { [weak self] image in
    guard let strongSelf = self else { 
    
        return
    }
    strongSelf.didDownloadImage(image)
    strongSelf.reloadData()
    strongSelf.doSomethingElse()
}

)

理由: シンタックスハイライトを有効にするためです。


トップへ戻る