トップ 追記

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|05|06|07|

2019-07-15 [cocoa][swift]【研究】スパゲッティ・コード誕生の理由(わけ)

自分はスパゲティ・プログラムには二種類あって、一つは未熟だから、もう一つは経験も技術もあるのに、と考えている。そして、問題は後者の方で、考えに考え抜いたスパゲティ、理由を聞いても皆が納得するないようで、本人も信念を持って取り組んだ結果だから、それを何とかするのが非常に困難だと考えている。

今回、別件でワインバーグ氏について調べていたら、スパゲティ・プログラムの原因を見つけるヒントが目に留まったので、それと、発見した原因を発表する。

_ よく適応すれbするほど、適応力を失いがちだ。

『コンサルタントの秘密 - 技術アドバイスの人間学 -』で紹介されていて、生物学の分野でロナルド・フィッシャー卿の「フィッシャーの基本定理」と呼ばれるもので、自然淘汰に関する基本定理だ。

ワインバーグ氏の著書では、プログラミングに関して取り上げた定理でないが、この意味を考えてみた閃いた。

経験のある、プロジェクトでも声がでかい人間が生み出すスパゲッティ・プログラム。それは、今、この瞬間に最適なもので、そのため、将来の変化に対応できず、それが、のちの人を苦しめている。

具体的な例を挙げてみよう。

例えば、三つのテーブル・ビューがあるスマートフォン・アプリケーション。一つは新規リスト、もう一つは、編集リスト、最後は削除リスト。リストを管理して、表示するという部分のコードは共通化できる。ベースクラスを導入しよう、と、スパゲティ・プログラム料理人は考える。

class BaseTableViewController {
    func sortList() {
        ....
    }
}

よし、うまくいった。これがリファクタリングだ!俺ってすごい。

ここで悲しい依頼が。新規リストは、条件によって並び替えのルールを変えて欲しい。ああ、どうしよう。そうだ、前処理関数を導入すればいい!

class BaseTableViewController {
    func prepare() {
        ...
    }
    
    func sortList() {
        prepare()
        ...
    }
}
 
class NewTableViewController {
    override func prepare() {
        ...
    }
}

新規リストのクラスで、前処理関数をオーバーライドして、挙動を変えればいい!

こうして、また新たなスパゲティ・プログラムが生み出される!

基本クラスのコードを読んでも、新キリストのコードを読んでも、どんな動きをするのか読み取れない。

では、どうやって、スパゲティ・プルグラムの調理が行われているのを見つけるのか?

長年の研究によって、自分が見つけたのは、以下だ。

  • これはリファクタリングした方がいいな。やろう!
    今この瞬間の共有に適応した、将来の適応力がないコードを生産する掛け声だ!
  • このコードは安全でないので修正する!
    これからプログラミングされる部分は安全であるように心がけることには意義がないが、既に生産されたコード。そして、それが今後も手を加える可能性が低い箇所だとしてら、そいつが単に気に食わないから修正するということの言い訳だ。そんな奴に贈るワインバーグ氏の言葉はこれだ。『壊れていないものを直すな。』

2019-07-14 [cocos2d-x]OpenGL ESからMetalへの移行

iOSもAndroidも、OpenGL ESから次の新しい描画エンジンへの移行を進めている。

Androidについては、GoogleがVulkan上にOpenGL ESを実装して、OpenGL ESの提供は続くようなので安心なのだが、iOSについては、AppleがOpenGL ESを非推奨となっているので、今後、利用できなくなっても不思議ではない。

そこで、cocos2d-xを利用している場合が対応方法を調べてみた。

_ MoltenGL

macOSとiOSでVulkanのAPIを提供するMoltenVKを提供したMoltenの製品で、Metal上にOpenGLとOpenGL ESのAPIを提供している。

MoltenVKは、Khronos Groupと共同でオープンソースとなっていて、cocosのフォーラムでも協力者がいればMoltenGLのオープンソース化も検討していそうだ。

ただ、MoltenGLで、問題なく、OpenGL ESを利用しているアプリケーションがMetal対応になるかは、やってみないとわからないと思う。

_ cocos2d-x metal-supportブランチ

cocs2d-x v3.17から派生したmetal-supportブランチで、Metal対応が進んでいる。

GitHubのIssuesでのやり取りも活発なので、metal-supportブランチが安定して成長するよう、一緒に貢献していくというのもありだと思う。


2019-07-13 [cocos2d-x][OpenGL ES]画面キャプチャ

cocos2d-x v2には、画面キャプチャの機能が用意されていないので、実装した。

ただし、内容には満足していない部分があって、例えば、使用しているOpenGL ESのAPIは呼び出しコストが高いという情報があるのと、フレームバッファの解像度が高ければ高いほどメモリを使用するので、機会があれば改善したい。

/*!
 @brief Screenshotクラス
 */
class MyGrab {
public:
    /*! @brief デストラクタ */
    virtual ~MyGrab();
 
    /*!
     @brief Singletonなインスタンスを返す。マルチスレッドを考えるの、この実装では不受分なのは認識している。
     @result BQGrabのインスタンス
     */
    static MyGrab* getInstance();
 
    /*!
     @brief Screenshot
     @result 画像
     */
    cocos2d::CCImage* grab();
 
private:
    /*!
     @brief コンストラクタ
     @discussion Singletonとする為、コンストラクタは非公開メンバ関数とする。
     */
    MyGrab();
 
    /*!
     @brief 初期化。
     @retval true 成功
     @retval false 失敗
     */
    bool initialize();
};
 
MyGrab::MyGrab()
{
    /* 初期化 */
    initialize();
}
 
MyGrab::~MyGrab()
{
}
 
MyGrab* MyGrab::getInstance()
{
    static MyGrab grab;
    return &grab;
}
 
bool MyGrab::initialize()
{
    return true;
}
 
CCImage* MyGrab::grab()
{
    /* EGLビューのフレーム・サイズを取得する */
    const unsigned int width = (unsigned int)CCDirector::sharedDirector()->getOpenGLView()->getFrameSize().width;
    const unsigned int height = (unsigned int)CCDirector::sharedDirector()->getOpenGLView()->getFrameSize().height;
 
    const unsigned int bytesPerPixel = 4; /* RGBA */
    GLubyte* dataBuffer = NULL;
    dataBuffer = (GLubyte*)malloc(width * height * bytesPerPixel);
 
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dataBuffer);
 
    GLubyte *imageBuffer = NULL;
    imageBuffer = (GLubyte*)malloc(width * height * bytesPerPixel);
    for (int i = 0; i < height; ++i) {
        memcpy(&imageBuffer[i * width * 4], &dataBuffer[(height - i - 1) * width * 4], width * 4);
    }
 
    CCImage* image = new CCImage();
    image->initWithImageData(imageBuffer, width * height * bytesPerPixel, CCImage::kFmtRawData, width, height, 8);
    free(dataBuffer);
    free(imageBuffer);
    image->autorelease();
    return image;
}

glReadPixels()はフレームバッファの内容をピクセルデータとして読み取るコマンドだ。

void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *data);

フレームバッファからウィンドウ座標xとyを左下隅としてwidth × heightのピクセル矩形を読み込み、dataで指定されたアドレスに格納する。formatパラメータは、そのピクセルが何を表しているかをOpenGLに伝える。typeパラメータは、ピクセルのデータ型を示す。


2019-06-07 Google I/O Extended: Recap Live Japan 2019

5/7〜9にかけてマウンテンビューで開催された『Google I/O 2019』の内容をダイジェストで伝えると共に、日本のGoogle Developer Advocateの方々に直接質問できる『Google I/O Extended: Recap Live Japan 2019』が6/7にGoogle東京オフィスで開催されたので、その中から、自分が気になって項目をピックアップした。

_ テーマ

building a more helpful Google for everyone
すべての人々のためにより役立つGoogleへ

_ Playストア

利用者が未インストールがインストールしたことがある、インストール済みなのかが分かるので、それに対応した表示を用意できる。

イベント告知ができる

_ App Bundle

App Bundleを推奨するが、デバッグ用に実機転送するため、apkは必要だね。

_ Firebaseアナリティクス

Firebase 向け Google アナリティクスと名称が変わってる。また、Google アナリティクス開発者サービス SDKが終了しているので、スマートフォンでは、Firebaseアナリティクスを利用することになる。

_ マテリアルシーミング

マテリアルデザインからマテリアルシーミングへ。

_ ダークテーマ

ラウンチャー上のアイコンは、ダームモード用を用意する必要はない。例えば、使用している色がロゴと関係あるなど、変更が難しいと思うので。

_ Java 8

Android StudioにOpenJDKが組み込まれているので、問題はないと認識している。

_ targetSDK

OSがリリースされたら、一年後、新規アプリはtargetSDKでの設定が必要。既存アプリはプラス一ヶ月猶予がある。


2019-05-30 [cocoa][swift]よく利用しているサイト

みなさんも、よく利用するサイトがあると思う。自分の場合は以下だ。

Bourne Shell自習テキスト

文字エンコードはJIS。必要最小限度のHTMLで記述。内容も基本的なこと。

でも、自分はずっと重宝しています。20年近く利用しているのでは?

ちょっと変わったLisp入門

数式処理システムに脅威がある、そこからLISP実装に興味があります。

ここの情報を参考に、Objective-CやSwift、C++などで何度実装したか。

実践!iOSで作るゲームアプリ

iOSのViewと画面管理についても参考になりました。


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

トップ 追記