16
7

More than 5 years have passed since last update.

【バグ】NSObject を継承したオブジェクトを unowned プロパティーとして保持すると、それを保持しているインスタンスを print すると落ちる

Last updated at Posted at 2018-01-25

TL;DR

下記のようなコードは落ちます:

import Foundation

class C: NSObject {

}

struct D {
    unowned let x: C
}

let c = C()
let d = D(x: c) // Playground ではここで落ちる
print(d) // シミュレーターや実機ではここで落ちる

どういうことか

自作ライブラリー NotAutoLayout をメンテナンスして、とある修正を対応している時に、新しくできたものがどうしても Playground だけで落ちて、まったく同じコードでも Test でも普通に動くしシミュレーターで確認しても何の問題もありませんでした。

ちなみに実際それを再現するブランチをこちらから落とせます:
https://github.com/el-hoshino/NotAutoLayout/tree/Playground-Bug

上記のものを落として、Workspace を開いてシミュレーターを Target にビルドして Playground 開くと、確実に parent.nal.layout(child) { $0 のクロージャーの終了場所で EXC_BAD_ACCESS で落ちるのです。しかし、Test にまったく同じコードがありますが、そちらは何の問題もなくテストが通ります。Playground は当然ながら lldb も使えないし Call Stack も表示されないので、落ちたとしても具体的になぜ落ちたかもわかりません。

この問題を Discord に投げてみたら、@kishikawakatsumi さんと @rintaro さんのおかげで、原因がようやく突き止められて、最終的には上の TL;DR で出したコードとなります。

具体的に言いますと、NSObject を継承したオブジェクトを class や struct の unowned プロパティーとして保持した際に、どうやら swift_ClassMirror_subscript() のバグで、これを強参照として読み取ろうとしているのが原因だそうです。現在このバグはこちらにも上がっています:
https://bugs.swift.org/browse/SR-5289

このバグを再現するには 2 つの条件が必要です:

  1. プロパティーの型は NSObject を継承する必要があります(つまりそうではない通常の class は問題ありません)
  2. プロパティーを unowned として保持する必要があります(つまり通常の強参照と weak による弱参照の保持も問題ありません)

上記 2 つの条件を満足した場合、そのプロパティーを持つインスタンスを print しようとすると落ちます。

また、Playground で動かす場合は、そもそも作られたものを全てとりあえず print しようとしているので、作られた時点で落ちます。ツラい。

以上、改めまして、この現象の原因を究明するのにお世話になりました @kishikawakatsumi さんと @rintaro さんに、大変ありがとうございます。

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7