NSUserDefaults

サンプル

AppDelegate.m
@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; // 取得
    [ud setObject:@"hoge" forKey:@"KEY_S"];
    [ud synchronize];
}

 

参考:https://glassonion.hatenablog.com/entry/20110920/1316473990

保存場所

Non SandBox: /Users/ユーザ名/Library/Preferences

SandBox: /Users/ユーザ名/Library/Containers/アプリ名/Data/Library/Preferences/

なのだそうだが、デバッグ時にそれらしいファイルはなかったな???

→registerDefaults メソッドはファイルに書き出しません!

しかし、standard と shared の違いがわからんなあ。

→shared は文字どおり「共有」ということらしい。
何と共有するかは、この記事によれば、同じグループ間で、ということらしいのだが、使ったことはない。

 

Objective-C 諸々

objective-c や cocoa の割と独自仕様っぽいところ。

delegate

見かける割にいまいち理解できていなかったデリゲート。

「MTKView のデリゲートがレンダラー」と気がつき、ああそういうことかと納得。

大抵の場合、ViewController が MTKView を呼び出す前に

_view.delegate = _renderer;

などとしているはずだ。

メタルの表示用ビューとして MTKView は存在しているが、各アプリでのこのビューの使い方は様々だろうから、そこら辺は自分で DrawInMTKView を実装してね、プロトコルは準備しておくから・・・ということなんだと思う。

わかりにくくなるのは、プロトコルが明示されているわけではないので、その仕組みに気が付きにくいからじゃないでしょうか。

本来であれば以下のような記述が必要なようだ。

委譲する側(のヘッダ)

@protocol HogeDelegate

@property (weak, nonatomic) id delegate;

@protpcol HogeDelegate <NSObject>
-(void)FugaMethod;
@end

以下のように明示的にぶん投げるときもある。

委譲する側(の .m ファイル)

[self.delegate FugaMethod] //こうやって使うようだ

ぶん投げられる側はプロトコル準拠を明示しておく。

委譲される側(のヘッダ)

@interface Fuga: NSObject <HogeDelegate>

ところで、これが面白いのは、ある種の情報の通知として利用できる点。
上の例なら、Hoge の情報を Fuga で受けられる。
ViewerController に受け持たせてしまい、あるオブジェクトでの情報をビューに反映させるといった使い方ができる。

参考:https://dev.classmethod.jp/articles/ios-delegate/

マルチスレッド処理

NSTread を使う方法、dispatch_async – queue の系を使う方法などいくつかあるようだ。

(続く)

KVO

https://qiita.com/mitsu9/items/e738c41a8d87cc71ec3e

KVOとはキー値監視(Key-Value Observing)のことを指します。
アプリ開発をかじったことのある人ならMVCという言葉を聞いたことがあると思いますが、KVOはModelの変更をControllerやViewに通知し、反映させるための仕組みです。
他の通知システムとしてはdelegateやNSNotificationを利用した方法などがあります。

なるほど。

サンプルとして MCV を意識したメモアプリあり。

インスタンス変数の宣言

Objective-Cでは、インスタンス変数をどこに宣言するのが正しいのか? 』が興味深かった。

やはり、みなさん疑問に思っていたんですね。

参考

phorlix100 の github issues
いやー、大変参考になりました!

NSSplitView

今すぐどうこうというわけではないが、NSSplitView を使えるとアプリのウィンドウの表現が豊かになりそうなので、ちょっと調べる。

ネット上で使えそうなリソースは・・・

How to Use NSSplitView in a macOS app

まあ、良い記事ではあると思うけど、ちょっと応用的でしょうか。

動画もあったが、現代的ではない。

いつもの通り、公式ページもチェック。

 

結局、自分で手を動かしてまあまあ使えるようになった。

上半分は Metal の View で下半分が NSView を Core Graphics で描画したものです。
NSSplitVewDelegate プロトコルを適用するところで、おかしな挙動するんじゃないかと思ってましたが、そんなことはなく。結果オーライということで。

静止画だと伝わらないですが、ディバイダで上下の比率を変えることができます。

今回はちょっと楽して ViewController に NSSplitVewDelegate プロトコルを適用してます。

 

retain, release と -fno-objc-arc

これまで UI パーツの使い方を割と丁寧におさえてきたが、ここら辺で cocoa (最近だと AppKit というのかな?)アプリの作成に慣れるために、個人的に試験的なアプリを作成している。

ARC をオフにするには

その際に「これはいくらなんでも説明不足なんじゃ?」と思ったのが、ARC の使い方。

まず、「現在ではデフォルトで ARC がオンになっている」みたいなことは書かれているが、では、どうやって ARC をオフにしていいのか?そうすることのメリットは?みたいな説明はほとんどなされていない。

いやあ、これだと、スニペットみたいなコードは書けても、アプリを作成する際に色々と不都合が起こると思う。

その説明はいったんおいて、実用的なことを先に書くと、ARC = オンになっている Obj-C プロジェクトである部分だけをオフにしたいならば、TARGETS -> Build Phase -> Compile Sources でオフにしたいファイルの Compiler Flags に -fno-objc-arc というオプションを与える

これで、このファイルに含まれるクラスの ARC は無効になる。
あるオブジェクトを保持したければ retain (メソッド)を、破棄したければ release (メソッド)を明示する。

上の例では AppDelegate にこのオプションを与えたが、多分、これはかなり実用的な例。
ワイが試験的に作成しているアプリはまだ構造はさほど複雑ではないので、WindowsController は AppDelegate に集約している。
この時、困ったのは、あるウィンドウを閉じた時、(ARC をオンにしていると)そのウィンドウを管理しているコントローラーの存在が、不安定なように見えたこと。
ウィンドウをクローズするというのは、ウィンドウオブジェクトが破棄されたのか、それとも見えなくなっているだけでどこかにあるのかなどいくつか可能性があると思うが、この時の挙動がはっきりしない。

例えば、あるウィンドウのコントローラー(のポインタ)を wc としたとき

if(wc = nil){
  //処理
}

のようなコードを書いても、思ったような処理ができなかった。

だが、ARC をそこだけオフにしてみたところ、今の所狙ったようで動作している。

問題は使い分け

ARC に関してある程度把握はできたが、だからと言って、以降、すべて ARC=オフ にしてコーディングするかといえば、そんなことはないと思う。

まだ慣れていないせいもあると思うが、生成したすべてのオブジェクトで参照カウンタを気にするのは大変負担のように思える。

具体的な使い分けに関しては(この記事以外、ほとんど和文の資料はないと思うが)

retain と release の関係について

を参照してください。

 

NSTimer

ここしばらく Objective-C & MacOS の話題が続いているが、深い理由はない。

意外に使えることがわかってきて、この環境が好きになってきたから。

今回は、NSTimer の話。

結論から書くと、簡単に試すことができる。

タイマー関係といえば、昔、Java でえらい目にあったことがある。
動作がむちゃくちゃ不安定

まあ、Java の実行環境を考えるとしょうがないでしょう。

サンプル

それはともかく、検証に使ったソースコードは github にあげてあるのでよろしく。


@implementation Timer

-(id)initWithTimer
{
   self = [super init];
  if(self){
     NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0f
                                                       target:self
                                                     selector:@selector(doTask:)
                                                     userInfo:nil
                                                      repeats:YES];
     //[timer fire];
    }

     return self;
}

-(void)doTask:(NSTimer *)timer
{
   //[timer invalidate];
   NSLog(@"timer");
}

@end

コーディング自体は簡単。
上のように Timer クラスを作って、AppDelegate から呼び出すのみ。

これでタイマー処理ができてしまう。

実行すると所定の間隔で doTask メソッドを実行してくれます。