LoginSignup
1
2

More than 3 years have passed since last update.

ファイルを保存するだけで動的にプログラムを更新できるInjectionIIIについて、macOSでもできるようにしてみた

Posted at

以前、ファイルを保存するだけで動的にプログラムを更新できるInjectionIIIについての解説記事を投稿した。
その際は、対象がiOSだけだった。macOSでもできると書いてあったが、方法がわからず、ようやく少し進展した(まだ道半ば)。
https://qiita.com/KoichiroEto/items/5cb149a6e5d74bbdd66c

3. macOSのプログラムからインジェクションしてみる

これまでは、iOS用アプリをインジェクションしていた。iOSアプリはシミュレーター上で動作しており、macOSアプリはそうではないという違いがある。そのため、いくつか追加の手順が必要となる。

3.1. なにかアプリを作る

まず、さきほどと同様に、なにかシンプルなアプリを開発する。
Xcodeを起動→Create a new Xcode Project→macOS→「App」→Next→Product Name:「MacTest」、User Interface: Storyboard→Next→「~/dev」を指定→Create
ViewController.swiftに、以下のようにshow()を追加。viewDidLoad()から呼ばれるようにする。

ViewController.swift
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        show()
    }
    func show() {
        let label = NSTextField(frame: NSRect(x: Int.random(in: 10..<100), y: Int.random(in: 10..<100), width: 150, height: 50))
        label.backgroundColor = NSColor.cyan
        label.stringValue = "Hello, world!"
        view.addSubview(label)
    }
}

(TextFieldの位置がランダムなのは、諸事情がある。後述する。)
まずはこの段階で実行してみる。Cmd-R→ビルドされ、実行される。ウィンドウが表示され、「Hello, world!」が表示される。
ViewController.swiftに戻り、"Hello, world!""Hello, Japan!"に修正してCmd-Sで保存する。当然、何も反映されない。この時点ではまだインジェクションされていないからだ。
再度Cmd-Rすると、一旦アプリが終了し、再度立ち上げられ、"Hello, Japan!"が表示される。約3秒で立ち上がる。この速度ならあまり不満は持たれないかもしれない。
該当個所を、「Hello, world!」に戻しておく。インジェクションの設定を始めてみよう。

3.2. プロジェクトを設定する

Xcodeに戻る。
Cmd-1→MacTestのプロジェクトを選択→PROJECT: MacTest→Build Settings→Linking→Other Linker Flags→ここにカーソルを乗せると左に三角が表示されるので、それをクリックする→Debugの右の「+」をおす→Any Architecture | Any SDK:「-Xlinker -interposable」→リターンを押すと確定する
Cmd-1→MacTestのプロジェクトを選択→TARGETS: MacTest→Signing & Capabilities→All→App Sandoboxの右の小さな「×」を押して、消す
Cmd-1→MacTestのプロジェクトを選択→TARGETS: MacTest→Signing & Capabilities→All→Hardened Runtime→「Disable Library Validation」をcheck

3.3. Bundleを追加

AppDelegate.swiftにBundleを追加する。

AppDelegate.swift
        #if DEBUG
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
        #endif

参考までに、AppDelegate.swiftの該当するメソッド全体を示す。

AppDelegate.swift
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        #if DEBUG
        Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
        #endif
    }

3.4. injected()を追加

ViewController.swiftに、injectedというメソッドを追加する。

ViewController.swift
    @objc func injected() {
        show()
    }

参考までに、ViewController classの全体である。

ViewController.swift
import Cocoa
class ViewController: NSViewController {
    @objc func injected() {
        show()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        show()
        NotificationCenter.default.addObserver(self, selector: Selector("injected"), name: NSNotification.Name(rawValue: "INJECTION_BUNDLE_NOTIFICATION"), object: nil)
    }
    func show() {
        let label = NSTextField(frame: NSRect(x: Int.random(in: 10..<100), y: Int.random(in: 10..<100), width: 150, height: 50))
        label.stringValue = "Hello, world!"
        label.backgroundColor = NSColor.cyan
        view.addSubview(label)
    }
    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
}

3.5. InjectionIIIにProjectを指定する

InjectionIIIが起動されていなかったら、起動する。
Status menuのInjectionIIIから「Open project」を選択→「~/dev/MacTest」を選択→「Select Project Directory」

3.6. 起動する

Cmd-R→アプリが起動して、「Hello, world!」が表示される。
この状態で、Hello, world!を編集してみる。Cmd-Sで保存する。そうすると、即座にコンパイルされ、読み込まれ、classがreplaceされる。
また、injectedが呼ばれ、そこからshowが呼ばれる。
ただ、前のオブジェクトが残ってしまっている。そのため、以前のTextFieldは消去されない。そのまま残るだけである。
そのため、以前のversionでは場所が固定されているので、文字が変更されない。これが更新される方法は、これから調べる予定。

とりあえず、今日はここまで!

1
2
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
1
2