post Image
Swiftガイドライン的な

Swiftガイドライン

Swiftのコーディングをする上でのガイドラインになります。
現在のプロジェクトでルールや注意すべき点などについて、まとめられていなかったので、今回作成しようと思いました。
(現時点で定義されていないものもあるので、決まり次第随時更新していきたい。。)

前提実行環境

  • Xcode8.3.2
  • Swift3.1

命名規則

  • クラス名、構造体名、列挙型名、プロトコル名はアッパーキャメルケースを用いる。
アッパーキャメルケース
class MsgModel {}

protocol MsgModelDelegate: class {}

enum Weather: String {}

struct Author {}
  • グローバル定数、クラス定数はスネークケースの大文字を用いる。
スネークケース(大文字)
public let GOOGLE_PLIST_NAME = "GoogleService-Info"

class MsgModel {
   static let TABLE_NAME = "MSG_TABLE"
}
  • 変数名、関数名、ラベル名にはキャメルケースを用いる。
キャメルケース
var senderName: String 

func saveMessage(textMessage: String) -> Void {
    // NOP
}
  • OptionalBindingでアンラップした変数には元の名前の先頭にアンダーバーを付ける。
OptionalBinding時の変数名
let text: String? = "hello world"
if let _text = text {
    xxx = _text.uppercaseString
}

let text: String? = "hello world"
guard let _text = text else {
    return
}
xxx = _text.uppercaseString

ファイル名

基本的には、class名, enum名, struct名, protocol名を記述。
以下に例外を記述。

  • extensionに限り、拡張するクラス名 + 拡張内容を表す英単語 とする。またそのファイルはExtensionディレクトリに配置する。(これはExtensionが複数人開発だと、重複する恐れがあったり、拡張メソッドを認知してもらうためである)

ファイルの分割

基本的には、class, enum, struct, protocol, extensionは、1つのファイルに対して1つ定義する。
以下に例外を記述。

  • fileprivateで定義できるのであれば、1つのファイルに2つ以上の定義をする。

  • 自作クラスに対するextensionを行う場合には、定義元のファイルに記述する。

  • delegateパターンなどで、protocolを使用する際には、委譲元に定義する。

変数, 定数定義

  • 基本的に1行で複数の変数定義は記述しない。
let row: Int = 0
let column: Int = 0
  • 基本的にletで定義して、必要に応じてvarに変更する。
let column: Int = 0
var column: Int = 0
  • 右辺の型が明確な場合は、左辺に型を記述しない。
let text = "hello"
let flg = false
let application = UIApplication.shared
let image = UIImage(named: "hello")
let text = data["text"] as String
  • 右辺の型が曖昧な場合は明記する。
// Int, UInt
let i: Int = 0

// Float, CGFloat, Double
let p: Float = 3.1415
  • 配列の初期化の方法

様々な方法が用意されているが、基本的にはAppleの推奨しているものに合わせる。Collection Types

文字列型

  • Swiftには、NSStringとStringの2つの文字列型があるが、NSStringはObjective-Cのクラスだが、SwiftのStringには互換性があるため、基本的にはStringを使用する。
// ×
let text: NSString = "hello"
// ○
let text: String = "hello"
  • 文字列結合とフォーマット(NSStringのメソッドは使用しない)
// 結合
"\(hello) \(hello)"
"hello" + "hello"

// 配列の文字列結合 
let array = ["すたば", "どとーる", "たりーず"]
let str = array.joined(separator: ",")

// フォーマット
let cg: CGFloat = 0.25
String(format: "%.01f", Float(cg))
  • 文字数の取得

余程厳密な文字数のカウントが必要でなければ、基本的にはString.characters.countを使用する。詳しく解説しているサイト

"hello".characters.count // => 3
  • 空文字の判定
"hello".isEmpty // => false

整数型

  • 整数型は、Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64 の整数が利用可能。
    基本的に、Int8, Int16, UInt8, UInt16 は使用しない。

  符号あり Int8, Int16, Int32, Int64
  符号なし UInt8, UInt16, UInt32, UInt64

let num: Int8 = 10
let num: UInt8 = 5
let num: Int64 = 10
let num: UInt64 = 5
  • 各整数型の最大値と最小値は、maxminというプロパティで取得できる。
let minInt32: Int32 = Int32.min    // -2147483648
  • 整数を扱うのに、Int というものがある。

これは実行するCPUのビット数に応じて、Int32, Int64と型が変わるため、 大きな値を扱う必要がある場合には、Int64を使うようにする。符号なしの整数の場合も同様でUIntという型が存在する。

let num: Int = 256  // CPUのビット数に依存する

浮動小数点型

  • 浮動小数点型は、DoubleFloatの2種類がある。
    それぞれ、64ビット、32ビットの浮動小数点になる。基本的には、Floatで十分なので、Floatを使用する。

CGFloat

  • Swiftで扱うCGFloatはビルドするアーキテクチャによってエイリアスが異なる。iPhone5s(arm64)とiPhone5(armv7, armv7s)では挙動が変わる。
アーキテクチャ CGflotのエイリアス
arm64 Double
armv7, armv7s Float
  • iPhone5sシミュレーターでコンパイルするとエラーになります。
let index: NSInteger = 1
let width: CGFloat = 100
let r1 = width * Float(index)
// => ERROR
  • CGFloatとの演算は常ににCGFloatにコンバージョンして演算するようにする。またRealm等を使用している際にもCGFloatを指定できないので、注意。

  • Extensionの利用を検討中

extension Float {
    public var f: CGFloat {
        return CGFloat(self)
    }
}
extension Double {
    public var f: CGFloat {
        return CGFloat(self)
    }
}

関数

基本的にはAPIのガイドラインにそって作成するが、以下に例外を記述。

  • 戻り値がVoid型の場合は、戻り値を書かない。
func test(text: String) -> Void { }
// ↓
func test(text: String) {  }
  • Swiftでは第一引数のラベルは省略可能だが、基本的には省略しない。
func changeData(columnName: String, data: String)
changeData(columnName: 42, data: 13) 
  • 引数が1つのもの、もしくは、それ以降の引数がデフォルト値の場合は省略する。
func test(_ test: String) 
test("this is message")

func test(_ test: String, label: String = "test") 
test("this is message")

Optional

オプショナル型とは変数にnilの代入を許容するデータ型で、反対に非オプショナル型はnilを代入できません。オプショナル型の変数にはデータ型の最後に?!をつけます。
次のサイトが非常にわかりやすかったので、必ず理解してください 参考サイト
またこのプロジェクトでoptionalを扱う際のルールを下記に。

  • 強制的アンラップ、暗黙的アンラップ型は、基本的には使用しない。OptionalBindingやOptionalChainingを活用する。どうしても使用したい場合には、リーダーに相談する。

  • OptionalBindingを使用する際には、基本的にガード節の考えを適応して、処理に必ず必要な値は、先頭でguardする。

// ×
let text: String? = "hello world"
if let _text = text {
    // ネストが深くなる可能性がある
    xxx = _text.uppercaseString
}

// ○
let text: String? = "hello world"
guard let _text = text else {
    return
}

xxx = _text.uppercaseString
  • Swift3にしてから、暗黙的アンラップ型でも、Swift3の埋め込み文字列の中ではアンラップしないで参照されていることがわかりました。
// ×
let text: String! = "world"
"hellow \(text)"
// hello Optional("world")

// ○
let text: String! = "world"
"hellow \(text)"
// hello world"

クロージャ

クロージャーはざっくり言うと自分を囲むスコープにある変数を操作できる関数です。

  • クロージャの引数の記述は略記しない。
let rename = UIAlertAction(title: "名前変更", style: .default, handler: { (action: UIAlertAction!) in
    self.rename()
})
  • TrailingClosures(後置記法)が利用できる場合には利用する。
let rename = UIAlertAction(title: "名前変更", style: .default) { (action: UIAlertAction!) in
    self.rename()
}
  • ShorthandArgumentNames(簡易引数名)が利用できる場合には利用する。
var value = [1, 2, 3].sorted { return $0 > $1 }
  • クロージャーが1行で記述できる場合には、returnを省略する(ただし、複数の返り値がある場合は別)
var value = [1, 2, 3].sorted { $0 > $1 }
  • クロージャー内コードが1行でかつ戻り値を要求しない場合は最後にreturnのみ記述する。
let funcClosure = { (text: String) in
    print(text)
    return
}

列挙型

  • 型が分かっている場合はenum型を省略して使用する (Realm等で使用する際には、実際の値を保持する必要があるため、rowValueを使用しなければならないが、その他の部分では、できる限りenum型を指定して、型推論やenumの恩恵を受けやすく作成する)
enum BloodType {
    case ab
    case a
    case b
    case o
}

let typeAB: BloodType = .ab
  • 値型enumを作成する場合には、値を省略しない
enum Signal: Int {
    case blue = 1
    case yellow = 2
    case red = 3
}

enum Weather: String {
    case sunny = "晴れ"
    case cloudy = "曇り"
    case rain = "雨"
    case snow = "雪"
}

プロトコル (Delegate)

  • プロトコルをDelegateとして実装する際には、基本的にはClassが中心となるため、:classを付与する。この:classを付与するのは循環参照を防ぐために、weakを設定する際に必要となる。
protocol MyProtocol: class {}

class MyClass {
    weak var delegate: MyProtocol?
}

// 2つの protocol を実装する際
class MyClass {
    weak var delegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)?
}
  • enumやstructにプロトコルを実装する際には、この:classを付与することが出来ないが、現段階では、使用していないので無視する。
// : classあり
protocol MyProtocol: class {}

class MyClass: MyProtocol {}
struct MyStruct: MyProtocol {} // Error
enum MyEnum: MyProtocol {} // Error
  • protocolを実装する際には、protocolごとに切り分けてextensionで実装する。これは明示的に別処理であることを表すためと、ソースの見通しを良くするためである。
extension AddFriendViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let user: User
        if let text = self.searchBar.text, text != "" {
            user = self.filteredUsers[indexPath.row]
        } else {
            user = self.users[indexPath.row]
        }

        self.delegate?.saveFriend(selectedFriend: user)
        tableView.deselectRow(at: indexPath, animated: true)
        self.navigationController!.popViewController(animated: true)
    }
}

extension AddFriendViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let text = self.searchBar.text, text != "" {
            return self.filteredUsers.count
        } else {
            return self.users.count
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! FriendTableViewCell
        var user: User
        if let text = self.searchBar.text, text != "" {
            user = self.filteredUsers[indexPath.row]
        } else {
            user = self.users[indexPath.row]
        }
        cell.bindData(friend: user)
        return cell
    }
}

extension AddFriendViewController: UISearchBarDelegate {

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        self.filteredContentForSearchText(searchText: searchBar.text!)
    }

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.filteredContentForSearchText(searchText: searchBar.text!)
    }
}

循環参照対策

循環参照とは何か?

お互いがお互いのオブジェクトを参照してしまう状態のこと。
次の記事を必ず理解してください -> 循環参照について

  • オブジェクトの強参照を防ぐために、1つのクラス(ViewController等)で完結しない処理、例えば、Delegate等を使用する際には、weakプロパティを必ず使用する。
protocol MyProtocol: class {}

class MyClass {
    weak var delegate: MyProtocol?
}
  • 弱参照の仕組みとして、weakとunownedがあるが、基本的にはweakを使用すれば良い。
循環参照の可能性 対象の変数が実行時にnilになる可能性 対応
ない unowned、weakは記述しない(強参照)
あり あり weakを記述する
あり なし weakかunownedを記述する

escaping

クロージャがスコープから抜けても存在し続けるときに @escaping が必要になります。具体的には以下のような場合です。
* クロージャがスコープ外で強参照されるとき
* クロージャを非同期的に実行するとき

  • 例) ダウンロードなど非同期に実行する
Download
class func downloadVideo(videoUrl: String, result: @escaping (_ isReadyToPlay: Bool, _ videoFileName: String) -> Void) {
    let videoURL = NSURL(string: videoUrl)
    let videoFileName = (videoUrl.components(separatedBy: "%").last!).components(separatedBy: "?").first!

    if fileExistsAtPath(path: videoFileName) {
        result(true, videoFileName)
    } else {
        let dowloadQueue = DispatchQueue(label: "videoDownloadQueue")
        dowloadQueue.async {
            let data = NSData(contentsOf: videoURL! as URL)
            if data != nil {
                var docURL = getDocumentsURL()
                docURL = docURL.appendingPathComponent(videoFileName, isDirectory: false)
                data!.write(to: docURL, atomically: true)
                DispatchQueue.main.async {
                    result(true, videoFileName)
               }
            } else {
                ProgressHUD.showError("ビデオが見つかりませんでした。")
            }
        }
    }
}
  • escapingするとどうなるか

 @escaping なクロージャ内でselfの変数やメソッドを使用する場合、selfをキャプチャすることを明示するため self. を付ける必要があります。

  • 循環参照に気をつける

 @escapingなクロージャはどこかから強参照される可能性がある。その参照元をクロージャ内で強参照すると循環参照になるので気をつける。
 特に、selfがプロパティとしてクロージャを強参照する場合、クロージャ内でselfを強参照すると循環参照になるので注意が必要です。
 クロージャ内でオブジェクトを弱参照したい場合は、そのオブジェクトに対してweakunownedを付けます。

  • 回避策として、selfをweakとして扱うことが出来ます。
class B {
    private var a: A?
    private var count = 0

    func do() {
        // selfを弱参照する
        a = A(closure: { [weak self] in 
            self?.count += 1
        })
    }

    deinit {
        // 呼ばれる
        print("deinit")
    }
}

let b = B()
b.do()

アクセス修飾子

  • open: 同じモジュール内だけでなく、別のモジュールからでもアクセスでき、別モジュールで継承またはオーバーライドができる
  • public: 同じモジュール内だけでなく、別のモジュールからでもアクセスできるが、別モジュールでは継承、オーバーライドはできない
  • internal: 同じモジュール内からのみアクセスできる
  • fileprivate: 同じソースファイル内からのみアクセスできる
  • private: 定義されたスコープ内でのみアクセスできる
  • ファイル分割のルールで行くと、基本的には、fileprivateinternalで事足りる。またinternalは省略可能なので、はじめはfileprivateで定義して、必要に応じて削除する運用とする。

GDC

今まで使用していたSwift2.3の時から、とても大きな変更点があったので、しっかりとSwift3で変更になった書き方を理解してから使用する。
参考になったサイト

  • 現在のプロジェクトでは、Serial処理を使用していないので、基本的はシステムで用意されたキューを使用する。優先度に関しては、理由がなければ default を使用する。
// システムで用意されたキューを使う
DispatchQueue.global(qos: .default).async {
    self.doSomething()
}
  • 現在のプロジェクトで良く使用されているパターン
DispatchQueue.global(qos: .default).async {
    // サブスレッドでの重い処理
    DispatchQueue.main.async {
        // 重い処理が終わった後にメインスレッドで実行したい処理
        // 強参照などによる循環参照に注意
    }
}

制御構文

if

  • 評価式は括弧()で括らない。
if a == b { }
  • 不要なelse文は書かない。
if a == b {
    print(a)
} else {
    // NOP
}

if a == b {
    print(a)
}

if a == b {
    // NOP
} else {
    print(a)
}

if a != b {
    print(a)
}

for文, for-in文

  • ループ処理はfor-in文で記述する。
for index in 0..<3 {
    print("index is \(index)")
}

for value in array {
    print("index is \(index)")
    print("value is \(value)")  
}
  • ループ内でif文がある場合には、where文が適応できないか検討する。
for index in 0..<3 {
    if index == 1 {
        print("index is \(index)")
    }
}

for index in 0..<3 where index == 1 {
    print("index is \(index)")
}
  • for文内のキャストはまとめてできないか検討する。
for temp in self.view.subviews {
    let view = temp as UIView
}

for view in self.view.subviews as [UIView] {
    // NOP
}

Switch文

  • ケースの処理で何も実施しない場合は、breakを記述する。
switch section {
  case .profile:
    print("HOGE")
  case .game:
    // NOP
    break
  default:
    // NOP
    break
}

記述

ホワイトスペース

  • インデントは スペース4つ
  • ブラケットの前後に半角スペースを入れる。
  • コンマの後ろに半角スペースを入れる。
  • メソッドの戻り値の -> の前後に半角スペースを入れる。
  • 演算子の前後に半角スペースを入れる。

  • 1行は、 100文字以内 にするようにする。折り返す際は関連する行のインデントに合わせる。
  • ブロックの間は1行空ける。
  • 2行以上の空白は認めない。
final class SampleViewController: UIViewController {

    override func viewDidLoad() {
        // NOP
    }

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

マジックナンバー

  • 記述した本人にしか意味が分からない数値や文字列の利用を避け、なるべく定数や変数などの名前で用途が理解しやすいようにする。

コメント

  • メソッド名や処理の流れが簡単なものには、コメントは記述しない
// コメントしない
func isHidden() {}
  • 不本意な処理やまだ改良の余地がある場合には、HACK:, TODO: を使用する。
// TODO: 動いているがもう少し簡素にかけるから近いうちに直す。
func isHidden() {}

// HACK: 不具合対応で根本解決が難しかったので、応急処置。あとでしっかり直す。
func isHidden() {}
  • 処理を書かない、あるいは書く必要がないことを明記するときはNOPを記載する
// 慣習として用意しておきたい場合など
func none() {
    // NOP
}

ブラケット

  • メソッドブラケットと制御構文のブラケットは式と同じ行でオープンブラケットを記述する。
// ◯
func test() {
    // NOP
}

// ☓
func test()
{
    // NOP
}

セミコロン

  • 式の終端にセミコロンは記述しない。
print("hello");
// ↓
print("hello")

ライブラリの選定方法

CocoaControllsから使えそうなSwiftライブラリを探す。

  • Swift製ライブラリであること(Objective-Cのみしかない場合はそれでも可能)
  • iOS8以上に対応していること
  • MITライセンスであること
  • 定期的にメンテナンスされていること
  • できればCocoaPodsが使用可能であること

ストーリーボード

Objective-Cの使用

Swiftで条件を満たせていれば、基本的にはSwiftでプログラムを作成した方が良いが、ライブラリや便利なExtensionがあれば、Objective-Cの利用も検討する。
Objective-Cを扱う方法を以下に記載。

  • プロジェクトに <# ProductName #>-Bridging-Header.h という名前のファイルを追加
  • BuildSettingsの Objective-C BridgingHeaderに上記ファイル名を設定
  • 使用したいObjective-CのヘッダーファイルをBridging-Header.hにimportする

Objective-Cとの互換

NSSting, NSURL, NSDataなど、NSが着くクラスは基本的にSwiftではNSのつかないクラスに変更されている。
基本的には、BridgeHeaderに記述されているものは、ほぼSwiftらしく扱えるが、NSが付くクラスを扱う必要性が発生する場合がある。
よく使われるものは、asでNSのつかないクラス(逆も)にキャストできるが、一応ドキュメントを見るなど注意はする必要がある。

  • 例) 互換性があるクラス
let v1 = "test"
let v2 = v1 as NSSting

let v1 = Data()
let v2 = v1 as NSData

let v1 = URL()
let v2 = v1 as NSURL

『 Swift 』Article List