2014年10月22日水曜日

[iOS][Objective-C]NSDateの比較


どうも。

NSDate同士の比較。
これまで機会がありそうでなかったので、
どうやるんだろうかと...。

もうObjective-Cに関わって何年にもなって、
Swiftも出てきたというのに、
こういうことがちょいちょいありますw

ググれば即出てきますが、
こんなカンジです。

 
    NSDate *date1;
    NSDate *date2;

         (中略)

    NSComparisonResult result = [date compare:date2];
 
    switch (result) {
        case NSOrderedSame:
            // date1 == date2
            break;
        case NSOrderedAscending:
            // date1 < date2
            break;
        case NSOrderedDescending:
            // date1 > date2
            break;
    }


NSComparisonResultはNSNumberの比較でも使いますね。
NSNumberの比較というのも、
個人的にあまりしたことがありませんが...。

NSDateはNSDateFormatter使って、
文字列から生成することもあります。
元になる文字列が、
・フォーマット違反
・日付として無効な数値
になっているなどの場合はnilが返ってきます。
まぁ、ここまでは合点がいきます。

問題なのは、
その後のcompareです。
nilとのcompareは、
NSOrderedSameが返ってきてしまう...。

文字列が信用できるならいいんですが、
今回は文字列が人の手による入力で、
しかも事前にはチェックすらできない状況だったりします。

仕方ないので、
ある程度泥くさいチェックを書かなきゃいけませんね...。

それでは。
ちゃお☆

まこぴー。

2014年10月16日木曜日

[iOS][Objective-C]NSStringからの変換とチェック


どうも。

ひさびさにiOSというか、
Objective-C...。

Swiftの覚え書きはまだもうちょっと先になりそう。
まだまだ仕事ではObjective-Cが現役なんでw

今回は、NSStringです。
NSStringからintやBOOLに変換できます。
plistに設定値を書いといて、
読み出してプログラム内で使うときなんかに重宝します。

NSString *aStr = @"123";
int a = [aStr intValue];
とすると、
aには123が代入されます。

NSString *bStr = @"YES";
BOOL b = [bStr boolValue];
とすると、
bにはYESが代入されます。

まぁ、
当たり前の話ですw
当たり前に使っているが、
実は仕組みを詳しく知らなかったりする。

まずはint変換から。
数値でない文字列の場合どうなる?
Javaで同じようなことをするときは、
NumberFormatExceptionで受けたりしますが、
そういう作法が存在しない以上、
何かしらよしなに値を返してくれるはずです。

NSString *aStr = @"aaa";
int a = [aStr intValue];
とすると、
aには0が代入されます。

@"aaa" → 0
@"123aaa" → 123
@"aaa123" →  0
@"123aa4" → 123

なるほど、
数値文字で始まっていれば、
それが続く限りは数値として認識してくれるみたい。
@"123aaa"を許容しないような場合には、
自力でチェックが必要になります。

チェックについては、
ConvertCheckというカテゴリにして実装してみた。

[NSString+ConvertCheck.m]

- (BOOL)canBeConvertedToInt {
 
    return [self isEqualToString:
            [NSString stringWithFormat:@"%d", [self intValue]]];
}

他にもNSScannerとかNSCharacterSetや正規表現も使えますが、
先に文字列チェックしてしまうとintの範囲を超えたりを気にしたりしなかったり、
コードもちょっと複雑になるんで...。

あと、
これだと@"0000"や@"000009"みたいなのは変換不可になってしまいます。
今回書いたコードの用途上はそれを弾いちゃっていいので構いませんが、
その辺は仕様や実情に合わせたコードを書く必要があろうかと思います。

さて、
今度はBOOL変換。

試してみる前に、
NSString.hを覗いてみると、
こんなコメントがあります。
/*
Skips initial space characters (whitespaceSet), or optional -/+ sign followed by zeroes.
Returns YES on encountering one of "Y", "y", "T", "t", or a digit 1-9. 
It ignores any trailing characters.
*/

つまり、
@"Yeah"でもYESですw
まぁ、
BOOLに関しては、
厳密なチェックが必要なケースってほとんどないと思うんで、
チェックするメソッドも実装しません。
なるほどねーっていうだけですw


それでは。
ちゃお☆


まこぴー。

2014年9月28日日曜日

[Android]Androidアプリのエントリポイント


どうも。

いろいろと忙しく、
久々の覚え書き。

Androidも関わる機会が多くなってきたので、
今回はAndroidに関するトピック。

自分でゼロから作ったり、
最初から全容を把握しているアプリならいいんですが、
もう出来上がったものに対して、
改修や不具合対応などをせねばならない場合もある。
で、
大体の場合ドキュメントはない...。
諸般の事情で、
内部の詳しいつくりを把握している人もいない...。

こんなときには、
与えられたソースコード一式の解析からスタートせねばならない。

で、自分の場合はAndroidアプリに関しては、
大した実戦経験がないワケで...。

しょんぼりしていても仕方ないし、
Androidアプリに限らず、
こんな状態のスタートはよくあることだったりする。

MainActivityとかTopActivityなどと、
クラス名などからアタリがつく場合もあれば、
必ずしもそうでない場合や、
規模が大きい場合など、
まずは困ったときには(アプリの)エントリポイントを探るのが手っ取り早い。

ということで、
Androidアプリのエントリポイントはというと、

AndroidManifest.xmlのintent-filter要素に、
android.intent.action.MAINが設定されているもの

という探し方ができるようだ。
ただし、
android.intent.action.MAIN
は複数のアクティビティに設定でき、
そんな場合には、
android.intent.category.LAUNCHER
が設定されているもの
ということらしい。

すなわち、

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

となっているようなActivityを見つければよいことになる。


それでは。
ちゃお☆


まこぴー。

2014年7月18日金曜日

[iOS][Android]iOSアプリ開発からのAndroidアプリ開発


どうも。

ご無沙汰しています。

仕事ではObjective-CでiOSアプリ開発して、
業務外では少しずつSwiftも弄ったりして、
もう一度原点回帰でC++辺りも学び直したいな、
などと、
平和に過ごしていたのですが、
いきなり仕事でAndroidやることに…。
突然なのはラブストーリーだけでじゅうぶんです。

このAndroidやる経緯を書き出すと、
文句だらけになってしまうのでw

とにかく、
望むか望まざるかに関わらず、
俺はAndroidをやらなければならなくなった。
ほとんど実践経験なしです。

iOSとAndroidで同時に開発してたプロジェクトのときに、
画面を含めた仕様を共通で自分が握ってたのと、
何かの折にコードレビューした程度。

iOSアプリの開発してると、
Android開発やってた人が、
突如iOSのエンジニアに仕立てられるというのをよく見てきたが…。
まさか自分がその逆をやらされるとはw

とにかく、
iOSアプリのエンジニアが、
最短でAndroidのエンジニアになる為にどうしたらいいか?

優秀なエンジニアは何でもすぐにできますが、
自分は並以下なんで…。

Androidは基本的にはJavaで書くことになる。
数年前に1年ちょっとくらいJavaの案件に携わる機会があったくらい。
(ほとんど上流工程しかやってないから、実践的な実装経験はかなり乏しい)
とりあえず、Javaがどんなもんだったか思い出す為に、
「AndroidエンジニアのためのモダンJava」
という本を買ってみた。
http://www.amazon.co.jp/Android%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A2%E3%83%80%E3%83%B3Java-%E5%B1%B1%E7%94%B0-%E7%A5%A5%E5%AF%9B/dp/477415878X

あっさりした内容だが、
今回の用途には適している気がする。
分かりきってるところはチラ見もしくはぶっ飛ばしてw
(半分以上が不要なんだけどね…w)
個人的にはコレクションについてとマルチスレッドについては、
多少しっかりと。

ネット上で目的に適ったページを探してみた。
事細かく説明してくれるワケではないが、
下記のページが非常に参考になる。

http://developer.smartnews.be/blog/2013/09/06/ios-android/

http://akisute.com/2014/01/ios-android.html

ここまでで、
Hello, world!
くらいのレベルはちゃきちゃきクリア。

ちなみに、
iOSとAndroidを組み合わせた検索をすると、
クロスプラットフォーム的なトピックばかりで、
なかなか今回の用途にフィットした情報は見つからない。
(根気強さと検索テクニックに欠けるのかも…)

後は、
アプリ開発的に重要と思われる事項を概念レベルで確認。
・アクティビティ
・サービス
・インテント
・UI部品(どんなのが標準であるか?のレベル)

ここまでサラリとやっておくと、
何となくキャッチアップできた(気がする)

あとは、
Javaでのシングルトンの実現方法と、
グローバル変数の扱い方の確認。
こいつらは乱用あるいは使用事態を原則的に避けねばならないが、
いざというときに強力な助けになる!

グローバル変数はJavaには概念上存在しないが、
抜け道はあります。
一般にバッドノウハウとして嫌われがちで、
Javaの世界では恐らくその傾向はより顕著でしょうが、
数画面程度のアプリでは使った方が、
解決としてシンプルな場合もあります。

と、
限られた時間(合計して数時間)で、
上記のような整理をして、
なんとかかんとか、
とりあえずは仕事としては進めてる感じですw

あとはやりながら身について、
ノウハウが蓄積されていくと思います…。
もちろん、
継続的に学習は必要かと思いますが。

ということで、
Androidに関しても、
覚え書きしておきたいことは、
整理も含めてここに書く。
と思います。


それでは。
ちゃお☆


まこぴー。

2014年7月5日土曜日

[iOS][Objective-C]NSLogをファイルに出力する


どうも。

NSLogによる出力をコンソールで確認。
デバッグ時によくやることです。

常にMacと端末をつなげて、
1台もしくは2台程度でデバッグ実行する場合にはいいのですが、
台数が多くなってきたり、
持って歩いて動作確認といった状況が発生した時に、
ログ出力が確認出来ない…。

NSLogの出力先は、
通常は標準エラー出力(stderr)です。
それをファイルにリダイレクトすればいい。


    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                         NSUserDomainMask,
                                                         YES);
    NSString *docDir = [paths objectAtIndex:0];
    NSString *path = [docDir stringByAppendingPathComponent:@"log.txt"];

freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);


上記コードでは、
(App)/Documents/log.txt
にコンソールの内容が出力される。

これを、
AppDelegateのdidFinishLaunchingWithOptions
辺りに書いておけばよいかと。

ファイルの内容やファイル自体の破棄など、
ログファイルのライフサイクルは、
freopen()のオプションで変えるとか、
ファイルを破棄する処理を組み込むとか、
実情に応じた形で。


それでは。
ちゃお☆


まこぴー。

2014年6月24日火曜日

[iOS][Objective-C]NSMutableArrayの要素をイテレートしながら削除する


どうも。

ちょっと考えれば分かることなんですが、
あまりこういうシーンが今までなかったので…。

「NSMutableArrayから、
何かしらの条件に合致する要素を削除したい。」

となったときに、
・条件判定→ループ
・要素の削除→removeObject

で、

for (int i = 0; i < [aList count]; i++) {
    ・
    ・
    ・
}

と書き始めて、
「これじゃアカン!」
と…。

removeすると削除して詰めちゃうから、
インデックスがおかしくなる…。

と、
ちょっと考えてみて、
「index降順でループすればいいじゃん!」
という結論。

    int aListCnt = (int)[aList count];

    for (int i = aListCnt - 1; i >= 0; i--) {
 
   MyClass *myClass = [aList objectAtIndex:i];

   if ([myClass shouldBeRemovedFromList]) {
 
        [aList removeObjectAtIndex:i];
       }
    }

まぁ、
これまでこんなシーンがなかったんだなぁと、
なぜかしみじみw

んで、
int aListCnt = (int)[aList count];
というまどろっこしいのがあるんですが、
警告対策です。

countメソッドはNSUIntegerが返ってくるので、
for (int i = [aList count] - 1; …………
と書くと警告が出ます。

では、
for (NSUInteger i = [aList count] - 1; i >= 0; …………
と書いたらどうか?

これはこれで別な警告が出ます。
「unsignedなんだから、i>=0って常に真でしょ?」
みたいな。

まぁ、
for (int i = (int)[aList count] …………
と書けばいいんですが、
ループの条件が長いのも鬱陶しいので、
こんな整理にしてみました。


それでは。
ちゃお☆

まこぴー。

2014年6月11日水曜日

[iOS][Objective-C]バックグラウンド動作


どうも。

アプリがバックグラウンドに遷移しても、
処理を継続したい。
というのは、
要件としてありえる話です。

しかしながら、
iOS側の思想としては、
「アクティブでないのなら可能な限り速やかにサスペンドする」
というような感じです。
これは、バッテリー消費等の観点だと思われます。

通常、
バックグラウンド遷移後、
RunLoopやワーカスレッドの停止により、
サスペンド状態になり、
アプリ動作は停止する。

じゃあ、
本気で即時サスペンドかというとそうでもなく、
AppDelegateの
- (void)applicationDidEnterBackground:(UIApplication *)application;
の中で、
然るべきデータの保存等が可能なように、
5sec以内の処理が許可されている。

ある程度の間処理が継続出来ればいい、
というのであれば、
下記のような方法により、
サスペンドまでの時間を延長する事ができる。
注意が必要なのは、
その延長時間は、
iOS6では600sec、iOS7では180secとなっていること。

[AppDelegate.m]


UIBackgroundTaskIdentifier bgTask = 0;

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    UIApplication *app = [UIApplication sharedApplication];

  bgTask = [app beginBackgroundTaskWithExpirationHandler:^{

  [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

上記はアプリ全般に言える事ですが、
特定の位置づけのアプリであれば、
上記の制約を飛び越える事ができます。

Audio再生や位置情報の利用など。
<AppName>-Info.plistに、
Required background modesとして定義する事が出来る。

だから、
iPodみたくAudioの再生をバックグラウンドでも止めたくない、
とか、
ナビゲーション機能はバックグラウンドだからといって止まってもらっては困る、
というのは、
Info.plistに定義の上、
無制限で処理の継続が許可される。

iOS7からは、
Background fetchとか、
Remote notificationsといった仕組みが追加になっているので、
SNSやWebサービス連携であれば、
そうした仕組みを積極利用すべきと考えられます。
これらは何かしらのトリガにより、
一時的にサスペンドから復帰して処理を実行後、
またサスペンド状態に戻る。
というような仕組みに見受けられる。
(使った事ないから詳しくは分からないw)

で、
裏技というか…。
バッテリー消費量だったり、
(iOS開発観点での)倫理的な問題から、
個人的にはやらない方がいいと思うんですが…。

やろうと思えば、
Info.plistに定義しといて、
空のAudioを再生し続けるとか、
必要なくても位置情報を更新し続けるとかすれば、
バックグラウンド動作が継続出来るワケですw

完全なバッドノウハウだと思います。
Rejectされない保証や、
後々取り下げられない保証はありません。
こういうのは危険と隣り合わせ、
と思っておくくらいがちょうどいいと思うし、
Storeに出したいのなら、
裏技は使わない方がいいかなと思います。

個人で無料で出すようなアプリならまだしも、
会社名義で出すようなアプリでは、
バッドノウハウは排除すべきだと思います。
しかしながら、
そうした警告をしても、
全く意に介さない人もいるので、
痛い目に遭わなきゃいいですがね…、
と見守るしかないですw


それでは。
ちゃお☆


まこぴー。