Objective-Cで、サブクラスだけで使用出来るプロパティを作成する

このエントリーをはてなブックマークに追加
はてなブックマーク - Objective-Cで、サブクラスだけで使用出来るプロパティを作成する

Objective-Cでクラスを作る場合は、他のクラスからアクセスさせたくないプロパティはクラスエクステンションにして、自クラスだけでアクセス出来るようにします。こんな感じです。

PCEBaseClass.h
PCEBaseClass.h

モジュールはこんな感じです。
PCEBaseClass.m
PCEBaseClass.m

こうすることによって、notesというプロパティは、参照するクラスからは変更されたくないので、ヘッダーにはreadonly,クラスエクステンションでは、readwriteで定義することによって、クラス内で変更可能です。
booksというプロパティは、クラス内ではNSMutableArrayとして振る舞いたいけれども、対外的には変更してほしくないので、NSArrayとして返しています。booksと別のbooksInternalという内部用プロパティをNSMutableArrayで定義して、内部ではbooksInternalにアクセスすることによって、オブジェクトの追加が出来るようにしています。

このクラスを継承したクラスを作った場合は、対外的には同じくbooks, notesを触られたくないですが、継承したクラス内では触りたい訳です。

PCEExtendedClass.h
PCEExtendedClass.h

モジュールでbooks, notesを触ろうとすると当然ですが、エラーになります。
PCEExtendedClass.m
PCEExtendedClass.m

これを解決するために、クラスエクステンションを別ファイルに出してあげると解決出来ました。
Class Extentionを作成
Class Extentionを作成

このようにします。
クラスエクステンション作成2
クラスエクステンション作成2

クラスエクステンションの新しいファイルに、プロパティを記述
クラスエクステンションの内容
クラスエクステンションの内容

ベースクラスでは、クラスエクステンションをモジュールでImportします。

ベースクラス
ベースクラス

継承したクラスでもクラスエクステンションをモジュールでImportします。booksに関しては、内部的にmutableのプロパティ booksInteranlをクラスエクステンションで定義しているのでそちらを使用します。

継承したクラス
継承したクラス

これで、他のクラスからはprivateExtentionのヘッダーをインポートしないようにすれば定義が見えないので、コンパイル時点で正しい処理が行われていることを確認出来ます。

libextobjc の @ keypath でObjective-Cのプロパティ名を文字列化する

このエントリーをはてなブックマークに追加
はてなブックマーク - libextobjc の @ keypath でObjective-Cのプロパティ名を文字列化する

まえから、型チェックをした上でプロパティ名を文字列化したいと思っていました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface MyClass
@property (nonatomic, strong) NSString * myPropertyName;
@end
@implementation MyClass
-(void) myFunc {
    self.myPropertyName = @"abc";
    NSString * property = NSStringFromSelector(@selector(myPropertyName));
    // myPropertyName が出力される
    NSLog(@"%@", property);

    // abc が出力される
    NSLog(@"%@",[self valueForKey:property]);
}
@end

これだと、SEL型から名前をえることが出来るのですが、myPropertyNameというプロパティが、MyClassのプロパティという保証がないので、困ることがあります。valueForKey:で使用した時に間違った方で使用してしまうこともあります。それを解決する方法をまえから考えていたのですが思い浮かばなかったものの、ReactiveCocoa(ReactiveCocoa/ReactiveCocoa )使っていたら、Libextobjc(jspahrsummers/libextobjc )にそれっぽいものがあったので調べてみました。libextobjc は、Objective-Cを便利に使う機能をまとめた関数群で、マクロなどを駆使して欲しい機能が実装されています。libextobjcの、”Compile-time checking of selectors”にはこのように説明されています。

Compile-time checking of selectors to ensure that an object declares a given selector, using EXTSelectorChecking.

セレクタの型チェックをコンパイル時に行うことが出来る、いい感じですね。これで上を書き換えるとこうなります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface MyClass
@property (nonatomic, strong) NSString * myPropertyName;
@end
@implementation MyClass
-(void) myFunc {
    self.myPropertyName = @"abc";
    NSString * property = @keypath(self.myPropertyName);
    // myPropertyName が出力される
    NSLog(@"%@", property);

    // abc が出力される
    NSLog(@"%@",[self valueForKey:property]);
}
@end

分かりやすいですね!ここで、”self.myPropertyName”と書いているので、どのインスタンスの型のプロパティかがコンパイル時にチェック出来ます。RestKitやMantleなどのO/Rマッピング機能を使うために、文字列型でプロパティ名が欲しいことが多いのでとても役立ちます。

どうやっているのかを見て見たところこんな感じでした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * \@keypath allows compile-time verification of key paths. Given a real object
 * receiver and key path:
 *
 * @code

NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String);
// => @"lowercaseString.UTF8String"

NSString *versionPath = @keypath(NSObject, version);
// => @"version"

NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString);
// => @"lowercaseString"

 * @endcode
 *
 * ... the macro returns an \c NSString containing all but the first path
 * component or argument (e.g., @"lowercaseString.UTF8String", @"version").
 *
 * In addition to simply creating a key path, this macro ensures that the key
 * path is valid at compile-time (causing a syntax error if not), and supports
 * refactoring, such that changing the name of the property will also update any
 * uses of \@keypath.
 */

#define keypath(...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))

#define keypath1(PATH) \
    (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))

#define keypath2(OBJ, PATH) \
    (((void)(NO && ((void)OBJ.PATH, NO)), # PATH))

分かりにくいですが、keypath1のところだけを見てみると、

1
2
#define keypath1(PATH) \
    (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))

こんな感じで、左の、”(NO && ((void)PATH, NO))”でコンパイルチェックだけを通して、実際にはNO &&の後なので使用されないようにしています。
後半の右側で、パラメータを#defineマクロの機能で、文字列化して(# PATH)、その”.”の右側をとったC文字列を作成して、”myPropertyName”となるのですが、keypath( の前に@を付けているので、Objective-C文字列になるという訳ですね。

言葉で説明すると分かりにくいですが、使うと便利と思います。

#fukuObjC 福岡Swift&Obj-C&Xcode開発周り勉強会 – 0×02 で話してきました

このエントリーをはてなブックマークに追加
はてなブックマーク - #fukuObjC 福岡Swift&Obj-C&Xcode開発周り勉強会 – 0×02 で話してきました

Swift&Obj-C&Xcode開発周り勉強会 – 0×02 の実況ツイートまとめ #fukuObjC – Togetterまとめ

久しぶりにiOS開発の勉強会に出てみようということで、福岡では出たことなかったのですが参加してみました。

Swift&Obj-C&Xcode開発周り勉強会 – 0×02 on Zusaar

せっかくですので新しいものをやってみて登壇しようと思い、関心のあったReactiveCocoaの勉強してサンプルアプリを作って、iOSアプリを綺麗に作る方法についてまとめてみました。

ReactiveCocoaは面倒ですけど、ちゃんと作れば良いプログラムが出来るのは間違いないのでどんどん取り込んでいきたいと思います。SwiftでのFRPも調査していきたいですね。

福岡の勉強会初めてでましたが、他の発表も面白かったです。Swiftはこういう勉強会で軽く見ながら実践投入する段階で一気に勉強するのが良さそう。いかに公開してくださったスライドのリンクを置いておきます。

はなだ_のぶかず (nobkz)さん

uounɹɐʇ (tarunon)さん
NSUserDefaults噺

くろねこまいける (kuronekomichael)さんの発表もそれぞれ良かった。スライドが上がったら追記します。

[Objective-C] libextobjc の @weakify と @strongify について

このエントリーをはてなブックマークに追加
はてなブックマーク - [Objective-C] libextobjc の @weakify と @strongify について

ちょっと遅れた話題ですが、libextobjcライブラリを使用して、weak変数を使う方法があることを知ったので調査してみました。

これまでの記述法と問題点

これまでわたしは、この記事ARC+Blocks+llvm4.0時代のコード記述作法 | Zero4Racer PRO Developer’s Blog で書いたルールにしたがって、ivar(クラス内の変数)を基本的に使わず、block内で使用する場合に、weak化して使用する方法を使ってきました。WEAKSELFMAKE;というマクロを作成して、selfのマクロを作成しています。

1
#define WEAKSELFMAKE __weak typeof(self) wself = self

使用する時は、

1
2
3
4
5
6
7
8
{
  WEAKSELFMAKE;
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
  controller.block_completed = ^ (NSNumber * toReturn) {
    wself.numberValue = toReturn.copy;
    [wself redlowItems];
  }
}

のような感じです。これで結構シンプルに書けてほぼ問題がないのですが、スコープ内のstrongの変数を使う場合は、weak化を自分で行う必要がありました。ちなみにStoryBoardIDをクラス名にしてNSStringFromClass([SomeViewController class])で書くのが最近のお気に入りです。

1
2
3
4
5
6
7
8
9
10
{
  WEAKSELFMAKE;
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
  __weak typeof(controller) wcontroller = controller;
  controller.block_completed = ^ (NSNumber * toReturn) {
    wself.numberValue = toReturn.copy;
    [wself redlowItems];
    [wcontroller dismissViewControllerAnimated:YES completion:NULL];
  }
}

@weakify, @strongifyで出来ること

jspahrsummers/libextobjc は、Objective-Cを便利にする拡張機能ライブラリです。cocoapodで、

1
pod 'libextobjc', '~> 0.4'

を追加すれば簡単に追加出来ます。上のサンプルを書き直すと、

1
2
3
4
5
6
7
8
9
10
11
12
{
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
  @weakify(self);
  @weakify(controller);
  controller.block_completed = ^ (NSNumber * toReturn) {
    @strongify(self);
    @strongify(controller);
    self.numberValue = toReturn.copy;
    [self redlowItems];
    [controller dismissViewControllerAnimated:YES completion:NULL];
  }
}

こんな感じになります。self, controller というオリジナルと同じ変数名を使える代わりに、使う前に、@strongify(self)をしてあげないといけないのがポイントです。

動作原理

Objective-Cのコードに慣れていたらこれが若干気持ち悪いコードに見えます。selfだと循環参照になってしまう気がしてしまうからです。reactive cocoa – Explanation of how weakify and strongify work in ReactiveCocoa – Stack Overflow この記事には、マクロを展開した後のコードが載せられていて分かりやすいです。

@weakifyのマクロ展開
@weakifyのマクロ展開

これを見ると、@weakifyマクロで self_weak_ というweak変数を作って、@strongifyマクロでself=self_weak_;と新しいself変数を作成していることが分かります。同じ変数名(self)だったら、ローカルスコープのselfの方が優先されるため、selfを使っても循環参照しないんですね。

あと、@weakifyという、予約語を作っているように見えますが、これも強引で、後ろに autorelease (このスクリーンキャプチャの場合は try {} @finally{} 付けることによって、展開後に@autorelease という形になるようにして、無理矢理@予約語のように見せてるんですね。マクロでこんな強引なことが出来るんですね。これの残念なのは、Xcode上での予約された@propertyのようなものと実際には違うので、コード上の色がおかしくなることです。

@weakifyサンプル
@weakifyサンプル

まとめ

実際これをプロジェクト規約に取り込むかですが、若干微妙なところですね。@strongifyを忘れるとselfを使っているところで循環参照してしまうのと、2行使ってしまうのでコードが煩雑になるので、現在のWEAKSELFMAKE;マクロの方が綺麗な気もします。それで、展開したマクロのself_weak_を使ってあげれば綺麗になると思いました。

1
2
3
4
5
6
7
8
9
10
{
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
  @weakify(self);
  @weakify(controller);
  controller.block_completed = ^ (NSNumber * toReturn) {
    self_weak_.numberValue = toReturn.copy;
    [self_weak_ redlowItems];
    [controller_weak_ dismissViewControllerAnimated:YES completion:NULL];
  }
}

これで、@strongifyを書き忘れることもないですし、selfを内部で使っていた時に色で見分けるのも簡単になります。しばらくこれで使ってみようと思います。

参考

cocoapods – libextobjc
libextobjc/extobjc at master · jspahrsummers/libextobjc
Safx: libextobjcの@strongifyと@weakifyについて
Objective-C – weakify/strongify マクロを使うと weak self パターンが簡単に書ける – Qiita

CocoaPodsのプロジェクトで、ヘッダーファイルが読め込めない時の対処方法

このエントリーをはてなブックマークに追加
はてなブックマーク - CocoaPodsのプロジェクトで、ヘッダーファイルが読め込めない時の対処方法

1つのプロジェクトに複数のターゲットを作成している時に起きる問題のようで引っかかったのでメモ。

File not found エラー
File not found エラー

cocoaPodsを導入して、podsで導入したフレームワークのヘッダーファイルが読めずに、

19:9: fatal error: ‘RestKit/RestKit.h’ file not found

のようなエラーが出ました。
Continue reading

Macの空き容量を増やすために行った8つの整理整頓テクニック

このエントリーをはてなブックマークに追加
はてなブックマーク - Macの空き容量を増やすために行った8つの整理整頓テクニック

年度末前でいろいろ忙しくなる前に、時間を取ってMacの整理を行いました。なんかいろいろばたばたしていてブログを書くのも久しぶりです。いつもMacの残り容量が40ー60GBで、しばらくほっとくと20GBを切ったりしているので、それは困るなと思って、整理整頓をしました。
Continue reading

TweetOverview2.0.3でのキーボードショートカット対応とiPad/キーボードに関する考察

このエントリーをはてなブックマークに追加
はてなブックマーク - TweetOverview2.0.3でのキーボードショートカット対応とiPad/キーボードに関する考察

TweetOverview 2.0.3がリリースされました。
iTunes の App Store で配信中の iPhone、iPod touch、iPad 用 TweetOverview ツイート俯瞰デスクサイドアプリ

今回のバージョンでは、キーボードショートカットに対応しています。
特にiPadでデスクサイドに置いた時に、リンクのウェブページを見ている時に矢印キーでページの上下が出来ればと思い、iOS 7 のキーボードショートカット機能を使用して作成しました。動作でもはこちら

現在対応しているショートカットは以下のものです。

  1. ウェブページ表示中の、↑↓矢印キーによるスクロール
  2. ウェブページ、詳細画面を、⌘+wキーで閉じる
  3. ツイート一覧画面を、← → キーでスクロールする

iPadのキーボードは、Logicoolのウルトラスリムキーボードミニを使っています。iPad miniのサイズでキーボードがうちやすいか心配だったのですが、このキーボードはよく考えられていて、caps-lockを削ったりして、aキーからlキーの幅を確保して、タイピングがしやすくなっています。MacBook風の色もいいですね。同じキーボードの白と黒は9,000円くらいなのに、この色だけセールで4700円ほどになっているみたいで、おすすめです。白とも黒ともマッチしないシルバーなので不人気なのかな?黒とは同じ色ではないですが、キーの色が黒なのでマッチしている気はします。このキーボードにしたのは、まず薄くて軽いことと、次にiPadをたてても使えることです。アプリによっては,たてた方が使いやすいアプリも多いので、このキーボードのように縦向きでも横向きでも使えるのはとても便利です。

技術的には、iOS7で追加されたキー取得のAPIを使用しました。@k_katsumi さんの、サンプルコードを参照しました。
kishikawakatsumi/KeyboardShortcuts
ViewControllerでキーを取得するのですが、childViewControllerを使用していると、最下層(親)のViewControllerにしかキーがいかないので、受け渡しの仕組みを作る必要がありました。また、キーをキャプチャすると、WebViewの入力時にそのキーが効かなくなるので(矢印など)、それもwebViewの入力中のステータスを確認して読み込みたいキーを変えていく必要があります。

APIとして、ボタンを押し続けているステータスや、ボタンのリピートなどがあればさらに良いと思うので、その辺は将来のiOSに期待です。

最後にiPadにおけるキーボードの考察ですが、やはりタッチによる操作中でも、キーボードによる操作は非常に便利であるということが分かりました。スクロール操作を行うために画面にタッチするよりも、キーボードのしたボタンに指を置いて一定量を読んだ後にボンポンキーを押していく方が楽です。また、⌘+wなどは使う人には染み付いているので、その自然な動作がキーボードで出来れば、タッチパネルをタッチするよりも使用感が高いです。もちろんiPadアプリの場合はタッチでの動作がメインになるのですが、キーボードを使用している層には、このようにキーボードショートカット対応してもらえるとうれしい機能がたくさんあると思いました。実際このアプリになれてしまうと、Safariや別のアプリでウェブを見ている時も、ついつい上下ボタンを押してしまうようになりました。

ちょっと癖のあるアプリですが、是非TweetOverviewを使って、追加してほしいキーボードショートカットなどがあったらレポートいただければと思います。

Text Kitを使用してUITextViewにリンクを埋め込んだりタブでカラムつけたりする方法

このエントリーをはてなブックマークに追加
はてなブックマーク - Text Kitを使用してUITextViewにリンクを埋め込んだりタブでカラムつけたりする方法

TweetOverview 2.0.2 がリリースされました。
iTunes の App Store で配信中の iPhone、iPod touch、iPad 用 TweetOverview ツイート俯瞰デスクサイドアプリ

TweetOverviewはiOS 7限定でアップデートしたのですが、iOS7限定にして、iOS7の最新機能を出来るだけ取り込むためにそうしました。その一つがTextKitです。「上を目指すプログラマーのためのiPhoneアプリ開発テクニック iOS 7編」を書きました[内容紹介あり] | Zero4Racer PRO Developer’s Blog こちらのリンクにもある、iOS7の解説本を書くためにいろいろテストをした機能を使って作成しています。

Continue reading

「上を目指すプログラマーのためのiPhoneアプリ開発テクニック iOS 7編」を書きました[内容紹介あり]

このエントリーをはてなブックマークに追加
はてなブックマーク - 「上を目指すプログラマーのためのiPhoneアプリ開発テクニック iOS 7編」を書きました[内容紹介あり]

iOS 7向けのiOSアプリ開発を解説した書籍で、わたしも共著で参加せせて頂いた本が12月末に発売予定になり、Amazonにも登録されました。

わたしは今回は以下の部分に参加させていただきました。

  • Text Kit(一部)
  • Sprite Kit
  • 付録のGame Controller

発売に向けて現在は校正などを行っていますが、とても良い本に出来上がっていると思います。わたし以外が書いた部分を読むと特に、分かりやすく書かれているなと感じます。扱われている内容をリストアップすると、 Continue reading

コードからUIWebViewをスクロールさせる方法

このエントリーをはてなブックマークに追加
はてなブックマーク - コードからUIWebViewをスクロールさせる方法

昔は難しかったのですが、iOS5から、UIWebViewがscrollViewをプロパティで持つようになったので簡単になった!オフセットしすぎると中身が見えなくなってしまうので、maxOffset以下にするのがポイント。
スクロールダウン(一度に画面の1/3)

1
2
            float maxYOffset = self.webView.scrollView.contentSize.height - self.webView.scrollView.frame.size.height;
            [wself.webView.scrollView setContentOffset:CGPointMake(self.webView.scrollView.contentOffset.x, MIN(self.webView.scrollView.contentOffset.y+self.webView.bounds.size.height/3.,maxYOffset)) animated:YES];

スクロールアップ

1
            [wself.webView.scrollView setContentOffset:CGPointMake(self.webView.scrollView.contentOffset.x, MAX(self.webView.scrollView.contentOffset.y-self.webView.bounds.size.height/3.,0)) animated:YES];

このスクロールと、前の記事の編集中判定を使って、TweetOverviewでキーボードでスクロールダウン、スクロールアップ、cmd+wで詳細画面を閉じるというのを実装したのですが、外部キーボードと組み合わせると非常にいい感じです。審査して来週ぐらいにリリースされると思いますのでお楽しみに。