トップ 追記

Cocoa練習帳

iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど

2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|

2019-04-22 [cocoa][swift]iOSアプリケーション開発入門:はじめの一歩

_ # 開発環境

  • mac
  • iOS機器

_ # Apple Developer Program

  • https://developer.apple.com/jp/programs/
    • 無料でも開発できますが、有料の登録をお勧めします。

_ # 参考資料

  • 無料の"ブック"アプリのSwift Programming Series
  • 日本語ドキュメント
    • https://developer.apple.com/jp/documentation/
  • App Programming Guide for iOS
    • https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html

_ # はじめてのアプリケーション

Xcodeを起動し、File / New / Project... を選択する。

NewProject

iOS / Single View App を選択する。

SingleViewApp

サンプルでは、Product Nameを「HelloWorld」としました。

ProductName

ストーリーボードでビューの背景色を設定します。

BGColor

「Run」ボタンをクリック(または「Product」>「Run」を実行)して、シミュレータを起動します。

sim

設定した背景色で起動します。

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/HelloWorld - GitHub

2019-04-21 [cocoa][swift]形態素解析

「私」物語化計画という作家を育てるオンラインサロンに参加しているのだが、プログラマとして貢献できる方法として、執筆に役立つアプリケーションの開発に取り組んでみたいと考えている。その第一歩として、同一文章中に出てくる単語の個数を気にしているという話を聞いたので、テキストから文法情報を抽出する方法を調べたので発表する。

macOSで利用できる形態素解析 (Morphological Analysis) の機能は複数存在する。それは、それが実装された時期に利用できた機能などと関係があるからだと思う。

_ # NSLinguisticTagger

iOS 5.0+
macOS 10.7+

文章を品詞や属性で区分するクラス。ただし、日本語の場合、品詞には対応していないようだ。

_ # NSOrthography

iOS 4.0+
macOS 10.6+

スペルチェックや文法チェックで利用されることを目的としたクラスのようだ。

_ # CFStringTokenizer

iOS 3.0+
macOS 10.5+

以前、発表したことがあるAPIだ。当時は、問題がないAPIだったが頭にCFという言葉が付いているように、以前のCoreFoundation + Cocoa、つなり、NSString時代だと思われ、SwiftのStringとは合っていない部分があると思う。

_ # NSString enumerateSubstringsInRange

iOS 4.0+
macOS 10.6+

文字列を形態素解析と品詞分解するAPIだが、NSStringのメソッドということで、SwiftのStringとは合っていない部分があると思う。

今回の用途では、NSLinguisticTaggerが合っているのではと考えてサンプルコードを作ってみた。

let text = "東京は、哀しい活気を呈していた、とさいしょの書き出しの一行に書きしるすというような事になるのではあるまいか、と思って東京に舞い戻って来たのに、私の眼には、何の事も無い相変らずの「東京生活」のごとくに映った。"
let linguisticTagger = NSLinguisticTagger(tagSchemes: NSLinguisticTagger.availableTagSchemes(forLanguage: "ja"), options: 0)
linguisticTagger.string = text
linguisticTagger.enumerateTags(in: NSRange(location: 0, length: text.count),
                     scheme: NSLinguisticTagScheme.tokenType,
                     options: [.omitWhitespace]) {
                        tag, tokenRange, sentenceRange, stop in
                        let subString = (text as NSString).substring(with: tokenRange)
                        print("\(subString) : \(String(describing: tag))")
}

_ 以下が、その結果。

東京 : Optional(__C.NSLinguisticTag(_rawValue: Word))
は : Optional(__C.NSLinguisticTag(_rawValue: Word))
、 : Optional(__C.NSLinguisticTag(_rawValue: Punctuation))
哀しい : Optional(__C.NSLinguisticTag(_rawValue: Word))
活気 : Optional(__C.NSLinguisticTag(_rawValue: Word))
を : Optional(__C.NSLinguisticTag(_rawValue: Word))
呈し : Optional(__C.NSLinguisticTag(_rawValue: Word))
て : Optional(__C.NSLinguisticTag(_rawValue: Word))
い : Optional(__C.NSLinguisticTag(_rawValue: Word))
た : Optional(__C.NSLinguisticTag(_rawValue: Word))
、 : Optional(__C.NSLinguisticTag(_rawValue: Punctuation))
・・・(略)・・・
「 : Optional(__C.NSLinguisticTag(_rawValue: Punctuation))
東京 : Optional(__C.NSLinguisticTag(_rawValue: Word))
生活 : Optional(__C.NSLinguisticTag(_rawValue: Word))
」 : Optional(__C.NSLinguisticTag(_rawValue: Punctuation))
の : Optional(__C.NSLinguisticTag(_rawValue: Word))
ごとく : Optional(__C.NSLinguisticTag(_rawValue: Word))
に : Optional(__C.NSLinguisticTag(_rawValue: Word))
映っ : Optional(__C.NSLinguisticTag(_rawValue: Word))
た : Optional(__C.NSLinguisticTag(_rawValue: Word))
。 : Optional(__C.NSLinguisticTag(_rawValue: Punctuation))

今後は、これをテキストエディターに組み込んでみたい。

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/mac/MorphologicalAnalysis - GitHub

2019-03-22 [cocoa][swift]テーブルビューを使ったmacOSアプリケーション

macOSとiOSのUI関連のフレームワークには差異があり、前者はNeXTSTEPから受け継がれたスタイルとなっていて、後者は貧弱な計算機リソースでの利用を想定した設定となっている。

ただ、最近ではmacOSのフレームワークの方がiOSの方に合わせることが可能な部分については似せてきていて、テーブルビューの場合は昔からのCell BasedとiOSと同様なView Basedの二通りの方式があるという状況だ。前者については非推奨となっているため、後者についてサンプルを作りながら説明する。

新規プロジェクトを生成。macOSでCocoa Appを選択。

新規プロジェクト

"Use Storyboards"と"Create Document-Bassed Application"を選択する。

プロジェクトの種類

nib(ファイル名のsuffixはxib)でなくStoryboardを選んだ場合、iOSに似たViewControllerを利用した構成となる。iOS向けの開発経験者にとっては、その方が分かりやすいと思うが、macOSプログラミングの書籍の多くはnibの場合の説明となっているので、どちらが学習しやすいか悩ましい問題だ。ただ、今後はStoryboardだと思うので茨の道だがこちらの道を選択することにした。

Storyboardで、ViewにライブラリからTable Viewを配置し、TableViewのContent ModeがView Basedになっていることを確認する。

ViewBased

サンプルでは、テーブルの列を2にしたが、列に識別子をつけるのは大事だ。iOSの場合、列は1個のみなので、行番号となるインデックスのみで良かったが、macOSでは列を識別するものが必要だ。また、列の順番を変更することができるので、列インデックスでは対応できない。

列の識別子

Storyboardを選ぶと、ビューコントローラが生成されるが、これとTable ViewをdataSourceとdelegateで繋げる。

繋げる

ターブルビューの行数や、表示する欄の設定は、iOSのUITableViewと似ている。

ViewControllerの親にNSTableViewDataSourceとNSTableViewDelegateを追加する。

class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
    ...
}

行数を返す。

    public func numberOfRows(in tableView: NSTableView) -> Int {
        return 64
    }

欄の内容をビューで返す。

    public func tableView(_ tableView: NSTableView, viewFor
        tableColumn: NSTableColumn?, row: Int) -> NSView? {
        print(#function + " column:" +
            tableColumn!.identifier.rawValue + " row:" + String(row))
        var result: NSTextField? =
            tableView.makeView(withIdentifier:NSUserInterfaceItemIdentifier(rawValue:
                "MyView"), owner: self) as? NSTextField
        if result == nil {
            result = NSTextField(frame: NSZeroRect)
            result?.identifier =
                NSUserInterfaceItemIdentifier(rawValue: "MyView")
        }
        if let view = result {
            print("view:" + view.identifier!.rawValue + " row:" + String(row))
            if let column = tableColumn {
                if column.identifier.rawValue == "TableColumn1" {
                    view.stringValue = "column 01"
                }
                else if column.identifier.rawValue == "TableColumn2" {
                    view.stringValue = "column 02"
                }
            }
        }
        return result
    }

Storyboardで設定した識別子で列がわかるので、列に対応したビューを返している。

好奇心公正な人なら、非推奨だと分かっていても、以前のCell Basedが気になっていると思うので調べてみた。

行数を返すのは同様だ。

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
    NSInteger count=0;
    if (self.namesArray)
        count=[self.namesArray count];
    return count;
}

欄の内容を返すのも似ている。

- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
    id returnValue=nil;
    NSString *columnIdentifer = [aTableColumn identifier];
    NSString *theName = [namesArray objectAtIndex:rowIndex];
    if ([columnIdentifer isEqualToString:@"name"]) {
        returnValue = theName;
    }
    return returnValue;
}

ただ、メソッドの戻り値の型がidだ。どういう事なのだろうか?

Cell Basedでは、ビューを管理するセルが存在する。デフォルトでは文字列を表示するビューが用意され、それを管理するセルに対して、値となるNSStringを返しているということのようだ。

複雑な欄を表示したい場合、それ様のセルを用意することになるが、それが一手間となるし、セルはビューでないので、ビューの重なりも独自に保持しないといけない。

大変そうなので、Cell Basedの探求はここまでにしておこう。

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/mac/VisiCalc - GitHub

2019-02-20 [git][OSS]Upstream開発

GitHubで公開されているオープンソースなコードを利用する際、修正が必要になった場合の対応方法として、Upstream開発の手順を説明する。

Upstream開発とは、簡単に説明すると、修正内容を本家にpull requestを投げて取り込んで貰い、独自に手を加えてしまった結果、本家から枝分かれする状況を避ける方法だ。

利用しているOSSへの貢献にもなるので、一石二鳥とも言える。

_ GitHubでforkする

GitHubで本家から自分の領域にforkする。

理由は、本家のリポジトリは普通はpush権限がないので、修正は自分のリポジトリに対して行う。

_ 自分の作業領域にcloneする

$ git clone git@github.com:murakami/cocos2d-x.git
$ cd cocos2d-x

_ 本家と内部のリポジトリをリモートに追加する

forkしたリポジトリで修正作業を行うのでなく、別に内部のリポジトリが存在する前提で、本家と内部のリポジトリをリモートに設定する。

その理由は、後で分かる。

$ git remote add upstream git@github.com:cocos2d/cocos2d-x.git
$ git remote add bitz ssh://git@git.bitz.co.jp/bitz/cocos2d-x.git
$ git remote
bitz
origin
upstream

設定されていることを確認。

_ 本家とfork先を同期する

$ git fetch upstream
$ git pull upstream ブランチ名
$ git push origin ブランチ名

_ 内部リポジトリの修正内容をfork先にchery-pickで取り込む

$ git fetch bitz
$ git checkout -b feature/修正ブランチ名
$ git cherry-pick XXXXXX
$ git push origin feature/修正ブランチ名

_ 本家に対してPull requestを作成する

GitHubのforkしたリポジトリのPull reqestsで、本家のブランチに対してPull requestが作成する。

forkしたブランチは公開されているものなのと、余計なログで汚れていないことが望ましいということで、forkしたブランチで開発作業を行うのは避けたほうがいい。


2019-02-11 [cocoa][swift]作譜用言語PL/0 表駆動の構文解析

『Algorithms + Data Structures = Programs』は、ニクラウス・ヴィルト氏の著名な書籍で、翻訳された書籍の表題は『アルゴリズム+データ構造=プログラム』だ。サンプルは、Pascalで記述され構造化プログラミングのバイブル的な書籍だ。

その次の版は、『アルゴリズムとデータ構造』と『翻訳系構成法序論』の二冊に分かれ、Modula-2で記述されている。その次の版は、Oberonで記述されているらしいが、残念ながら入手は困難だ。

ニクラウス・ヴィルト氏は、AppleのObject Pascalの開発にも関与したという話を聞いたことがある。

『翻訳系構成法序論』の感想は、最少限度の知識のみを必要とし、簡素な内容となっていて、初学者の教科書としては理想的ではないかと思っている。

それでは、続きを始める。

前回は、ゴリゴリと記述していたコードを「表駆動の構文解析」では、汎用的なコードを処理するという内容だ。

読み取った記号が終端記号か非終端記号か、そして、次の処理に移動するのか、if分の条件分岐のように、別候補に移動するのかを保持する表、Swiftでは構造体やクラスがそれに対応するので、実装してみる。

class Node {
    public var successor: Node? = nil
    public var alternative: Node? = nil
    public var terminal: Bool = true
    public var terminalSymbol: Character = "\0"
    public var nonterminalSymbol: Header? = nil
}

上記では未定義だった見出しに相当するのが以下だ。

class Header {
    public var symbol: Character = "\0"
    public var entry: Node? = nil
}

これを使いと前回の分析子の処理は以下となる。

func parse(goal: Header, match: inout Bool) {
    var s: Node? = goal.entry
    repeat {
        if s!.terminal {
            if s!.terminalSymbol == ch {
                match = true
                readChar()
            }
            else {
                if s!.terminalSymbol == empty {
                    match = true
                }
                else {
                    match = false
                }
            }
        }
        else {
            parse(goal: s!.nonterminalSymbol!, match: &match)
        }
        if match {
            s = s!.successor
        }
        else {
            s = s!.alternative
        }
    } while s != nil
}

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/mac/pl0 - GitHub

2019-01-27 [cocoa][swift]作譜用言語PL/0 構文に対する分析子の作成

有名な『Algorithms + Data Structures = Programs』の後半を独立して誕生した『COMPILERBAU:』を翻訳した『翻訳系構成法序論』を今の電子計算機環境で取り組んでみた。

使用するプログラミング言語Swiftを選択したのだが、コンパイラの実装には少々向いていない部分があるので、まずは、一文字読み込んで処理するサンプルを記述してみた。

import Foundation
 
let parser = Parser()
import Foundation
 
class Parser {
    var ch: Character = "\0"
    var lineString: String = ""
    
    init() {
        readChar()
        S()
    }
    
    func readChar() {
        lineString = readLine()!
        ch = lineString[lineString.index(lineString.startIndex, offsetBy:0)]
        lineString = String(lineString.suffix(lineString.count - 1))
    }
    
    /*
     開式記号に対応する手続き。
     */
    func S() {
    }
}

それでは、教科書のサンプルコードを記述してみよう。

以下の約束事があるとする。

A="x"|"("B")".
B=AC.
C={"+"A}.

これは以下のようになる。

x
(x)
(x+x)
((x))
((x+(x+x)))

これを実装してみると以下となる。

/* 分析子 */
class Parser {
    var ch: Character = "\0"
    var lineString: String = ""
    
    init() {
        print("\(#function)")
        readChar()
        A()
    }
    
    func A() {
        //print("\(#function)")
        if ch == "x" {
            readChar()
        }
        else if ch == "(" {
            readChar()
            A()
            while ch == "+" {
                readChar()
                A()
            }
            if ch == ")" {
                readChar()
            }
            else {
                error()
            }
        }
        else {
            error()
        }
    }
 
    func readChar() {
        if lineString.count <= 0 {
            lineString = readLine()!
        }
        ch = lineString[lineString.index(lineString.startIndex, offsetBy:0)]
        lineString = String(lineString.suffix(lineString.count - 1))
        print(ch)
    }
    
    func error() {
        print("error: \(#function)")
        exit(-1)
    }
}

ここまでは、約束事を愚直にコードで処理しているという感じだ。

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/mac/pl0 - GitHub

2019-01-14 [cocoa][swift]Swift Protobufについて

JSON(JavaScript Object Notation)は、通信などで利用されているデータ交換フォーマットだ。人によって読み書きが容易で、かつ、計算機にとって簡単に解釈や生成が行える。また、システムの標準的なライブラリが対応していて導入が楽という利点もあり、広く利用されている。

JSONにかわるものとしてMessage Packというフォーマとがあって、コンパクトで率的に扱えるということで、自分の周りでは使われだしている。ただ、macOS/iOSプログラマーとして難点なのは複数のライブラリが存在するのだが、安心して利用できる決定打がないということだ。また、IDL (Interface Definition Language) による読み書きコードの生成の需要があるのだが、Message Packは利用側が独自に用意するしかないということだ。

IDLが利用できるデータ交換フォーマットは色々あるのだが、Googleがオープンソースとして公開してたProtocol Buffersというデータ交換フォーマットのSwiftライブラリをAppleが用意していることを知った。そこで、どのような使いごこちなのか試してみることにした。

これを例えば、~/binにコピーして、~/.bash_profileなどでパスを通しておく。

PATH="$HOME/bin/protoc-3/bin:$HOME/bin:$PATH"

次に、Swift Protobuf プラグインを組み込む。

最も手軽な方法は、Homebrewだと思う。

$ brew install swift-protobuf

でも、今回はgithubからクローンするやり方を選んだ。

$ git clone https://github.com/apple/swift-protobuf.git
$ cd swift-protobuf

リリース・バージョンを確認する。

$ git tag -l

この記事を書いている時に最新だった1.3.1を選んでビルドする。

$ git checkout tags/1.3.1
$ swift build -c release -Xswiftc -static-stdlib

.build/release に、protoc-gen-swift というファイルが生成されているので、これを例えば ~/bin にコピーする。

以下のようなprotoファイル"my.proto"を用意する。

syntax = "proto3";
 
message BookInfo {
   int64 id = 1;
   string title = 2;
   string author = 3;
}

これを以下のコマンドでswiftコードに変換する。

$ protoc --swift_out=. my.proto

my.pb.swift というファイルが生成されているはずだ。

次に、自分のXcodeプロジェクトに組み込む。

CocoaPodsやCarthageを利用する方法があるが、ここでは、Swift ProtobufのXcodeプロジェクトを自分のXcodeプロジェクトに組み込む方法でやることにする。

自分のXcodeプロジェクトに組み込むと、TARGETSのBuild PhasesのTarget DependencesにmacOS/iOS/tvOS/watchOS毎に追加できるようになっているので、必要なものを追加する。

そして、先ほど、生成した my.pb.swift を追加する。

すると、以下のように呼び出せるはずだ。

// Create a BookInfo object and populate it:
var info = BookInfo()
info.id = 1734
info.title = "Really Interesting Book"
info.author = "Jane Smith"
 
// As above, but generating a read-only value:
let info2 = BookInfo.with {
    $0.id = 1735
    $0.title = "Even More Interesting"
    $0.author = "Jane Q. Smith"
}
print("\(info2)")
 
// Serialize to binary protobuf format:
let binaryData: Data = try! info.serializedData()
 
// Deserialize a received Data object from `binaryData`
let decodedInfo = try! BookInfo(serializedData: binaryData)
print("\(decodedInfo)")
 
// Serialize to JSON format as a Data object
let jsonData: Data = try! info.jsonUTF8Data()
 
// Deserialize from JSON format from `jsonData`
let receivedFromJSON = try! BookInfo(jsonUTF8Data: jsonData)
print("\(receivedFromJSON)")

2019-01-08 [cocoa][swift]マイDocumentクラス

一月の勉強会でMVCについてディスカッションすることになった。そこで場が盛り上がるよう、ネタとして自分がよく採用するマイDocumentクラスに発表する。

アプリケーションの設計法としてMVCが話題となることが多いが、それは、MVCはデザイン・パターンが話題になる以前のもので、今のデザイン・パターンから見ると複数のパターンが組み合わさった大きな枠組みのものだというのも理由としてあるのかな?

早速本題に入る。Appleの文書で説明されている伝統的なMVCは以下のとおり。

Traditional version of MVC as a compound pattern

これは、この論文でも説明されている。

そして、Cocoa版は以下となる。

Cocoa version of MVC as a compound design pattern

ViewとModelは直接やりとりしない。間にControllerを挟んでいるのが特徴だ。このControllerだが、一種類でない。NSApplicationDelegateだったりNSDOcumentだったりNSControllerだったりNSViewControllerだったり、アプリケーション独自のクラスだったりする。

iOS開発を始めたときから独自のDocumentクラスを用意するようにしている。

@interface Document : NSObject
@property (strong, nonatomic) NSString              *version;
 
+ (Document *)sharedDocument;
- (void)load;
- (void)save;
@end
@interface Document ()
- (void)_clearDefaults;
- (void)_updateDefaults;
- (void)_loadDefaults;
- (NSString *)_modelDir;
- (NSString *)_modelPath;
@end
 
@implementation Document
 
@synthesize version = _version;
 
+ (Document *)sharedDocument;
{
    static Document *_sharedInstance = nil;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[Document alloc] init];
    });
    return _sharedInstance;
}
 
- (id)init
{
    self = [super init];
    if (self) {
        _version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    }
    return self;
}
 
- (void)dealloc
{
    self.version = nil;
}
 
- (void)load
{
    [self _loadDefaults];
    
    NSString    *modelPath = [self _modelPath];
    if ((! modelPath) || (! [[NSFileManager defaultManager] fileExistsAtPath:modelPath])) {
        return;
    }
}
 
- (void)save
{
    [self _updateDefaults];
    
    NSFileManager   *fileManager = [NSFileManager defaultManager];
    
    NSString    *modelDir = [self _modelDir];
    if (![fileManager fileExistsAtPath:modelDir]) {
        NSError *error = nil;
        [fileManager createDirectoryAtPath:modelDir
               withIntermediateDirectories:YES
                                attributes:nil
                                     error:&error];
    }
    
    NSString    *modelPath = [self _modelPath];
    [NSKeyedArchiver archiveRootObject:self.indexArray toFile:modelPath];
}
 
- (void)_clearDefaults
{
    if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"version"];
    }
}
 
- (void)_updateDefaults
{
    NSString    *versionString = nil;
    if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
        versionString = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
    }
    if ((versionString == nil) || ([versionString compare:self.version] != NSOrderedSame)) {
        [[NSUserDefaults standardUserDefaults] setObject:self.version forKey:@"version"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}
 
- (void)_loadDefaults
{
    NSString    *versionString = nil;
    if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
        versionString = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
    }
    if ((versionString == nil) || ([versionString compare:self.version] != NSOrderedSame)) {
        /* バージョン不一致対応 */
    }
    else {
        /* 読み出し */
    }
}
 
- (NSString *)_modelDir
{
    NSArray    *paths;
    NSString   *path;
    paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    if ([paths count] < 1) {
        return nil;
    }
    path = [paths objectAtIndex:0];
    
    path = [path stringByAppendingPathComponent:@".model"];
    return path;
}
 
- (NSString *)_modelPath
{
    NSString   *path;
    path = [[self _modelDir] stringByAppendingPathComponent:@"model.dat"];
    return path;
}
@end

macOSのDocument-Based. Applicationsについて説明する。

主要なクラスは、以下の3つ。

クラス 内容
NSDocument データ管理。
NSWindowController ウィンドウ管理。
NSDocumentController ドキュメント管理。

ファイルとDocument、Modelの関係を図だ。

Document

macOSのDocument-Based Applicationのクラスの関係を図示する。

Document-Based Apps
Key objects in a document-based app

2018-12-23 [cocoa][swift]Universal Links

以前、組み込みWebブラウザの会社に勤めていた関係で、独自の機能の呼び出しにURLスキームを利用する方法はよ馴染みがある。iOSでも同様にCustom URL Schemeを利用してアプリケーションを呼び出すことができるが、この方法は、スキームが重複と、意図しない外部サービスから呼べるという問題がある。

そこで登場したのがUniversal Links。

ただ、Custom URL Schemeの知識は必要だと思うので、まずは説明する。

_ # Custom URL Scheme

形式は、以下のとおり。

スキーム名://〜

スキーム名は、重複を避けるため、ドメイン名を逆から並べる、バンドルIDから派生した文字列がオススメされている。例えば、バンドルIDがcom.example.editorのアプリケーションの一覧画面を表示するスキーム名は、com.example.editor.listという感じにする。

Custom URL Schemeは、Info.plistに定義するが、Xcodeの画面から設定する方法もある。

まずは、Info.plistでの設定箇所を説明する。

  • URL typesの
    • Item nに
      • URL identifierにユニークな識別子を設定。バンドルIDから発生した文字列がオススメ。
      • URL Schemesに
        • Item nに、スキーム名を設定。複数設定可能。

Xcodeの画面から設定する場合は、TARGETS > Info > URL Types のIdentifierに識別子をURL Schemesにスキーム名を設定する。

iOS9以降では、安全性の観点からLSApplicationQueriesSchemesを設定するなどの対応が必要となる。

_ # Universal Links

iOS9以降から利用可能。なので、それより前のバージョンをサポートする場合は、Custom URL Schemeの対応も必要。

対応手順は以下のとおり。

Webサーバのルートに、関連付けファイル (apple-app-site-association) を配置する。

{
	"applinks": {
		"apps": [],
		"details": {
			"チームID.com.example.アプリ名(バンドルID)": {
				"paths":[ "*" ]
			}
		}
	}
}

ファイル名は apple-app-site-association。

Content-Typeは application/json。

Appleの資料では、署名がある場合のContent-Typeは、application/pkcs7-mimeという説明があるが、Webサーバとの通信はHTTPSが推奨されているので署名されてなくても、なので、訳あってHTTPの場合の対応のようだ。

ちなみに、署名する場合は、以下の感じのコマンドとなる。

$ openssl smime \
    -sign \
    -nodetach \
    -in "unsigned.json" \
    -out "apple-app-site-association" \
    -outform DER \
    -inkey "private-key.pem" \
    -signer "certificate.pem"

アプリケーション側の設定は以下のとおり。

XcodeのTARGETS > Capabilities > Associated Domains をONにして、Domainsにサーバのドメイン名を設定する。

これで、App ConnectのAppIDsが、この内容で更新されるはずだが、されていなかったら手動で変更する。

Universal Linksには、Universal Linksの設定が正しいかどうかを確認するサイトが用意されている。

  • App Search API Validation Tool
    https://search.developer.apple.com/appsearch-validation-tool/

Universal Linksからの呼び出しは、UIApplicationDelegateの、以下のメソッドで検出できる。

optional func application(_ application: UIApplication, 
                 continue userActivity: NSUserActivity, 
       restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool

2018-12-12 [cocoa][swift]ターミナルでgitの状態を表示する

ターミナルでgit操作をしている際に、プロンプトにgitの状態が表示されると便利だ。

Appleが提供する開発環境は、App Store経由で入手できるXcodeをインストールするだけで完了するが、開発者サイトから個別にダウンロードできるCommand Line Tools for Xcodeをインストールしたのちに、以下の対応を行うと、プロンプトにgitの状態が表示される。

.bashrcに以下を記述する。

# Git
 
if [ -f /Library/Developer/CommandLineTools/usr/share/git-core/git-completion.bash ]; then
	source /Library/Developer/CommandLineTools/usr/share/git-core/git-completion.bash
fi
 
if [ -f /Library/Developer/CommandLineTools/usr/share/git-core/git-prompt.sh ]; then
	source /Library/Developer/CommandLineTools/usr/share/git-core/git-prompt.sh
fi
 
GIT_PS1_SHOWDIRTYSTATE=true
GIT_PS1_SHOWUNTRACKEDFILES=true
GIT_PS1_SHOWSTASHSTATE=true
GIT_PS1_SHOWUPSTREAM=auto
 
PS1="\h:\W \u\$(__git_ps1)$ "
 
# End Of File

これで完了だ。


トップ 追記