MOSA Multi-OS Software Artists

MOSAはソフトウェア開発者を支援します

  • iPhone/iPod touch アプリ紹介
  • MOSA掲示板
  • 活動履歴
  • About MOSA(English)

MOSA Developer News[MOSADeN=モサ伝]第109号

2004-04-13

目次

  • Cocoaでいこう! Macらしく  第46回  Yoshiki(DreamField)
  • 藤本裕之のプログラミング夜話 #43
  • 高橋真人の「プログラミング指南」 第42回
  • ニュース・解説
  • MOSAからのお知らせ

Cocoaでいこう! Macらしく  第46回 Yoshiki(DreamField)

 前回までで、パネルを開いた時に、メインウィンドウのイメージの横幅と縦幅を表示するようになりました。今回は、パネルを開いた後にメインウィンドウが切り替わった時の処理を実装します。

パネルを表示しよう(その8)

 メインウィンドウが切り替わった時は、パネルに表示している内容も、そのメインウィンドウのイメージの横幅と縦幅に切り替えなければなりません。では、これをどうやって実現すれば良いでしょうか。
 今まで組んだプログラムを思い出してみましょう。MyDocument.mを見てください。メインウィンドウが切り替わった時には、windowDidBecomeMain:が呼び出されるのでした。この中では全員が共通で使うimageInfoクラスのインスタンスに情報をセットしています。このセットした情報をパネルに反映させるには、どうすれば良かったでしょうか。InfopanelController.mを見て下さい。この中の、updateImageInfoが呼び出されれば、パネルに反映されるのでした。つまり、MyDocument.mのwindowDidBecomeMain:の中で情報をセットした後に、updateImageInfoメッセージをInfopanelControllerクラスのインスタンスに投げるようにすれば良いわけです。これは可能でしょうか。
 メッセージを投げるためには、そのインスタンスのアドレスが分かっていなければなりません。InfoPanelControllerクラスのインスタンスを生成するのは、AppController.mの中の、showInfoPanel:でした。これを見ると、インスタンス変数であるinfoPanelControllerにそのアドレスを格納しています。つまり、AppControllerクラスのインスタンスに教えてもらえば、アドレスを取得することはできそうです。でもちょっと待ってください。パネルは最初から表示されているわけでは無いのです。表示するまでインスタンスは生成されませんから、メッセージは送れません。では、これも判断するようにして・・・。何だかとても複雑になってしまいそうですね。この様な処理を単純にできる、とても便利な方法があります。それは、NSNotificationCenterを使うという方法です。

 NSNotificationCenterクラスのインスタンスは、通知の仲介をしてくれます。何か通知を行いたい人は、受け取る人が誰であるか気にする必要はありませんし、受け取る人が存在しているのかどうかさえ考える必要もありません。ただ、NSNotificationCenterに通知を投げるだけです。一方、通知を受け取りたい人は、通知を投げる人のことを知る必要はありません。受け取りたい旨をNSNotificationCenterに登録しておくだけで、通知が来た時に受け取ることができます。この時、特定のメッセージだけ受けたいという指定もできますし、特定の人から来たメッセージだけ受けたいという指定も可能です。
 ところで、CodeWarriorでPowerPlantを使っていた方は、LBroadcasterとLListenerを思い浮かべるかもしれません。こちらは、メッセージを投げる人に投げられる人を登録し、メッセージを投げる方は誰に投げているのかを意識をしないですむという仕組みです。両者を比べると、NSNotificationCenterの方がずっと結合が粗であることが分かります(LBroadcasterとLListenerは、元々はボタンやメニュー等、コントロールの操作をメッセージのやり取りで実現するためのものですから、そもそも目的が違います。まともにオブジェクト指向で組めないC++で、まともにオブジェクト指向で組みたいがために考え出された仕組みなんですね)。

 それでは、NSNotificationCenterの詳しい説明はreferenceや入門書を読んでもらうこととして、ここでは実際にプログラムを組んで行きましょう。ImageInfo.mに、次のメソッドを実装してください。このメソッドは、ImageInfoの内容に変化があったことを通知します。

- (void)change{
     [ [ NSNotificationCenter defaultCenter] postNotificationName:
TNVWImageInfoChangedNotification object:self];
}

 NSNotificationCenterクラスのインスタンスは、生成することもできますが、あらかじめタスクに一つ用意されています。ですから、通常はそれを使います。NSNotificationCenterクラスにdefaultCenterメッセージを投げると、その用意されているインスタンスを返してくれます。そして、それに対してpostNotificationName:object:メッセージを投げることにより、通知を行っています。第一引数は通知の名前を示すNSStringクラスのオブジェクト、第二引数は、実は何でも良いのですが、通常は通知を行うオブジェクト(つまり自分自身)を指定します。通知の名前は直接文字列を書いていません。インプリメント部よりも上に次の一行を入れてください。

NSString *TNVWImageInfoChangedNotification = @"TNVWImageInfoChangedNotification";

 そしてImageInfo.hのインターフェース部よりも上に、extern宣言を入れて下さい。

extern NSString *TNVWImageInfoChangedNotification;

 この様に宣言して、通知を受け取る方でもTNVWImageInfoChangedNotificationを使うようすれば、打ち間違えてもビルド時にエラーになるので、すぐに分かります。直接文字列を指定すると、これが分からず苦労することになります。あとは、メソッドの宣言にchangeも加えておいてください。続きは、次回です。

藤本裕之のプログラミング夜話 #43

 このところちと忙しくてネタを仕込んでいるヒマがないので素材ムキ出し気味の話で失礼したい。こないだ、Objective-Cでプログラミング中、なんとも不可解なバグに遭遇してCocoa-dev MLに質問し、Fritz Anderson氏のアドバイスで解決、目からウロコが3枚くらい落ちた。その顛末。

ことはあるダイアログボックスを巡るコードで起きた。InterfaceBuilder(以下IBと省略)でテキストの入力域(NSTextField)とボタン(NSButton)が1個ずつあるダイアログを作り、NSObjectをサブクラスしてこれのコントローラを作った(NSControllerを使わないのはJAGUARでも動作させたいから)。コントローラに3つのOutlet(Panel, TextToDo, DoItButton)を定義、インスタンシエイトし、それぞれダイアログそのもの、入力域、ボタンと連結する。ソースファイルをジェネレイトして、controlTextDidChange:をオーバーライド、TextToDoの中身が空っぽだったらDoItButtonを選択不能にするコードを書いた……。
 ここまでは何の問題もなかった。ちゃんと意図した通りに動いてたのね。ところがここに「メイン・ウィンドウへ文字列をドラッグ&ドロップされたら、このダイアログを出し、その文字列をTextToDoにセットしたい」という要求が出て来たのね。で、メイン・ウィンドウの方にそういうコードを書き、ダイアログのコントローラに外から文字列をセットするためのメソッドを追加した。「setTextToDo:」という名前の以下のようなコード……。

-(void) setTextToDo:(NSString*) inString {

  [TextToDo setStringValue:inString];
  [self controlTextDidChange:nil];

}

 これが苦闘の始まり。何が起きたかって? まず、最初に気づいたのはもちろん、このセットが正しく行われないこと。文字列をドラッグ&ドロップするとちゃんとダイアログは出るんだが、そこで終わり、何も起こらない。次に気がついたのは、開いたダイアログのTextToDoにキーボードから文字を入力しても「DoItButton」が選択可能にならないこと……。ヘンでしょ? 
 で、いろいろ調べるウチ、どうもTextToDoというOutletがヘンであることが判明した。このコントローラのawakeFromNib:にデバッグ文を入れてこのObjectのアドレスを確認すると、なんとこいつがゼロだったのだ。
awakeFromNib: といえばオレがオーバーライドしていない initWithCoder:(Nibファイルの中身からObjectを生成する時にCallされる)の次に送られるメッセージである。ここでこいつのアドレスがゼロなら、そりゃこのObjectに対するメッセージは全部機能しないに決まってる。
 試しにもう一つNSTextFieldをダイアログに追加し、そいつのアドレスもプリントするとそっちはマトモ。では、と今までの領域を消し、そいつを改めてコントローラのOutletに繋ぐ……と、またゼロになってしまうではないか。どーなっとるのだ?

 ここまで調べ、途方に暮れてMLに質問した。Fritzの出してくれた回答はスーパーなもんだった。「おまえ、そのOutletの名前Xを使って『setX:』ってメソッドを定義してないか? そいつが initWithCoder: から呼ばれてOutletをゼロにしちゃってんだよ」と。半信半疑でsetTextToDo: にNSLog()を入れてみると果たしてその通り。awakeFromNib: より前にこいつが呼ばれてた。慌ててawakeFromNib:のドキュメントを読むとなるほどこんな意味のことが書いてある。「initWithCoderはIBで定義できる以上の特別な設定を可能にするため、Outletに連結されたインスタンスをロードする前にその名前にsetがついた「setVariable:」を探し、もしあればこれをCALLする。なければインスタンスを探す……」
 いや、以前一度これを読んだんだが、そんときは具体的なイメージが湧かなかったので読み流しちゃたんだよね。そーだったのか。ぢゃ、オレがいままでこのケースに捕まらなかったのは、(意識してなかったんだから当然ながら)偶然Outlet名そのままにsetをつけたメソッドを定義しなかったからつうことになるぢゃないか。がぁん。目から湧き出るウロコをぬぐいつつ、ちと背筋が寒くなった春4月でありました。
(2004_04_08)

高橋真人の「プログラミング指南」第42回

オブジェクト指向のとっかかり(28)

 こんにちは、高橋真人です。
 さて、前回で一通りの説明を終えたプログラムの例ですが、それに手を加えて少しだけ実用的なものに改造していきます。なぜ「少しだけ」なのかというと、一度に大幅に変更してしまうと理解しづらくなってしまう(特に今までのものとの関連性が見えなくなってしまう)からです。
 オブジェクト指向で書かれた実用的なプログラムソースコードのサンプルであれば、今の時代インターネットでいくらでも入手可能ですから、ここはあえて「プログラミング指南」の原点に戻って、ゆっくりと時に冗長なぐらいに説明を展開していくという方向で行きましょう。
 改良したプログラムは以下のようになります。

// main.cpp
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "TextContent.h"
#include "MacText.h"
#include "DosText.h"
#include "UnixText.h"

int main()
{
     std::cerr << "*** Line terminate character convert program *** n"
          << "Enter source file name: " << std::flush;

     std::string inputFile;
     std::cin >> inputFile;

     std::ifstream ifs(inputFile.c_str(), std::ios::binary);
     if (not ifs) {
          std::cerr << "file was not found.";
          std::abort();
     }

     std::cerr << "Reading....." << std::endl;

     std::string termChar;
     TextContent *tc = TextContent::InstanceFactory(ifs);

     if (tc == NULL) {
          std::cerr << "No terminate characters found." << std::flush;
          std::abort();
     }

     tc->ReadFrom(ifs);

     std::cerr << "What character do you convert to? n"
          << "1: MAC  2: DOC  3: UNIX n" << "Select number: ";

     int number;
     std::cin >> number;

     TextContent *tmp;
     switch (number) {
          case 1:
               tmp = new MacText(tc);
               break;
          case 2:
               tmp = new DosText(tc);
               break;
          case 3:
               tmp = new UnixText(tc);
               break;
          default:
               std::cerr << "Bad number." << std::endl;
               std::abort();
     }

     delete tc;
     tc = tmp;

     std::cerr << "Enter destination file name: " << std::flush;
     std::string outputFile;
     std::cin >> outputFile;
     std::ofstream ofs(outputFile.c_str(), std::ios::binary);
     if (not ofs) {
          std::cerr << "Cannot make destination file." << std::endl;
          std::abort();
     }

     tc->WriteTo(ofs);
     std::cerr << "Converting completed." << std::endl;

     return 0;
}

// TextContent.h
#pragma once

#include <fstream>
#include <string>
#include <vector>

class TextContent
{
public:
                              TextContent(const char *inTermChar);

     static TextContent  *InstanceFactory(std::ifstream &ifs);
     virtual void        ReadFrom(std::ifstream &ifs);
     virtual void        WriteTo(std::ofstream &ofs);

     std::vector<std::string> content;
     std::string                        termChar;
};

// TextContent.cpp
#include "TextContent.h"
#include "MacText.h"
#include "DosText.h"
#include "UnixText.h"

TextContent::TextContent(const char *inTermChar) : termChar(inTermChar)
{
}

TextContent *TextContent::InstanceFactory(std::ifstream &ifs)
{
     TextContent *returnInstance = NULL;

     char c;
     while (ifs.get(c)) {
          if (c == 0x0d) {    // CR
               if (ifs.get(c)) {
                    if (c == 0x0a) {    // CR + LF
                         returnInstance = new DosText;
                         break;
                    }
               }
               returnInstance = new MacText;;
               break;
          }
          else if (c == 0x0a) {    // LF
               returnInstance = new UnixText;
               break;
          }
     }

     return returnInstance;
}

void TextContent::ReadFrom(std::ifstream &ifs)
{
     ifs.seekg(0, std::ios::end);
     std::size_t size = ifs.tellg();
     ifs.seekg(0);

     std::string buffer(size, ' 0');
     ifs.read(&buffer[0], size);

     std::string::size_type p1 = 0;
     std::string::size_type p2 = buffer.find(termChar);
     while (p2 != std::string::npos) {
          content.push_back(std::string(buffer.substr(p1, p2 - p1)));
          p1 = p2 + termChar.size();

          p2 = buffer.find(termChar, p1);
     }
}

void TextContent::WriteTo(std::ofstream &ofs)
{
     for (std::size_t i = 0; i < content.size(); ++i) {
          ofs << content[i].c_str() << termChar;
     }
}

// MacText.h
#pragma once

#include "TextContent.h"

class MacText : public TextContent
{
public:
               MacText();
               MacText(const TextContent *inTC);
};

// MacText.cpp
#include "MacText.h"

MacText::MacText() : TextContent(" x0d")
{
}

MacText::MacText(const TextContent *inTC) : TextContent(" x0d")
{
     content = inTC->content;
}

// UnixText.h
#pragma once

#include "TextContent.h"

class UnixText : public TextContent
{
public:
               UnixText();
               UnixText(const TextContent *inTC);
};

// UnixText.cpp
#include "UnixText.h"

UnixText::UnixText() : TextContent(" x0a")
{
}

UnixText::UnixText(const TextContent *inTC) : TextContent(" x0a")
{
     content = inTC->content;
}

// DosText.h
#pragma once

#include "TextContent.h"
class DosText : public TextContent
{
public:
               DosText();
               DosText(const TextContent *inTC);
};

// DosText.cpp
#include "DosText.h"
DosText::DosText() : TextContent(" x0d x0a")
{
}

DosText::DosText(const TextContent *inTC) : TextContent(" x0d x0a")
{
     content = inTC->content;
}

 例によって、解説は次回からです。

ニュース・解説

 今週の解説担当:小池邦人

Carbon ドキュメント & サンプル & SDK ナビゲーション(2004/04/09)

【開発環境】

Apple社からはWWDC2004に関する色々な情報が提供されています。まずはカンファレンスセッションに関する詳細ですが、その日本語訳が以下のサイトに登録されました。

http://developer.apple.com/ja/wwdc/descriptions/

私としては、ちゃんとCarbon関連のセッションもあり一安心しています(笑)。トラックは「アプリケーションテクノロジー」「開発ツール」「エンタープライズIT」「グラフィックス&メディア」「ハードウェア技術」「OSの基礎」「QuickTime デジタルメディア」の7つに分かれています。セッションのタイトルと内容を見ていただくと理解できると思いますが、バリバリのプログラマでない方にもアピールできるようなセッションが数多く用意されています。最近のWWDCは、プログラマの祭典から、多種多様のユーザやデベロッパを対象にしたカンファレンスへと変貌したことがよく理解できます。

それから、今年は(昨年もあった?)WWDCが開催される前日の6月27日に、WWDC 2004 Pre-Conference Workshopsと名付けられた事前ワークショップが開催されます。ワークショップには、「Cocoa Programming」「AppleScript Hands-On」「Carbon Programming」「The Unix Connection with Mac OS X」「Effective Debugging and Optimization of Mac OS X Code」という5つのコースが用意されています。Cocoa Programmingなどは一日がかりで行われるようですので、これからMac OS Xのプログラミング環境にチャレンジしようとされている方は、前日からこのワークショップに参加されるのが近道かもしれません。詳細については以下のサイトをご覧ください。

http://developer.apple.com/wwdc/preconference.html

また、WWDCと同時に開催される各種イベントについても詳細が載っています。
Mac OS Xのソフトウェアコンテストである「Apple Design Awards 2004」や、会場で併設される「WWDC 2004 Exhibit Fair」(サードパーティの展示会)についての詳細は、それぞれ以下のサイトを参照してください。

http://developer.apple.com/wwdc/ada.html

http://developer.apple.com/wwdc/exhibits.html

今年も映画上映とApple本社でのパーティは開催されるようです(笑)。現時点では、日本からのツアーに関して公式サイトでの紹介はありませんが、こちらも近々発表されると思われます。WWDC2004の参加費用の早期割引は4月30日までですので、参加を予定されている方は忘れないように登録いたしましょう。WWDC2004に関するQ&Aの日本語訳は、以下のサイトにまとめられています。

http://developer.apple.com/ja/wwdc/faq/

【テクニカルドキュメント】

前回から4月9日の期間中、Apple社のDeveloperサイトにはドキュメントが7つ登録されました。「Porting Drivers to Mac OS X」「Core Audio」「Providing Navigation Dialogs」のそれぞれの内容については、先週号の新居さんの記事を参照してください。

「Porting Drivers to Mac OS X」(PDFあり)
「Apple Human Interface Guidelines」(PDFあり)
「Core Audio」(PDFあり)
「Providing Navigation Dialogs」(PDFあり)
「WebObjects 5.2.3 API Differences」
「WebObjects 5.2.3 API Reference」
「Mac OS X Assembler Guide」

http://developer.apple.com/documentation/index-rev-date.html

また、デベロッパ向けの読み物としてXgridについての解説が登録されていま
す。

「High Performance Computing for the Rest of Us」(読み物)

http://developer.apple.com/hardware/hpc/xgrid_intro.html

前回から4月9日の期間中、新規テクニカルノートはひとつも登録されませんでしたが、テクニカルQ&Aの方は6つ登録されました。QuickTimeの「Sequence Grabber」(映像やサウンドの取り込みに利用するコンポーネント)に関する内容が2つ含まれています。

QA1349 「Sequence Grabber – How often should I call SGIdle?」
QA1348 「Sequence Grabber – Using the SGDataProc for Sound」
QA1347 「Movie export with AAC or AMR audio formats」
QA1345 「QuickTime movies require a valid graphics port」
QA1312 「Rendezvous service types used in Mac OS X」
QA1299 「NSL and how it relates to Rendezvous」

http://developer.apple.com/technicalqas/index-rev-date.html

【サンプルソースコード】

前回から4月9日の期間中、Apple社のSample Codeサイトにはサンプルソースコードが8つ登録されました。久しぶりにサンプルソースコードの復活です(笑)。このうちの7つがOpenGLに関連したサンプルです。サンプル内容は再録に近いのですが、すべて新しく書き換えられています。「Core Graphics」「AGL」「GLUT」という3つのライブラリのどれかを使い、CarbonやCocoaのウィンドウやユーザインターフェースと連動させる方法が解説されている良質のサンプルです。アプリケーションの目的や移植方法の違いにより、OpenGL APIとのリンクに「Core Graphics」「AGL」「GLUT」の使い分けが必要な時にはぜひ参考にしてみてください。「FSCopyObject」の方は再録で、いくつかのバグフィックスがなされているようです。

「GLCarbon1ContextPbuffer」(OpenGL)
「GLCarbonAGLFullScreen」(OpenGL)
「GLCarbonCGLFullScreen」(OpenGL)
「GLCarbonSharedPbuffer」(OpenGL)
「GLUTBasics」(OpenGL)
「GLUTSurfaceTexture」(OpenGL)
「GLCarbonAGLWindow」(OpenGL)
「FSCopyObject」(Core Foundation)

http://developer.apple.com/samplecode/

【デベロップメント SDK】

前回から4月9日の期間中、Apple社のSDKサイトにはひとつもSDKが登録されませんでした。

http://developer.apple.com/sdk/

MOSAからのお知らせと編集後記は割愛します

MOSA Developer News   略称[MOSADeN=モサ伝]
Apple、Mac OSは米国アップルコンピュータ社の登録商標です。またそのほかの各製品名等はそれぞれ各社の商標ならびに登録商標です。
このメールの再配信、および掲載された記事の無断転載を禁じます。
http://www.mosa.gr.jp/
Copyright (C)2004-2006 MOSA. All rights reserved.