Note: This repository is no longer being maintained.
このスタイルガイドは、弊社のiOSチームが1年以上Swiftのコードを書いて、レビューをして、検証を重ねて培ってきたものです。弊社がリリースしているアプリに適用して、優れていると判断したコーディングルールを反映しました。
ここに記されたルールは次の目的を達成もしくはサポートしようとするものです。
- プログラマー自身のエラーを減らして、さらにエラーを見つけやすくする
- コードの可読性と明快さを向上させる(他の人がコードをレビューもしくは書き換えると仮定して)
- コードが不必要に肥大することを抑える
- チームメンバーの多数が支持するコーディングの美学を守る
このガイドラインの多くの項目は批判されたり、もしかしたらSwiftコミュニティの一般的なルールとは真逆のことかもしれません。しかし、弊社はチーム開発においてこれらのプラクティスを試して、検証してきました。そして、弊社では上手くいっています。
このガイドラインは未完成であり、変更される可能性があります。弊社のアプリが成長したり、チームで改善をしたり、Swift自体が進化を遂げたりすることに従って、このガイドラインも必要に応じて更新していきます。
| OK | NG |
|---|---|
self.backgroundColor = UIColor.whiteColor()
self.completion = {
// ...
} |
self.backgroundColor = UIColor.whiteColor();
self.completion = {
// ...
}; |
理由: 文末にセミコロンを付けることで実用的なメリットはありません。しかし、Objective-Cのコードをコピー&ペーストしたことが一目瞭然というメリットだけがあります。
XcodeのText Editingで以下のように設定します。
理由: 異なるテキストエディタ間でインデントを揃えるためです。
| OK | NG |
|---|---|
class Button {
// ...
}
// <-- ここに1行を入れます |
class Button {
// ...
} // <-- 空行がありません |
class Button {
// ...
}
// <-- ここに1行を入れます
// <-- ここの1行は余分です |
理由: 文末に空行がないことで起きるエラーを防ぎ、コミットのdiffのノイズを減らします。
| OK |
|---|
class BaseViewController: UIViewController {
// ...
override viewDidLoad() {
// ...
}
override viewWillAppear(animated: Bool) {
// ...
}
} |
理由: コードのブロックとブロックの間に十分な余白を持たせるためです。
| OK | NG |
|---|---|
func <| (lhs: Int, rhs: Int) -> Int {
// ...
}
let value = 1 <| 2 |
func <|(lhs: Int, rhs: Int) -> Int {
// ...
}
let value = 1<|2 |
理由: 可読性のためです。
| OK | NG |
|---|---|
func doSomething(value: Int) -> Int {
// ...
} |
func doSomething(value: Int)->Int {
// ...
} |
理由: 可読性のためです。
| OK | NG |
|---|---|
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
) |
理由: コンマで分けたものが一目で分けられていると判別できるようにします。
| OK | NG |
|---|---|
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 |
理由: コロンは左側のオブジェクトを説明するためであり、右側を説明するためではありません。
| OK | NG |
|---|---|
switch result {
case .Success:
self.completion()
case .Failure:
self.failure()
} |
switch result {
case .Success :
self.completion()
case .Failure:self.reportError()
} |
理由: 前項の理由と同様で、コロンは左側のオブジェクトを説明するためであり、右側を説明するためではありません。
| OK | NG |
|---|---|
class Icon {
// ...
} |
class Icon{
// ...
} |
let block = { () -> Void in
// ...
} |
let block ={ () -> Void in
// ...
} |
理由: 宣言部分と実装部分を分けるためです。
| OK | NG |
|---|---|
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() }
} |
理由: コードを読んでいる時に十分な余白を持たせるためです。
| OK | NG |
|---|---|
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の書き忘れでないことを明確にするためです。
| OK | NG |
|---|---|
class Button {
var didTap: (sender: Button) -> Void = { _ in }
func tap() {
self.didTap()
}
} |
class Button {
var didTap: (sender: Button) -> Void = {_ in}
func tap() {
self.didTap()
}
} |
理由: コードを短くしつつ、宣言と宣言の間に余白を入れるためです。
| OK | NG |
|---|---|
lazy var largeImage: UIImage = { () -> UIImage in
let image = // ...
return image
}() |
lazy var largeImage: UIImage = { () -> UIImage in
let image = // ...
return image
}() |
理由: 終わり波括弧が左端に揃っていることで、視覚的にスコープを把握しやすいからです。このルールは下のフォーマットガイドラインに基づいています。
括弧のルールを適用しています。
| OK | NG |
|---|---|
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)
}
}
} |
理由: 括弧のルールと合わせて、このフォーマットは一貫性と可読性を向上させます。
| OK | NG |
|---|---|
struct Rectangle {
// ...
var right: Float {
return self.x + self.width
}
} |
struct Rectangle {
// ...
var right: Float {
get {
return self.x + self.width
}
}
} |
理由: returnが読み取り専用であることを十分明確にしており、より簡潔な記述を可能にしています。
括弧のルールを適用しています。
| OK | NG |
|---|---|
if array.isEmpty {
// ...
}
else {
// ...
} |
if array.isEmpty {
// ...
} else {
// ...
} |
if array.isEmpty
{
// ...
}
else
{
// ...
} |
理由: 括弧のルールと合わせて、このフォーマットは一貫性と可読性を向上させます。条件分岐文と終わり波括弧が視覚的にスコープを分かりやすくしています。
括弧のルールを適用しています。
| OK | NG |
|---|---|
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文を空行で分けることは視認性を向上させます。
| OK | NG |
|---|---|
if array.isEmpty {
// ...
} |
if (array.isEmpty) {
// ...
} |
理由: Objective-Cではないからです。
| OK | NG |
|---|---|
guard let strongSelf = self else {
return
}
// strongSelfを使用したコードをここに書きます |
if let strongSelf = self {
// strongSelfを使用したコードをここに書きます
} |
理由: 追うべきネストが多くなればなるほど、コードを追いかけることが困難になります。
命名規則はそのほとんどがAppleの命名規則に基づいています。
| OK | NG |
|---|---|
class ImageButton {
enum ButtonState {
// ...
}
} |
class image_button {
enum buttonState {
// ...
}
} |
理由: 統一性を持たせるためにAppleの命名規則を採用しています。
| OK | NG |
|---|---|
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の命名規則を採用しています。
| OK | NG |
|---|---|
var webView: UIWebView?
var URLString: String?
func didTapReloadButton() {
// ..
} |
var web_view: UIWebView?
var urlString: String?
func DidTapReloadButton() {
// ..
} |
理由: 統一性を持たせるためにAppleの命名規則を採用しています。頭文字語に関しては、Upper caseの読みやすさを重視するからです。
| OK | NG |
|---|---|
for (i, value) in array.enumerate() {
// ... "i" is well known
} |
for (i, v) in array.enumerate() {
// ... what's "v"?
} |
理由: 1文字の名前より良い名前は必ずあります。iに関しては、indexを使用するよりも可読性が高いです。
出来る限り省略された名前を付けない(ただし、こちらは使用して良い(例えば、min/max))。
| OK | NG |
|---|---|
let errorCode = error.code |
let err = error.code |
理由: 僅かに短くなることより、明快さの方が重要です。
| OK | NG |
|---|---|
class Article {
var title: String
} |
class Article {
var text: String
// これは記事のタイトルかコンテンツか分からない
} |
| Better | |
class NewsArticle {
var headlineTitle: String
} |
理由: 僅かに短くなることより、明快さの方が重要です。また、その特徴を表す名前を付けることで、他の名前と衝突することを避けることができます。
| OK | NG |
|---|---|
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) {
// ...
} |
理由: 正しい型を知るために宣言部分をチェックする時間を節約できます。
| OK | NG |
|---|---|
class User {
// ...
}
enum Result {
// ...
}
protocol Queryable {
// ...
} |
class UserClass {
// ...
}
enum ResultEnum {
// ...
}
protocol QueryableProtocol {
// ...
} |
理由: 余分な接尾辞は無駄です。ただし、Objective-Cのクラスと同一名のプロトコルは~Protocolの接尾辞を付けてSwiftにブリッジングされることに注意してください(例えば、NSObjectとNSObjectProtocol)。このことはSwiftのコンパイラが自動生成したものであり、このガイドラインとは関係ありません。
| OK | NG |
|---|---|
import Foundation
import UIKit
import Alamofire
import Cartography
import SwiftyJSON |
import Foundation
import Alamofire
import SwiftyJSON
import UIKit
import Cartography |
理由: ブランチ間で依存関係の記述が変更された時、マージコンフリクトを減らします。
// MARK: - <宣言の名前>を付ける。
| OK | NG |
|---|---|
// 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
} |
理由: ソースコード上のどこにプロパティやメソッドが宣言されているかを容易に特定できます。
| OK | NG |
|---|---|
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: Public// MARK: Internal- Classの継承 (最上位の親から子へ)
// MARK: NSObject// MARK: UIResponder// MARK: UIViewController
- Protocolの継承 (最上位の親から子へ)
// MARK: UITableViewDataSource// MARK: UIScrollViewDelegate// MARK: UITableViewDelegate
// MARK: Private
理由: ソースコード上のどこにプロパティやメソッドが宣言されているかを容易に特定できます。publicとinternalの宣言は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を使用する、使用していない変数は_を使用するなどです。
| OK | NG |
|---|---|
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 // 小さい数字を返す
} |
理由: 最も良いコメントは書いた人自身は必要のないものです。もしコメントを書く必要があるならば、そのコードを書いた理由を説明するべきであり、単に明らかなことを説明するべきではありません。
| OK | NG |
|---|---|
self.titleLabel.text = "Date Today:" // TODO: localize |
self.titleLabel.text = "Date Today:" |
理由: 実装した機能をデバッグしてテストをする場合、大抵はネイティブの言語を使用します。そして、翻訳された文字列は分けてテストされます。このガイドラインにより、全ての未翻訳の文字列が説明され、後で見つけることが容易になります。
| OK | NG |
|---|---|
@objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
// ...
} |
func scrollViewDidScroll(scrollView: UIScrollView) {
// ...
} |
理由: コンパイラの最適化による解決困難なバグを防ぎます。
| OK |
|---|
@IBOutlet private dynamic weak var closeButton: UIButton?
@IBAction private dynamic func closeButtonTouchUpInside(sender: UIButton) {
// ...
} |
理由: Swiftコンパイラはたまにdynamicとして扱ってくれません。dynamicを書くことで、privateで宣言していたとしても安全性を保証します。
| 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で宣言していたとしても安全性を保証します。
| OK | NG |
|---|---|
@IBOutlet dynamic weak var profileIcon: UIImageView? |
@IBOutlet var profileIcon: UIImageView! |
理由: サブクラスがその@IBOutletのビューを生成しなかったとしても安全性を保証します。また、viewDidLoad(_:)の前にプロパティにアクセスすることでクラッシュすることを防ぎます。
理由: Xcodeの補完を必要のないプロパティやメソッドで汚してしまうことを防ぐことができます。また、理論的には、コンパイラが最適化をさらにして、ビルドが速くなるでしょう。
</tab理由: APIの使用者にとって意図したことを明確にすることができます。
| OK | NG |
|---|---|
private let defaultTimeout: NSTimeInterval = 30
internal class NetworkRequest {
// ...
} |
let defaultTimeout: NSTimeInterval = 30
class NetworkRequest {
// ...
} |
| OK | NG |
|---|---|
private let someGlobal = "someValue"
class AppDelegate {
// ...
private var isForeground = false
} |
public let someGlobal = "someValue"
public class AppDelegate {
// ...
var isForeground = false
} |
理由: Appバンドルでpublicは役に立ちません。すなわち、アクセス修飾子はinternalかprivateのいずれかと推測できます。この場合、privateを明示するだけで十分です。
| OK | NG |
|---|---|
@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?
} |
理由: 宣言の順序のルールと合わせて、縦方向にコードを流し読みしている時に、可読性が増します。
| OK | NG |
|---|---|
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 |
理由: 冗長になることを防ぐためです。また、ジェネリクスの型にバインドされるときの曖昧さを減らします。
| OK | NG |
|---|---|
var radius: CGFloat = 0
var length = CGFloat(0) |
var radius: CGFloat = CGFloat(0)
var length = 0 as CGFloat // キャストするよりもイニシャライザの方が良い |
理由: 冗長になることを防ぐためです。また、ジェネリクスの型にバインドされるときの曖昧さを減らします。
| OK |
|---|
let badgeNumber = unreadItems.count |
空かどうかをチェックするとき:
| OK | NG |
|---|---|
if sequence.isEmpty {
// ... |
if sequence.count <= 0 {
// ... |
最初か最後の要素を取得するとき:
| OK | NG |
|---|---|
let first = sequence.first
let last = sequence.last |
let first = sequence[0]
let last = sequence[sequence.count - 1] |
最初か最後の要素を削除するとき:
| OK | NG |
|---|---|
sequence.removeFirst()
sequence.removeLast() |
sequence.removeAtIndex(0)
sequence.removeAtIndex(sequence.count - 1) |
全てのインデックスでイテレートするとき:
| OK | NG |
|---|---|
for i in sequence.indices {
// ...
} |
for i in 0 ..< sequence.count {
// ...
} |
最初か最後のインデックスを取得するとき:
| OK | NG |
|---|---|
let first = sequence.indices.first
let last = sequence.indices.last |
let first = 0
let last = sequence.count - 1 |
最後のn個以外全てのインデックスでイテレートするとき:
| OK | NG |
|---|---|
for i in sequence.indices.dropLast(n) {
// ...
} |
for i in 0 ..< (sequence.count - n) {
// ...
} |
最初のn個以外全てのインデックスでイテレートするとき:
| OK | NG |
|---|---|
for i in sequence.indices.dropFirst(n) {
// ...
} |
for i in n ..< sequence.count {
// ...
} |
基本的に、countの値に足したり、引いたりして使用したい場合は、Swiftらしい書き方でもっと良い書き方があるでしょう。
理由: 意図していることが明確になり、ミスを減らすことができます。特にOff-by-oneエラーを減らすことができます。
特に、このルールはこれまでずっと議論されてきたselfを使用するか否かをカバーする内容です。
(この意図は次のルールで説明します)
| OK | NG |
|---|---|
self.animatableViews.forEach { view in
self.animateView(view)
} |
animatableViews.forEach { view in
animateView(view)
} |
理由: selfを付けるか付けないかを判断するよりも、必ずselfを付けるようにすることで、ミスを少なくすることができることが分かりました。すなわち、このルールは循環参照に関するルールが必要であることを意味しています。詳細は下のルールをご覧ください。
| OK | NG |
|---|---|
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]が抜けていないかを確認するだけです。
| OK | NG |
|---|---|
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として扱う必要がないため)、クラッシュを起こしやすくなります。誰もゾンビオブジェクトは好ましくないでしょう。
| OK | NG |
|---|---|
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()
}
) |
理由: シンタックスハイライトを有効にするためです。

