🎯 最終演習

Objective-Cで連絡先を整理してみよう(総合演習)

ここまでの Objective-C レッスンで学んだことを全部つないで、動くものを1つ完成させます。新しい構文は出てきません。クラス・プロパティ・NSArray・NSDictionary・for-in・ブロック——すでに練習したものだけで組み立てます。

作るのは連絡先の整理です。ごぶさたしている相手を見つけ、分類ごとに数えて、一覧にまとめます。

Objective-Cで最初に慣れるべきは、角括弧の書き方です。[object method]——これは関数を呼んでいるのではなく、「オブジェクトにメッセージを送っている」という考え方です。この演習では、それが自然に手に馴染むところまで進みます。

このページではコードを実行していません。Objective-Cはコンパイルが必要で、ブラウザの中では走らせられないためです。かわりにお手本と同じコードを書けたかを、その場で判定します(判定はあなたのブラウザの中だけで行われ、どこにも送信されません)。

表示している実行結果は本物です。このページを作るときに、実際にclangでコンパイルして走らせた出力をそのまま載せています。

完成イメージ

最後まで進むと、このコードが完成します。下の実行結果は、このページを作るときに開発機で実際にコードを走らせて得たものです(このページ上では実行していません)。

===== 連絡先 =====
中村        友人   070-9999-0000 120日前  ごぶさた
佐野        友人   080-3333-4444  47日前  ごぶさた
兄          家族   090-5555-6666  21日前
取引先窓口  仕事   03-7777-8888   9日前
母          家族   090-1234-5678   3日前
山田課長    仕事   03-1111-2222   1日前
----------------------------------------------
  家族: 2件
  仕事: 2件
  友人: 2件
登録件数: 6件
30日以上連絡していない相手: 2件
いちばんごぶさた: 中村(120日前)

STEP 1連絡先をクラスで表す

🎯 このステップのゴール: @interfaceで宣言し、@implementationで実装を書く

まずデータの形を決めます。連絡先1件が持つのは「名前・電話番号・分類・最後に連絡した日」の4つです。

Objective-Cのクラスは2つに分かれています。@interface「外から何ができるか」の宣言@implementation「実際にどうするか」の実装です。

この分け方はC言語のヘッダファイルから来ています。実務では.h.mという別々のファイルに書きますが、ここでは1つにまとめてあります。

@propertyの括弧の中が大事です。

copy——NSStringには必ずこれを付けます。渡された文字列が後から書き換えられる可能性があるからです。NSMutableStringを渡されて、あとで中身を変えられたら、この連絡先の名前も勝手に変わりますcopyなら複製を持つので安全です。

assignNSIntegerのような数値に使います。オブジェクトではないので、複製も参照管理も要りません。

readonly外から書き換えられないという意味です。連絡先の記録なので、作ったあと変わっては困ります。

初期化メソッドのself = [super init];お決まりの形です。親クラスに初期化させ、その戻り値を自分に代入し直しますif (self)で確かめるところまで含めて、この4行はそのまま覚えてしまってください

中で_nameとアンダースコア付きで書いているのは、@propertyが自動で作る変数です。初期化の中だけは、この形で直接入れます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
登録件数: 6件
1件目: 母 090-1234-5678 3日前

STEP 2NSLogではなくprintfで出す

🎯 このステップのゴール: NSStringを組み立て、UTF8Stringでprintfに渡す

Objective-Cの入門でまず教わるのはNSLogです。この演習では使いません。理由があります。

NSLogは出力の先頭に日時とプロセス名を付けます。——2026-08-18 13:04:00.586 a.out[75024:25908553] 合計: 1234のような形です。

だから実行するたびに出力が変わります。このページに載せた結果と、あなたの手元の結果が一致しません。教材として困ります

かわりにprintfを使います。ただしNSStringをそのまま渡せません——printfはC言語の関数で、NSStringを知らないからです。

[line UTF8String]C言語の文字列に変換してから渡します。

組み立てには[NSString stringWithFormat:@"..."]を使います。%@がオブジェクト用の書式指定で、NSStringもそのまま埋め込めます。

整数の書式にひとつ落とし穴があります。NSInteger%dで出すと、環境によってはおかしな値になります。64ビットではNSIntegerlongだからです。%ldを使い、(long)でキャストするのがお決まりの形です。

この(long)は省かないでください。32ビット環境ではNSIntegerintになるので、キャストしないと環境によって壊れます

@autoreleasepool一時的に作られたオブジェクトを片付ける仕組みです。mainを囲むのがお決まりです。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
母 (090-1234-5678) 3日前
山田課長 (03-1111-2222) 1日前
佐野 (080-3333-4444) 47日前
兄 (090-5555-6666) 21日前
取引先窓口 (03-7777-8888) 9日前
中村 (070-9999-0000) 120日前

STEP 3NS_ENUMとswitchで分類を扱う

🎯 このステップのゴール: 型の付いた列挙を作り、switchで名前に変える

分類をNSStringで持つと、@"家族"@"かぞく"が混ざります。集計が合わなくなります。

typedef NS_ENUM(NSInteger, ContactGroup)——これがObjective-Cの列挙の書き方です。

ただのenumと何が違うのか。NS_ENUM「値の型」も一緒に決めます。ここではNSInteger型が決まっていると、コンパイラが「この変数にはこの列挙の値しか入らない」と分かります

効き目はswitchで出ます。caseを1つ書き忘れると、「この値が扱われていない」と警告が出ます。ただのenumでは出ません。

名前の付け方にも作法があります。ContactGroupFamily——列挙の名前を頭に付けます

Objective-Cには名前空間がありません。だからFamilyという短い名前を作ると、他のライブラリのFamilyとぶつかりますNSStringUIViewNSUIも、同じ理由で付いている接頭辞です。

switchの各caseですぐreturnしているので、breakを書き忘れて次に流れ込む事故は起きません

末尾のreturn @"その他";保険です。NS_ENUMのおかげで通常は届きませんが、数値をキャストして無理やり渡された場合に備えています。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
家族: 母
仕事: 山田課長
友人: 佐野
家族: 兄
仕事: 取引先窓口
友人: 中村

STEP 4NSDictionaryで分類ごとに数える

🎯 このステップのゴール: 数値をNSNumberで包み、可変の辞書に積み上げる

分類ごとの件数を数えます。使うのはNSMutableDictionaryです。

ここでObjective-Cの大きな制約に出会います。——コレクションにはNSIntegerのような素の数値を入れられません。入れられるのはオブジェクトだけです。

だから@(c.group)と書いてNSNumberに包みます@()「これをオブジェクトにする」という短い書き方です。

取り出すときは逆に.integerValue中身の数値に戻します包んで、出して、また包む——手間ですが、Objective-Cのコレクションを使うかぎり必ず付いてきます。

そして、この面倒がありがたい場面があります。——まだ無いキーを読んでも落ちませんcounts[key]nilを返します。

nilにメッセージを送っても落ちないのがObjective-Cの特徴です。current.integerValuecurrentnilなら0を返します。だから「キーが無ければ0を入れる」という前処理が要りません

これは他の言語と大きく違う性質です。JavaならNullPointerException、Swiftなら型エラー——Objective-Cは黙って0やnilを返します。

便利ですが、裏返すと危険でもあります。変数がnilのまま処理が進んでもエラーにならず、静かに間違った結果が出ます。「なぜか0になる」の原因は、たいていこれです。

取り出す順番はorderという配列で持っています。辞書は順番を保証しないからです。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
家族: 2件
仕事: 2件
友人: 2件
無いキー: 0件

STEP 5ブロックで並べ替える

🎯 このステップのゴール: 比較の手順を無名関数として渡す

最後に連絡した日が古い順に並べ替えます。

[contacts sortedArrayUsingComparator:^NSComparisonResult(Contact *a, Contact *b) { ... }]

^で始まるのがブロック——他の言語でいうラムダ式や無名関数です。その場に書いた小さな関数を、そのまま引数として渡しています

読み方に慣れが要ります。^NSComparisonResult(Contact *a, Contact *b)——^のあとが戻り値の型、括弧が引数。C言語の関数宣言に^を付けた形だと思ってください。

返す値は3つのうちのどれかです。NSOrderedAscendingなら1つ目が先、NSOrderedDescendingなら2つ目が先、NSOrderedSameなら同じ

名前が直感に反するので注意してください。「Ascending(昇順)」は「昇順に並べる」ではなく「1つ目のほうが小さい」という意味です。だから大きい順に並べたいときにNSOrderedAscendingを返す——ここは何度書いても迷うところです。

この演習ではa.lastCallDay > b.lastCallDayのときNSOrderedAscending——つまり日数の大きい(=古い)ほうを先に持ってきています。

sortedArrayUsingComparatorは元の配列を変えません。並べ替えた新しい配列を返します。NSArrayはそもそも変更できないので、これが自然な形です。

元を直接並べ替えたいならNSMutableArraysortUsingComparatorですが、「呼んだら元のデータが変わる」関数は扱いに注意が要ります

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
--- ごぶさたな順 ---
中村 120日前
佐野 47日前
兄 21日前
取引先窓口 9日前
母 3日前
山田課長 1日前
元の1件目: 母

STEP 6ひとつの関数にまとめて仕上げる

🎯 このステップのゴール: Reportにまとめ、日本語の幅を揃えて表にする

最後の仕上げです。ここまでの部品をReportという1つの関数にまとめます

桁揃えにPadRightを自分で書いています。「山田課長」は4文字ですが、等幅フォントでは英数字8文字ぶんの幅——文字数で数えると揃いません。

[s characterAtIndex:i]で1文字ずつ見て、0x80より小さければ1、そうでなければ2として数えています。

足すところでNSMutableStringを使っています。NSStringは変更できないので、繰り返し足すなら可変のほうです。[s mutableCopy]変更できる複製を作ってから足していきます。

この「不変と可変が別クラス」という考え方は、Objective-Cのいたるところに出てきます。NSArrayNSMutableArrayNSDictionaryNSMutableDictionary——すべて同じ対です。

既定は不変のほうを選んでください。変更できないものは知らないうちに書き換わる心配がありません。可変が必要になってから切り替えれば十分です。

static const NSInteger kStaleDays = 30;で「ごぶさた」の境目を定めます。先頭のkは定数を表す慣習で、Appleのコードでも広く使われています。

sorted.firstObjectで先頭を取っています。sorted[0]とは違って、空の配列でも落ちません——nilが返るだけです。「空かもしれない」ときはfirstObjectを使ってください。

完成です。kStaleDays10に変えてみてください。「ごぶさた」の付く相手が増えます。200にすれば0件になり、締めの行が消えます。

1つの数字を変えるだけで判定が全部連動する——定数に切り出しておいた効果が確かめられます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
===== 連絡先 =====
中村        友人   070-9999-0000 120日前  ごぶさた
佐野        友人   080-3333-4444  47日前  ごぶさた
兄          家族   090-5555-6666  21日前
取引先窓口  仕事   03-7777-8888   9日前
母          家族   090-1234-5678   3日前
山田課長    仕事   03-1111-2222   1日前
----------------------------------------------
  家族: 2件
  仕事: 2件
  友人: 2件
登録件数: 6件
30日以上連絡していない相手: 2件
いちばんごぶさた: 中村(120日前)

よくある質問

このページではコードを実行しないのですか?

していません。Objective-Cはコンパイルが必要で、ブラウザの中では走らせられないためです。このページではお手本と同じコードを書けたかどうかを判定する形にしています(判定はあなたのブラウザの中だけで行われ、コードはどこにも送信されません)。

ただし表示している実行結果は本物です。このページを作るときに実際にclangでコンパイルして走らせた出力を、そのまま載せています。

なぜ NSLog を使わないのですか

出力に日時とプロセス名が混ざるからです。

NSLog(@"合計: %d", 1234)と書くと、実際には2026-08-18 13:04:00.586 a.out[75024:25908553] 合計: 1234のように出ます。実行するたびに変わるので、教材に載せた結果と一致しません。

この演習ではprintfUTF8Stringに統一しています。

実務ではNSLogで構いません。日時が付くのは、ログとしてはむしろ利点です。

NSInteger を %d で出してはいけないのですか

環境によって壊れます。64ビットではNSIntegerlong、32ビットではintになるためです。

%ldを使い、(long)でキャストするのがお決まりの形です。printf("%ld日", (long)c.lastCallDay)のように書きます。

キャストを省くと、環境を変えたときだけ壊れます。手元で動いていたものが別の機械で崩れる——見つけにくい種類の不具合です。

nil にメッセージを送っても落ちないのは便利ですか

便利ですが、危なくもあります。

便利な面は、nilチェックを省けることです。この演習でも、辞書に無いキーを読んでnil.integerValueが0を返すおかげで、「キーが無ければ0を入れる」という前処理が要りません

危ない面は、間違いが表に出ないことです。変数がnilのまま処理が進んでも例外にならず、静かに0や空文字が返り続けます。「なぜか0になる」「なぜか何も表示されない」の原因は、たいていこれです。

大事な場面では、自分でnilを確かめてください

🚩 次はこのカテゴリへ

SwiftはObjective-Cの後継として作られた言語です。この演習で書いた@interface@implementationの分離も、@(...)で数値を包む手間も、Swiftではどれも要りません。そしてnilは型で管理され、黙って0を返すことがなくなりました。Swiftカテゴリの最終演習で、その違いを確かめてみてください。

Swiftのレッスンへ進む
広告スペース(バナー)
広告スペース(記事内)