NSOutlineViewDelegate プロトコル

NSOutlineView 関連のプロトコルでは、DataSource の方は存在感があるが Delegate の影は薄い。

まあ、DataSource の方はないと表示すらできないわけだが…。

試しに

@interface OutlineviewManager : NSObject<NSOutlineViewDataSource>

@interface OutlineviewManager : NSObject<NSOutlineViewDataSource, NSOutlineViewDelegate>

としてもコンパイル自体はできる。

行選択時の機能

公式ページを見るに、行選択をした時のメソッドを供給しているようです。

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;

のメソッドは、公式の説明だと item が選択できるかどうかを定義するもののように紹介されていますが、おそらく機能も提供できます。
OutlineView に Person クラス(のインスタンス)を表示させているとき

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
{
Person *p = item;
NSLog(@"%@", p.name);

return YES;
}

を実装しておくと name プロパティの値(文字列)を表示してくれます。

 

cocoa プログラミング

今まで延々 GUI なしの Objective-C の記事書いてきたが、ここらで GUI つまり cocoa の話もちらほら。

Q&A 形式で。

Q Xcode 上で GUI 部品をどう出せばいいかわからない。

A 右上の + ボタンを押下する。

すると検索画面が出現するので、検索窓から button だの view だの打ち込んでパーツ選ぶ。

なお、View 関係で View 自体の大きさをウィンドウのリサイズに追従させたいときは、Autoresize 使う。

 

(続く)

Mac アプリインストーラー .pkg の作り方

あまり作る機会がないのだが、Mac アプリ用のインストーラーとして pkg 形式というものがある。

アプリそのものの場合、公証を受けなくてもユーザーが右クリック→「開く」でインストさせることはできるが、インストーラーの場合、デフォルトでは

オーナー root :グループ wheel

でインストされるせいか、この辺はキビしく、公証を受けていないと

このパッケージは、このバージョンの macOS と互換性がありません

という無慈悲なアラートとともにインスト自体がストップしてしまう。

だから、配布したい場合、公証まで受ける必要がある。

で、pkg 形式のファイルの作り方だが、現在だと『Mac用installerの作り方』 がまとまっているようだ。
ただ、この記事、.app が前提となっていて、コマンドラインツールでは全く通用しない。
説明も、もっぱら手順のみでコマンドの意味もまったく解説されておらず、ある程度のスキルを持った人はイライラすると思う。

コマンドラインツールの公証

追記の記事が一番確実。(ワイも Xcode 上で実行できないか試したが、記事のようにコマンドラインからビルドするのが確実なようです。しかし –timestamp というフラグは言われないと気が付かない)

だが、その他のコマンドがいささか古いのでアップデート。

まず、XXX.plist をつくる。

pkgbuild --analyze --root build/pkgroot hoge.plist

中身を見るとわかりますが、コマンドラインツールの場合、hoge.plist はほぼ何の有益な情報も記入されてないです。次のコマンドのために形式的に必要なのでは?と思います。

次はこれを使って .pkg を作成。

pkgbuild --root build/pkgroot --component-plist hoge.plist --scripts scriptDir --identifier id --version version --install-location "/" hoge.pkg

お次は hoge.pkg に(インストーラーの方の)署名。

productsign --sign "Developer ID Installer: team name" "hoge.pkg" "hoge2.pkg"

この時点でも公証は受けられる。
この時の password は apple ID 本体のやつではなくて、サードパーティアプリの二段階認証用のものを使う。
ない場合は(ほとんどの場合、ないと思うが)、appleID のページに行って作成。

xcrun notarytool submit hoge2.pkg --apple-id "hoge@hoge.com" --password 1234-5678-abcd-efgh --team-id 1234ABCD --wait

それほど大きくないソフトであれば数分で以下のメッセージが返ってきます。

Processing complete
  id: 1234678-abcd-4f44-86ae-792c54d13965
  status: Accepted

その他事項はまずはこの確認やってからでいいと思う。
ネットに落ちている情報は古いことが多く(この記事だっていつかは陳腐化するでしょう)、一回で決まることはまずないと思うので。


追記:英語になるがこの記事 や この記事(こちらは日本語)なども参考に。

 

プロセスとかスレッドとか -pthread や semaphore-

今までよく分かってなかったプロセス・スレッド・同期・非同期云々を今のうちにまとめておこうと思い、ネット上を散策。

とりあえず、ここから始まる書き物を読んでいたのだが、読み進めるうちにちょっとイライラしてきた。

なんでこの人は、Xinu というそれほどメジャーとは言えないような環境でテストしているんだろうか?

今、現在、この仕事の評価が即できるほどの力量を持ち合わせていないので、なんとも言えないのだが、現在、使いやすい環境を一つ選んでサンプル載せた方が利便性という意味では良いと思う。

Linux, MacOS, Windows なんでもいいでしょ。

 

その一方で、ネットには(まとまっていないものの)「おお!」と呻きたくなるような有用なサンプルコードが落ちている。

文句ばかり言っても始まらないので、それらを収集して、暫定的なコメントをつけていく。

fork() とプロセス

まずは、以下のサンプルコードを実行してみよう。

fork.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    pid_t   pid;

    pid = fork();
    printf("PID=%d\n", pid);
    exit(0);
}

実行形式のファイルを得るのは、Linux or MacOS なら、上のコードをテキストファイルに書き込んで

gcc fork.c -o fork

でいい。それだけ。

実行すると

% ./fork 
PID=80644
PID=0

という結果が得られる。
この PID というのは 「システム管理者が端末から ps コマンド叩いて、何やらやっている PID のこと?」と思うかもしれないが、その通り。

次に以下のコードも同様に実行。

fork2.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    pid_t   pid;

    pid = fork();
    printf("PID=%d %d\n", pid, getpid());
    getchar();
    exit(0);
}

同様に実行すると getchar() でこのプログラムは入力待ちとなり一時停止する。

この状態で他の端末から ps してみよう。

% ps
  PID TTY           TIME CMD
48613 ttys000    0:00.17 -zsh
31547 ttys001    0:00.90 /bin/zsh -il
85863 ttys001    0:00.02 ./fork2
85865 ttys001    0:00.00 ./fork2

予想通り、PID はあの PID だった。

なお、「通常子プロセスは exec コマンドと組み合わせて他のプログラムを起動するときに使われる」と説明されているが、具体的なコードがない。

このわかりやすい例が StackOverFlow にあったので、あげておく。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main (void) {
    pid_t processId;
    if ((processId = fork()) == 0) {
        char app[] = "/bin/echo";
        char * const argv[] = { app, "success", NULL };
        if (execv(app, argv) < 0) {
            perror("execv error");
        }
    } else if (processId < 0) {
        perror("fork error");
    } else {
        return EXIT_SUCCESS;
    }
    return EXIT_FAILURE;
}

 

ところで、このコードは参考(下参照)から拾ってきたが、この著者の説明は明快で、こちらの記事でプロセスとスレッドについて実に簡潔に説明している。

 

 

普通はこういう理解だと思う。

最近このブログでは NativeMessaging を取り上げたが、NativeMessaging は通常「プロセス間通信」と呼ばれていると思う。
一つのプロセスは chrome 拡張の JavaScript プログラム、もう一つのプロセスは host のネイティブアプリ。

なんで大学教授のやつがイライラしたかと言えば、通常はスレッドというべきものをプロセスと呼んでいたから。

内的な整合性が取れていればそれでもいいんだが、有用性でいうとあれ?って感じ。率直に言えば「使えない」。昨今のアカデミア軽視の風潮はこんなところから来ているのかもしれない。

スレッドと pthread

そういうわけで次はスレッド。

スレッドを生成した側のメインスレッドが先に終了して、同時にスレッドが意図しないタイミングで強制終了すると想定しない状況に陥るかもしれません。ここまでの例では、メイン関数側がスレッドの処理時間よりも十分に長い時間を待機させるようにしてそれを避けていましたが、メイン関数側は、生成したスレッドの終了するタイミングの長短にかかわらずスレッドの終了を待ち合わせるべきでしょう。

そーそー。しかし、わかりやすいですね。

そして提示されていたサンプルがこちら。

threadjoin.c
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void *th_func(void *arg) {
    puts("thread created");
    sleep(10);
    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t th;

    if (pthread_create(&th, NULL, th_func, NULL) == 0) {
        puts("Main thread");
        pthread_join(th, NULL);
        puts("Main exit");
    }
}

sleep に与える変数を変えると確かにその間もメインスレは待ってますね。

すげ。

セマフォ semaphore

pthread に相当する obj-c の機能はないかとあれこれ物色していたのだが、セマフォ semaphore がそれに該当するらしい。以下のサンプルを作成。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    dispatch_semaphore_t syncSemaphore = dispatch_semaphore_create(0);

    // 非同期タスクの実行
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 何らかの処理
        NSLog(@"非同期タスク実行中");

        // タスクが終了したら、セマフォのカウンタを +1 にする
        dispatch_semaphore_signal(syncSemaphore);
    });

    // セマフォのカウンタは当初 -1 なので、メインスレッドは停止している
    dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"非同期タスク終了");
    
    return 0;
}

これ、セマフォがないと、メインスレッドの方が先に終了してしまい、必ずしも dispatch_async が処理されるとは限らない。

「非同期処理の同期をとる」のような時に使うらしい。
実行結果は期待どおり

非同期タスク実行中
非同期タスク終了
Program ended with exit code: 0

となる。

@synchronized とシングルトン

おまけ。この記事が参考になった。

参考:

プロセス生成と実行

Linux スレッド pthread

MacOS/iOS スレッドプログラミング

Cocoa のマルチスレッドシステム

The document “hogehoge.h” could not be saved. You don’t have permission. アラート

Xcode + (obj-)C 環境でけっこう出る。

おそらく関数の打ち間違い(strlen -> strln など)をそのままにしてビルドを強行してしまい、Xcode は気を利かせて?標準ライブラリのヘッダファイルの複製を作ろうとしているんだと思う。

もちろん、その必要はないし、Xcode SDK のファイルパーミッションはそれを許さない。

解決策は

・DerivedData を削除

・Xcode を強制終了

その後、再起動させるとアラートは出なくなります。