スタッフブログ

STAFF BLOG

やってみた

2021.01.26

Swiftのnilについて考えてみた

初めまして、金井 大朗と申します!

先日社内にてSwiftのnilとは何なのかという話題が出て口頭で説明しようとしても漠然とnullみたいなもの?としか出てこなかったので改めて調べてみました。

このあたりは興味がある人は既にQiita等で見たことがあるかも知れません。

まずはOptional型について

Optional型とは、nil、もしくは別の値を持つことができる型のことを指します。

Optional型ではない変数にnilをセットすることはできません。

var hoge: String? = nil             // OK
var fuga: Optional<String> = nil    // OK
var piyo: String = nil              // コンパイルエラー

Optional型の定義は型の後に?をつけるOptional<型>のように定義することができます。

ではこのOptional型とは一体何者なのでしょうか。

GitHubに公開されているSwiftのOptional.h型のコードを見てみましょう。

template <typename T, bool = is_trivially_copyable<T>::value>
class OptionalStorage {
  union {
    char empty;
    T value;
  };
  bool hasVal;

こちらはC++なので見慣れない方がいるかも知れませんが、OptionalStorageというクラスに渡された型を保持するTとTの値を持っているかどうかのbool値を持つクラスになります。

unionの意味はchar型とT型の変数を同じメモリ空間に割り当てるという意味になり、この場合はchar型か渡されたT型のサイズの大きな方をメモリに割り当てることになります。

nilとは?

nilを代入する処理を探す前に力尽きてしまったので以下は推測になります。

恐らくnilとは、Tの実態が定義されておらずhasValがfalseの状態のことを指しているのではないでしょうか。

ここで一つ疑問が出てきました。nilを代入したとしてもOptional型という実体は定義されているようなのでメモリは消費されるのではないでしょうか?

試してみた

簡易的なメモリチェック用のプロジェクトを作成して検証してみます。

単純にボタンが配置されたStoryBoardとボタンを押した際にOptional型の配列にnilを1024 * 1024個追加するものを用意しました。

メモリ使用量の計測関数については以下の関数を利用させて頂いています。

    var hoge: [Bool?] = []
    var counter = 0;
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print(String(format:"Bool? Size: %d" ,MemoryLayout<Bool?>.size))
        print(String(format:"UInt8? Size: %d" ,MemoryLayout<UInt8?>.size))
    }

    // ボタンが押された際に配列にnilを1024 * 1024個追加する処理
    @IBAction func TapButton(_ sender: Any) {
        counter += 1
        print(String(format: "%d回目", counter))
        printMemory(header: "StartMemory:")
        for i in 0..<1024 * 1024 {
            hoge.append(nil)
        }
        printMemory(header: "EndMemory:")
    }
    
    // メモリ表示文を整形
    func printMemory(header: String) {
        if let memory = getMemoryUsed() {
            print(String(format: "%@%dMB", header , memory))
        }
    }
    
    // 使用者が単位を把握できるようにするため
    typealias MegaByte = UInt64

    // 引数にenumで任意の単位を指定できるのが好ましい e.g. unit = .auto (デフォルト引数)
    func getMemoryUsed() -> MegaByte? {
        // タスク情報を取得
        var info = mach_task_basic_info()
        // `info`の値からその型に必要なメモリを取得
        var count = UInt32(MemoryLayout.size(ofValue: info) / MemoryLayout<integer_t>.size)
        let result = withUnsafeMutablePointer(to: &info) {
            task_info(mach_task_self_,
                      task_flavor_t(MACH_TASK_BASIC_INFO),
                      // `task_info`の引数にするためにInt32のメモリ配置と解釈させる必要がある
                      $0.withMemoryRebound(to: Int32.self, capacity: 1) { pointer in
                        UnsafeMutablePointer<Int32>(pointer)
                      }, &count)
        }
        // MB表記に変換して返却
        return result == KERN_SUCCESS ? info.resident_size / 1024 / 1024 : nil
    }

今回はBool?型とUInt8?型にて検証を行いました。

Bool型とUInt8型はそれぞれ1バイトなのでそれぞれTに値する部分が1バイト、hasValのboolが1バイトの合計2バイトの予想でしたが、MemoryLayoutにてサイズを計測した結果

Bool? Size: 1
UInt8? Size: 2

UInt8?型は想定通りでしたがBool?型は予想と違う結果になりました。

以下Bool?とUInt8?にnilを代入した際のメモリ消費量を12回試行した結果になります。

Bool? UInt8?
開始時メモリ 追加後メモリ 開始時メモリ 追加後メモリ
1回目 77MB 80MB 77MB 83MB
2回目 80MB 83MB 83MB 89MB
3回目 83MB 84MB 89MB 91MB
4回目 84MB 89MB 91MB 101MB
5回目 89MB 90MB 101MB 103MB
6回目 90MB 91MB 103MB 105MB
7回目 91MB 92MB 105MB 107MB
8回目 92MB 101MB 107MB 125MB
9回目 101MB 102MB 125MB 127MB
10回目 102MB 103MB 127MB 129MB
11回目 103MB 104MB 129MB 131MB
12回目 104MB 105MB 131MB 133MB

やはりnilを代入してもメモリは消費されるようです。Bool?にtrue、Uint8?に10を代入してもメモリ消費量に変化はなかったためnilを代入した時点でT分のメモリも確保されているようです。

2バイト*1M個なので2MBずつ上がるかと予想していましたが所々メモリ消費量が急上昇していたり2MB以下の上昇量だったりしています。

この辺りは配列処理やメモリ管理系で別に動いているものがあるのかも知れませんね。

BACK

お問合せ

イーディーエーに興味をお持ちいただいて
ありがとうございます!
スマホアプリに関するご相談、
お見積りや弊社へのご質問など、
お気軽にお問い合わせください。
担当者より折り返しご連絡させていただきます。

    お名前必須
    会社名
    メールアドレス必須
    電話番号必須
    お問合わせ種別必須
    お問合わせ内容必須

    アンケートにご協力ください。
    弊社サイトへはどのようにしてアクセスされましたか?

    個人情報のお取扱いに関する同意事項

    1.事業者の氏名又は名称

    株式会社イーディーエー

    2.個人情報保護管理者の氏名又は職名、所属及び連絡先

    個人情報保護管理者 小宮 保人
    Mail:[email protected]

    3.取得した個人情報の利用目的

    当フォームで取得した個人情報は、お問い合わせに関する回答のために利用し、目的外利用はいたしません。

    4.弊社が取得した個人情報の第三者への委託、提供について

    弊社は、ご本人に関する情報をご本人の同意なしに第三者に委託または提供することはありません。

    5.個人情報保護のための安全管理

    弊社は、ご本人の個人情報を保護するための規程類を定め、従業者全員に周知・徹底と啓発・教育を図るとともに、その遵守状況の監査を定期的に実施いたします。
    また、ご本人の個人情報を保護するために必要な安全管理措置の維持・向上に努めてまいります。

    6.個人情報の開示・訂正・利用停止等の手続

    ご本人が、弊社が保有するご自身の個人情報の、利用目的の通知、開示、内容の訂正、追加又は削除、利用の停止、消去及び第三者への提供の停止を求める場合には、下記に連絡を頂くことで、対応致します。

    株式会社イーディーエー 個人情報お問合せ窓口
    〒106-0032 東京都港区六本木7丁目14番23 ラウンドクロス六本木4F
    TEL:03-5422-7524 FAX:03-5422-7534
    Mail:[email protected]

    7.ご提供いただく情報の任意性

    個人情報のご提供は任意ですが、同意を頂けない場合には、第3項にあります利用目的が達成できない事をご了承いただくこととなります。

    8.弊社Webサイトの運営について

    弊社サイトでは、ご本人が弊社Webサイトを再度訪問されたときなどに、より便利に閲覧して頂けるよう「クッキー(Cookie)」という技術を使用することがあります。これは、ご本人のコンピュータが弊社Webサイトのどのページに訪れたかを記録しますが、ご本人が弊社Webサイトにおいてご自身の個人情報を入力されない限りご本人ご自身を特定、識別することはできません。
    クッキーの使用を希望されない場合は、ご本人のブラウザの設定を変更することにより、クッキーの使用を拒否することができます。その場合、一部または全部のサービスがご利用できなくなることがあります。