Objc-dependency-visualizerというOSSツールを使うと、アプリ内で使用している Objective-C クラスの依存関係をビジュアライズしてくれます。
試しに "iOS7 Sampler" でやってみると、こんな感じのを生成してくれました。
実行するのはrubyスクリプトで、依存関係だけが記述されているだけのシンプルなjsファイルが生成されます。
で、閲覧時にはリポジトリに同梱されている index.html 内のJavaScriptから、生成したjsファイルとビジュアライゼーション用 JavaScript ライブラリ「D3.js」を使用してビジュアライズされます。
そんなわけで、引っ張ったり特定の箇所にフォーカスしたり表示をいろいろカスタマイズしたりできます。
(SVProgressHUDにフォーカスした図)
使い方
GitHubからcloneしてきます。
git clone https://github.com/PaulTaykalo/objc-dependency-visualizer.git
あとはスクリプトを実行するだけ。
cd objc-dependency-visualizer ./generate-objc-dependencies-to-json.rb -s <PROJECT_PREFIX> > origin.js
・・・といっても引数に指定する <PROJECT_PREFIX> って何?という話ですが、スクリプトの中身を見たところ、
IO.popen("find ~/Library/Developer/Xcode/DerivedData -name \"#{options[:project_name]}*-*\" -type d -depth 1 -exec find {} -type d -name \"i386\" -o -name \"armv*\" -o -name \"x86_64\" \\; ") { |f| f.each do |line| paths << line end }
と "~/Library/Developer/Xcode/DerivedData" を見に行ってるので、その配下にあるフォルダ名のプレフィックス(例:フォルダ名 "iOS7Sampler-gsaqzzlucgunafcnfuaxtsbqynjz" であれば "iOS7Sampler")を入れておけばよさそうです。
成功すると origin.js に次のような依存関係を抽出したものが出力されます。
var dependencies = { links: [ { "source" : "ActivityTrackingViewController", "dest" : "CMMotionActivityManager" }, { "source" : "ActivityTrackingViewController", "dest" : "CMStepCounter" }, { "source" : "ActivityTrackingViewController", "dest" : "SVProgressHUD" }, { "source" : "AVCaptureManager", "dest" : "AVCaptureDevice" }, { "source" : "AVCaptureManager", "dest" : "AVCaptureDeviceInput" }, { "source" : "AVCaptureManager", "dest" : "AVCaptureMovieFileOutput" }, { "source" : "AVCaptureManager", "dest" : "AVCaptureSession" }, { "source" : "AVCaptureManager", "dest" : "AVCaptureVideoPreviewLayer" }, { "source" : "BeaconViewController", "dest" : "CBPeripheralManager" }, { "source" : "BeaconViewController", "dest" : "CLBeaconRegion" }, { "source" : "BeaconViewController", "dest" : "CLLocationManager" }, { "source" : "BeaconViewController", "dest" : "PulsingHaloLayer" }, (中略) ] } ;
あとはリポジトリに同梱されていた index.html を開くだけ。
しくみ
ビジュアライズの部分は上述した通り「D3.js」が用いられています。
で、依存性を抽出する部分は、スクリプトのソースを見てみると、
IO.popen("find \"#{options[:search_directory]}\" -name \"*.o\" -exec /usr/bin/nm -o {} \\;") { |f|
という処理があることから、nmコマンドが用いられているようです。(nmはオブジェクトファイルからシンボルをリストするコマンド)
ちなみに、昔書いた
で紹介した objc_dep.py ではプロジェクト内にあるソースファイルを直接見に行く形式だったので、同じプロジェクトに対して実行しても結果が変わってくるかと思います。
プロジェクトフォルダにはあるけど実は使用してないコードとかが混ざらない分、こっちの方がよりアプリの実態を正確に表してくれそうかなと。