Objective-C 再入門

普通に iPhone アプリを使うなら swift でいいんだろうが、既存の C/C++ ライブラリを組み込んで使うような場合などは、Objective-C も検討せねばならないだろう。

特にレガシーなプロジェクトなぞ、本体が Objective-C で書かれているので、swift に移植するにせよ、ある程度の Objective-C の理解は必須だろう。

なぜ Objective-C はとっつきにくいのか?

ところで、Objective-C のクセの強さは、万人の知るところだが、これは歴史的背景を考えると理解しやすいかもしれない。

C にオブジェクト指向の思想を取り込むために、大雑把に二つのアプローチがあったようだ。

一つは言語自体の設計を変えてしまう方法。これが現在の主流で、 C++ の源流となっている。

もう一つは、C のコンパイラー自体には大きな変更を加えず、文法的な表記に特殊仕様を入れ、パース時にこれを解釈して、オブジェクトを取り扱えるようにする方法。Objective-C は、どちらかといえば、こちらだろう。

だから、C++ 感覚で取り組もうとすると訳のわからないことになってしまう。

また、Objective-C がとっつきにくいのは、ビギナー向けのほとんどのサンプルが Mac や iPhone の GUI アプリだという点。これだと、Objective-C の勉強というよりも cocoa などの勉強になってしまい、Objective-C の言語の本質みたいなものが掴みにくい。

サンプル

というわけで、GUI 一切なしのコマンドラインプログラムを試しに書いてみた。


#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <stdio.h>
// Person クラスの宣言
@interface Person : NSObject {
    NSString *name;
}
- (void)setName :(NSString *)Name;
- (void)printName;
@end
// Person クラスの実装
@implementation Person : NSObject
- (void)setName :(NSString *)Name {
    name = Name;
}
- (void)printName {
    printf("My name is %s \n", [name UTF8String]);
}
@end
// 実行プログラム
int main(void) {
    id person;
    person = [[Person alloc] init];//Person *person = new Person(); などとはしない
    [person setName:@"akiba"];
    [person printName];
    return 0;
}

これを Xcode 上で実行するとこんな感じになる。

Person クラスを実体化して、フィールド変数の name をセットした後、ゲッター経由でこの値を取得・表示するというよくあるプログラムだ。

GUI がないから、ワンファイルにすっきりと収まっているが、(Java や C++ に慣れた目から見ると)違和感を感じるところがちょいちょいある。

まず、どうということはないクラス Person でも NSObject を継承している点。またクラスを定義するときに@ディレクティブを多用している。

Objective-C の「C のコンパイラー自体には大きな変更を加えず、文法的な表記に特殊仕様を入れ、パース時にこれを解釈して、オブジェクトを取り扱えるようにする」という歴史的経緯に由来する特徴がよく出ているのではないかと思う。

ところで、最も違和感のあるのはこの箇所だろう。

    person = [[Person alloc] init];

オブジェクトに対してメッセージを送ることで機能を発現させているわけだが、これも歴史的な背景があり、 SmallTalk の影響らしい。

ただし、id 型は必ずしも使う必要はなく、

Person *person = [[Person alloc] init];

としても動きます。

また、Objective-C は C の拡張なので、C の関数も当然使える。実際、printf は C と同じように動作している。

その他、注意しておきたいところ

フィールド変数(インスタンス変数)

name のセッターは setName になってますが、フィールド変数の先頭文字を「大文字」にするのはお約束のようです。
ここら辺は Java と一緒でしょうか。

UTF8String がわからん、という声も聞きますが、name 自体は NSString (へのポインタ)です。標準 C の String に変換する必要があるので、ここで変換をかけているわけです。

なお、インスタンス変数の宣言の仕方などについては、この質疑応答が凄まじくよくまとまっています。

m と mm の違い

Objective-C のファイルは .m .mm の二つがあり得ます。
この記事がわかりやすいんですが、会員限定かな。
簡単にいえば、.m が objective-C ファイル本来の拡張子、.mm の方は C++ のファイルを取り込む際に使用する拡張子です。

継承とカテゴリ

オブジェクト指向の特徴の一つは継承だが、Objective-C はここら辺は素直だ。

よくわからんのは「カテゴリクラス」。

Person(hogehoge)

とあった場合、このクラスは元の Person のカテゴリクラスです。

元の場所とは違った場所でメソッドが追加できます。(同じ理由だが分割することもできる)

情報の古い(アップデートされてない)記事が多い

Obj-C ユーザーが少ない理由に、記事がそもそも少ないし、あっても古い(アップデートされてない)というのも一因になっていると思う。

例えば、よく「Obj-C ではクラス変数という概念がない」などと紹介されているが、2016 以降(OS 10.12 〜)はある

公式アナウンスはこちら

使い方は別記事で説明するかもしれないが、@property(class) などとする。
若干クセはあるのだが、普通にクラス変数っぽく使える。あるじゃん。

また、objective-c 自体も文法が(主に 2010 年代に)モダンに改変されており、@property や @synthesize なども使い方が微妙に変わっている。

この図はこちらの記事からお借りしてきました。わかりやすいですね、感謝!

 

多重継承の禁止とプロトコル

継承が出てきたので、書いておくと Obj-C では多重継承、つまり複数のクラスを親クラスにすることができないらしい。

多重継承の代わりにプロトコルで代用しなさいよ、というのがアップルの教え。
サンプルコードなどで

@interface ViewController : NSViewController<NSTableViewDataSource>

という書き方をよく見かけるが、<> 内がプロトコル。


このまま続けてもいいのだが、まとまり悪くなるのでここでいったん切ろう。

ワイもそうだが、IT 業界にいる限り Objective-C だけいじっていればいいというものではない。
というか実際に多い案件は、別の言語だったりする。

だから、この記事は「ちょっと objective-C 思い出すときに読み返す」ときに役にたつ内容にしておく。

その他

@property 表記

アクセサー(ゲッターやセッターのこと)を定義できる。慣れれば便利なのだろうが、Java あたり読み書きしているとすぐにこの規約を忘れる。

この記事読んでね。

クラスエクステンションの () 表記

忘れやすいのがクラスエクステンションの時に使われる () 。

詳しくはこの記事あたり参照。

なんでヘッダーで定義された宣言を .m ファイルでもう一回やる必要があるのか?疑問に思ったら読むといいと思う。

Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 名前
@property (nonatomic) NSString *name;
// 名前と年齢をログに出力する
- (void)displayProfile;
@end
Person.m
#import <Foundation/Foundation.h>
@interface Person ()
// 年齢
@property (nonatomic) NSInteger age;
// 名前をログに出力する
- (void)displayName;
// 年齢をログに出力する
- (void)displayAge;
@end
@implementation Person
……
@end

要するに Obj-C には Public だの Private がないため、ヘッダーファイルではなく実体ファイルの方でメソッド・メンバー変数を定義することで外部クラスからのアクセス制限を行っているという理屈です。

Person.h
@interface Person: NSObject
@end

Person.m
@interface Person()
@end

@implementation Person
@end

という構造が見えてますか?

強調して書くとこういう仕組み。

強調して、と書いたがプロジェクトを作成するとき AppDelegate は () をつけて生成してくれますね。

 

 

 

 

 

 

カテゴリの () 表記

ここが紛らわしいと思うのだが、@interface () はクラスエクステンションだけでなくカテゴリでも使われる

ただ、こちらはファイル名自体が ClassA+ClassB.h のようになっているので、それで判断できるかな。

カテゴリーとクラスエクステンション』参照。

 

 

J2ObjC は使えるか?

Java プログラムを Objective-C プログラムに変換してくれる天下のグーグル謹製プロジェクト J2ObjC 。

興味を持ったので試してみた。

何はともあれビルド。

ビルド

公式のページに従う。

注意点としては・・・。

PROTOBUF_ROOT_DIR=/usr/local

を .zprofile あたりに追記。この時に

PROTOBUF_ROOT_DIR='/usr/local'

とやってしまうと文字列と判定されてしまうので、ビルド途上でおこられます。

並列ビルドも失敗しやすいようなので、まず

make -j4 dist

と最低限のコンパイルを片付けてしまい、必要に応じて

make frameworks
make all_frameworks

とするといいと思う。

Hello World

ビルドはとんでもなく時間がかかるので、すぐにできる j2objc, j2objcc を使ってハロワしてみよう。

公式ページにある Hello.java

public class Hello {
  public static void main(String[] args) {
    System.out.println("hello, world");
  }
}

j2objc Hello.java

すると同名の .m ファイル(Hello.m)ができる。


#define J2OBJC_IMPORTED_BY_JAVA_IMPLEMENTATION 1




#include "Hello.h"
#include "IOSObjectArray.h"
#include "J2ObjC_source.h"
#include "java/io/PrintStream.h"
#include "java/lang/System.h"



#if __has_feature(objc_arc)
#error "Hello must not be compiled with ARC (-fobjc-arc)"
#endif

#pragma clang diagnostic error "-Wreturn-type"
#pragma clang diagnostic ignored "-Wswitch"


@implementation Hello

J2OBJC_IGNORE_DESIGNATED_BEGIN
- (instancetype)init {
  Hello_init(self);
  return self;
}
J2OBJC_IGNORE_DESIGNATED_END

+ (void)mainWithNSStringArray:(IOSObjectArray *)args {
  Hello_mainWithNSStringArray_(args);
}

+ (const J2ObjcClassInfo *)__metadata {
  static J2ObjcMethodInfo methods[] = {
    { NULL, NULL, 0x1, -1, -1, -1, -1, -1, -1 },
    { NULL, "V", 0x9, 0, 1, -1, -1, -1, -1 },
  };
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wobjc-multiple-method-names"
  #pragma clang diagnostic ignored "-Wundeclared-selector"
  methods[0].selector = @selector(init);
  methods[1].selector = @selector(mainWithNSStringArray:);
  #pragma clang diagnostic pop
  static const void *ptrTable[] = { "main", "[LNSString;" };
  static const J2ObjcClassInfo _Hello = { "Hello", NULL, ptrTable, methods, NULL, 7, 0x1, 2, 0, -1, -1, -1, -1, -1 };
  return &_Hello;
}

@end

void Hello_init(Hello *self) {
  NSObject_init(self);
}

Hello *new_Hello_init() {
  J2OBJC_NEW_IMPL(Hello, init)
}

Hello *create_Hello_init() {
  J2OBJC_CREATE_IMPL(Hello, init)
}

void Hello_mainWithNSStringArray_(IOSObjectArray *args) {
  Hello_initialize();
  [((JavaIoPrintStream *) nil_chk(JreLoadStatic(JavaLangSystem, out))) printlnWithNSString:@"hello, world"];
}

J2OBJC_CLASS_TYPE_LITERAL_SOURCE(Hello)

おお、Objective-C じゃん!

次に

j2objcc -c Hello.m
j2objcc -o hello Hello.o

として実行バイナリ hello をつくる。

% ./hello Hello
hello, world

おおお。

Java リバーシアプリを iPhone アプリに

そうこうするうちに dist フォルダに生成物ができていたので、これを利用して Xcode プロジェクトのサンプルを試してみた。
公式が Java reversi プログラムを Xcode 上でビルドするサンプルを紹介していたので、これにトライする。

ほぼそのままで動きました。

JRE.framework を JRE.xcframework にするくらいの変更でOK牧場。

すげ。

 

 

(続く)

 

曖昧な日本のユーザー

私の所属する会社(休眠がちだが)は、集まったメンバー(学生バイト含む)の仲がよく、とてもよくまとまっていると思う。
この点に関してはなんの不満もないのだが、リリースしたソフトのユーザーについては、なんの不満もないかと言われるとちょっと微妙。

お行儀の良いユーザーたち

ユーザーに関しては、一般的な水準で見れば良質だとは思う。なにしろクレーマーのようなユーザーは一人もいなかった。みんな、お行儀よい。おとなしいと言っていいかもしれない。
無い物ねだりかもしれないが、おとなしすぎて物足りない感じは正直ある。

例えば、OpenDolphin-2.7m 系列(ただし、これは代表のまったくの個人制作)に関しては、最近のユーザーの関心はもっぱらデータ移行だろう。
具体的にどことは言わないが基本設計自体がもはや古臭く、新規の導入は勧めていないし、私たちの興味も「標準型電子カルテ」を意識した新規のシステムに向かっていて、実際、2.7m シリーズはメンテ程度しかやっていない。
だから、データ移行の必要が生じた際には「◯ヶ月程度を目処に、XXという電子カルテ向けにデータコンバートしてほしい」と明快に意思表示してもらわないと動きようがないのだ。準備にもそれなりの時間がかかるのだから。
どうも見ていると、OpenDolphin HTML/PDF Viewer あたりをばら撒いてくれることを期待しているようなのだ。
あちこちで「できない」とはっきりと言っていると思うんだけど?

Q4 データ移行ツールのバイナリ・ソースコードなどは配布・公開しないのか?

A4 これ、過去に商用開発元の方から似たような要望を受けたことがあります。 一瞬、私もそうしようかと思いました。が、

OpenDolphin のカルテを途中経過版も含めて一括書き出し

→途中経過版の一部を削除など加工

→加工したデータを再度、OpenDolphin などの電子カルテデータベースに戻す

という使い方をしてしまうと立派な「改竄」ツールになってしまうので、思い直してやめました。”

OpenDolphin-2.7m(系列)FAQ』より

これはおそらく
・以前に実行可能なファイルバックアップシステム付き OpenDolphin-2.7m クライアント
(いわゆる OpenOcean クライアント)を無料で配布した
・オープンソースプロダクツに「無料」というニュアンスを強く感じている
から、「どうせ、そのうち無料で配布してくれるんだろう」と安心しちゃっているからなのかなあと思う。

大事なことなので2回言いますが、ないですよ。

あとメンズ陣がたまに言うには「全然、勉強してないし、その痕跡も見せない。作業現場に来て暗い感じで、突っ立っているようなものなので、何も言葉がかけられない」んだそうだ。
ここら辺は、彼らはシビアだ。

与えられることに慣れすぎていると・・・

私たちのグループに専業従事者は一人もいない。

だから、ユーザーが反応してくれない箇所などは最悪放置すらあり得る。

日本のユーザーは、成熟したメーカーのそれなりによくできた製品(尖った部分はないが全体として80点みたいな製品)を使うことに慣れている。サポートも手厚い。
私の個人的な印象かもしれないが、アメリカはここら辺はかなり違う。
名もなきベンチャーのピーキーな製品がしれっと販売され、一般市民もそれにチャレンジする。いかにダメな部分があろうが、魅力的な機能があれば、その製品のファンになってくれる。

このような消費行動の差が、日本のユーザーの一種の甘えにつながっているのかもしれない。

人生の多くの場面で当てはまることかもしれないが、「与えられることに慣れすぎている」と何かを失うのかもしれない。

 

AXIA

反オープンソース評論家

常々、「IT 評論家」のような人が嫌いだった。

特にオープンソース評論家のような人は、その主張が激しく界隈ではある種の「武闘派」とみなされていると思う。

その点で、いわゆる OpenOcean 騒動はチェックしていた。
OpenOcean 騒動というのは、air-h-128k-il さんたちが OpenDolphin という GPL でライセンスされた電子カルテの独自カスタマイズバージョン OpenOcean をソース・バイナリともに公開・配布した際に、小林慎治(医師、現岐阜大医学部特任講師)というオープンソース評論家のような人が、「OpenOcean は GPL に違反している!」と難癖つけた事件のことだ。

この難癖というのがなんと言っていいやら・・・。

まあ、トンデモの類。

最近になって、当時の開発陣からかなりまとまった反論が提出されている。
(例えば『小林慎治氏の OpenOcean に関する事実誤認』、『OpenOcean 騒動』、『OpenOcean 騒動 #27』)

ここで注目したいのは、オープンソースの責任性について。

彼は怪文書の中で言っている。

OpenOceanのWebには2018年12月までの試用期間であると記載とされています。ユーザーはこの指示に従うべきものとも考えられますが、このままだと使用期限が近づいてもクライアントに何の警告も表示されずに2019年1月以降起動しなくなってしまいます。GUIなインタラクティブメニューを持つソフトウェアであれば、1ヶ月以上前から警告を表示しておくのが親切ではないでしょうか。
Webに記載される試用期間を見落とすほうが悪いのかもしれませんが、使用期限を過ぎて起動しないソフトウェアに対してどのように対処すべきかも書かれていませんので、ユーザーである医師には対応できずに医療事故につながる危険性があります。

ここだけ取り出すと提案の一つとしてあってもいいかなと思えなくもないが、実際に彼の取った行動は理解不能。

なんと、使用期限に関するコードを削除したものをプルリクエスト(PR)として OpenOcean リポジトリに送ってしまったのだった。「残り X 日です」とアラートウインドウを出すような実務的なコードではなくて。
その PR は今もリポジトリに記録されている。こういうものだ。

expired() というメソッドで使用期限を設定しているわけだが、この機能に関するコードを削除しただけ。

すごいでしょ。

issue あたりで改善要望出しておけばいいだけだし、そんなに使用期限がいやなら使用期限のないバージョンを自分でビルドしてユーザーに提供すればいいだけ。後者は、ほぼパクリとも言える行為だが、air さんたちだったら許していたと思う。

その一方で怪文書は、

第三者にその製品を提供することは著作権法が定める頒布権の侵害であり、公衆の場であるGitHubにソースコードを置くことは著作権法が定める公衆送信権侵害です。

とソースコードの公開停止を主張している。
PR 送ったのが 2018/11/4 で、怪文書が公開されたのが 2018/11/26 のことだ。
俗っぽい言い方をするならば「PR がマージされなかったので、逆ギレして公開停止を主張した」ということになる。

ところで、そもそもオープンソースは、ビルド産物に関しては責任は限定的だし無保証だ。
この点に関してある人が面白いことを言っていた。


OpenOceanの試用期限表示とオープンソースの責任について

1. OpenOceanの試用期限表示について

OpenOcean配布サイトには「使用期限は、2018/12/31 までです。」と明記されているので、「警告なく起動しなくなる仕様」という批判は不当であると言えます。情報が公開されていた以上、利用者はその条件を理解した上で使用を開始する責任があります。

2. オープンソースにおける開発者の責任について

多くのオープンソースソフトウェア(OSS)のライセンスには、開発者による保証の放棄(無保証)と責任の限定が明確に記載されています。

無保証の原則: OSSは一般的に「あるがまま(as-is)」で提供され、品質保証や機能保証は行われません。利用者は自己責任においてソフトウェアの品質や安全性を確認し、利用する必要があります。

責任の限定: ほとんどのOSSライセンスは、開発者がソフトウェアの利用によって生じた損害に対して責任を負わない旨を規定しています。これは、オープンソース活動がボランティアベースで行われることが多く、開発者に過度な負担をかけないための措置でもあります。

新機能の搭載: 開発者が責任に限定的である立場にあるからこそ、新機能や実験的な仕様を導入しやすいという側面があります。


実際、OpenOcean には、ファイルバックアップ機能などそれまでのドルフィンになかった新機能が搭載されていた。

どれが正しいのだかもわかりにくい著作権表記だのでワーワー騒いで、OpenOcean 開発陣からユーザーによる新機能の評価を得る機会を奪ってしまった。

「小林慎治はオープンソースの足を引っ張っているだけ」という批判がなされている一つの根拠になっている。

おそらくこの人のスキルからしてオープンソースといった開発現場に近い位置での論評は無理がある。
それを自覚したのか、小林氏、最近では初学者向けに「医療Dx」に関して語ることが多く、一時の「オープンソース」偏重とは異なる態度をとっている。
言い方を変えれば、実務家が彼を「オープンソース」領域から追っ払ったとも言えるわけで、反「オープンソース評論家」のささやかな勝利といえるだろう。

 

(追記)本稿では、責任性について触れたが、私がのけぞったのは、そもそも怪文書の根拠となっていた著作権表記が改竄されたものであったという事実。
そして怪文書の「是正勧告」とやらに従った場合、まるでミステリのような犯罪が成立していたという指摘。
OpenOcean 怪文書 -GPL 誤用による違法行為教唆-』に詳しく書かれていますので、興味のある方はそちらをご覧ください。
OSS は無保証・限定責任ですが、OSS に関して語ったことは語った人の責任です。
どう責任取るつもりなんでしょうか?
でも、この人のことだから、言いっ放しで終わりそう。

(追記2)X の TL みてたら、佐渡秀治という人が引っかかってきたようだ。

「流れは知らんけど」って。追補されているのだから、流れは重要だろう。
これ、反撃されるとみた。
→案の定、『OpenOcean 怪文書 – 適切な GPL 使用のために-』で追記されてた。
ところで、ネットで検索してたら、この人のことを評した情報はほとんどない。
あの言動では当然そうなるよなあというのが感想。

 

造船大

限定公開記事だが、以前にFラン大のことを書いた。

で、X で監視している垢があると言っていたが、無論、今でも続けている。

そのうちの一人を「造船大」と呼んでいる。

2025 年になっても、フォロワー約100、インプレションも二桁と絶好調である。