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
にコンソールの内容が出力される。

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

ファイルの内容やファイル自体の破棄など、
ログファイルのライフサイクルは、
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


それでは。
ちゃお☆


まこぴー。

2014年6月3日火曜日

[iOS][Swift]新しいプログラミング言語「Swift」


どうも。

またまた、
ずいぶんとご無沙汰してしまいました…。

WWDC 2014があり、
iOS8の発表やら、
なんとまぁ、
新プログラミング言語の「Swift」の発表があったり…。

で、
そのSwift。
リファレンスを読むどころか、
早くもXcode6でSwiftをお試し実装している人たちもいる訳ですw
(まぁ、エンジニアたる者、自分もそうでなきゃいけないんですが…)

ちょいとすぐにはそんな時間が取れないので、
移動時間にリファレンスをチラ見して、
先取りしている人たちのコードなんかもチラ見して。
既にググれば、
サワリとしてはそこそこな情報があります。

で、
受けた印象としては、
コードの見た目的には、
JavascriptとかPythonとかに似ているかな。
まぁ、モダンな言語のいいとこ取りみたいな感じなんでしょうか?

Objective-Cに比べれば、
コード量や可読性は一気に上がりそうです。

iOS8以降のみをターゲットにして、
新たに書くならSwiftの方がいいかも知れない。
共存しながらのコンバートを経て、
最終的にはSwiftに完全に置き換わるのか、
果たして…。

ともかく、
実情のお仕事ベースで言うと、
これまで、そして、
いまいまはObjective-Cで書いているワケで、
当面はObjective-Cとのお付き合いは続きそうです。
また、ゆくゆくも、
レガシーなiOSアプリのコードとして付き合わざるを得ないシーンはあるかと。

忘れてはいけないことは、
iOS開発であることには変わりない。
ということです。
クセのあるUIとか画面遷移だとかマルチタスキングだとか、
気を使わなければならないポイントは同じ。

このブログも、
iOSアプリをObjective-Cで開発する、
という前提のトピックばかりでやってますが、
当面はその内容は変わらないと思います。
(まぁ、あまり更新もできていなかったりもしますが…w)

もともと自分が開発をする上で困らないように覚え書きしているので、
Objective-Cの開発が続くのであれば、
Objective-Cの覚え書きをするし、
違う言語になればその覚え書きをするまでです。


それでは。
ちゃお☆


まこぴー。

2014年4月17日木曜日

[iOS][Objective-C]StackとQueue(簡易実装)


どうも。

ずいぶんとご無沙汰しました…。
ごぶさたした理由はいろいろありますが、
書くと長いのでw

で、
今回のお題は、
StackとQueueについて。

Javaには、
java.util.Stackとかjava.util.Queueとかあって、
普通に使えるワケですが、
Objective-Cにはそんなのない…。

StackやQueueというのはデータ構造であって、
その仕組みが分かっていれば、
NSMutableArrayに対して、
相応の処理をしてやればいいだけ。

とは言え、
popとかpushと書いて、
イメージ通りに動く方が見た目にもスッキリなので、
予め実装しておく。

NSMutableArrayを使うのであれば、
カテゴリにしてしまうのが楽ですね☆

カテゴリ名は、
VirtualStack, VirtualQueueとでもしておきます。
Virtualなんてつけるほどすごいことでもないけどw

[NSMutableArray+VirtualStack.m]

#import "NSMutableArray+VirtualStack.h"

@implementation NSMutableArray (VirtualStack)

- (id)pop {

    id lastObj = [self lastObject];
 
    if (lastObj != nil) {
     
        [self removeLastObject];
    }
 
    return lastObj;
}

- (void)push:(id)obj {
 
    [self addObject:obj];
}

@end

[NSMutableArray+VirtualQueue.m]

#import "NSMutableArray+VirtualQueue.h"

@implementation NSMutableArray (VirtualQueue)

- (id)dequeue {
 
    id headObj = [self objectAtIndex:0];
 
    if (headObj != nil) {
     
        [self removeObjectAtIndex:0];
    }
 
    return headObj;
}

- (void)enqueue:(id)obj {
 
    [self addObject:obj];
}

@end

と、
ここまで書いておいて…。
これって、
StackもQueueも両方あったら、
Stackとしてpush-popしてきたものに対して、
いきなりdequeueとか出来ちゃったりするw

それぞれ、
NSMutableArrayをメンバーに持つ、
別なクラスに切り出した方がいいのかもしれない。
複数人開発や、
マルチスレッドでのアクセスがありうるとか、
そういうリスク管理の観点では、
クラスとしてきちんと区別した方がいい。

あくまで、
簡易的に実現するなら、
という方法ですね、これは。


それでは。
ちゃお☆


まこぴー。

2014年4月3日木曜日

[iOS][Objective-C]UIの表示位置やサイズを変更する


どうも。

実際の業務となると、
UIの表示位置やサイズを変更することが、
ちょいちょいとあります。

aViewというUIView*があって、
Y位置を100, 高さを100にしたいとすると…。

aView.frame.origin.y = 100.0f;
aView.frame.size.height = 100.0f;

としてやれるなら簡単ですが、
上記はできません…。

X, Y, Width, Heightのどれかをひとつだけでも変えたい場合でも、
やれることは、
[aView setFrame:xxxx];
だけである。

従って、
新しいCGRectを作ってやって、
そのCGRectをsetFrameしてやるしかない…。
上記の例だと、
XとWidthが変わらないから、
まずは元の位置をベースにしてCGRectを取得し、
YとHeightを変更。
そのCGRectをsetFrameする。

CCRect newFrm = aView.frame;
newFrm.origin.y = 100.0f;
newFrm.size.height = 100.0f;

[aView setFrame:newFrm];

ひとつの部品が1回動くだけならいいけど、
いくつかの部品を相関関係も気にしながら動かさなきゃいけないことの方が多い。
そうなると、
上記のようなコードをたくさん書かねばならず、
可読性やメンテナンス性が大きく損なわれる。

ので、
UIViewのカテゴリにしてしまう。
UIButtonとかUISwitchとか、
その辺も軒並みUIViewを継承しているので、
全般的に使える。

例のごとく、
カテゴリ名はとりあえずAddition…。
ソースはちょっと分量が多いのでヘッダのみ。
実装は上記のようなコードを書いて、
さらに内部でコールしているに過ぎない。

#import <UIKit/UIKit.h>

@interface UIView (Addition)

#pragma mark - Setters of X, Y, Width, Height
#pragma mark (with one parameter)
- (void)setX:(CGFloat)x;
- (void)setY:(CGFloat)y;
- (void)setW:(CGFloat)w;
- (void)setH:(CGFloat)h;
#pragma mark (with two parameters)
- (void)setX:(CGFloat)x Y:(CGFloat)y;
- (void)setX:(CGFloat)x W:(CGFloat)w;
- (void)setY:(CGFloat)y H:(CGFloat)h;
- (void)setW:(CGFloat)w H:(CGFloat)h;
#pragma mark (with all parameters)
- (void)setX:(CGFloat)x Y:(CGFloat)y W:(CGFloat)w H:(CGFloat)h;
#pragma mark (with CGPoint, CGSize)
- (void)setPoint:(CGPoint)p;
- (void)setSize:(CGSize)s;

#pragma mark - Calculation of X, Y, Width, Height
#pragma mark (of X)
- (void)xPlus:(CGFloat)f;
- (void)xMinus:(CGFloat)f;
#pragma mark (of Y)
- (void)yPlus:(CGFloat)f;
- (void)yMinus:(CGFloat)f;
#pragma mark (of Width)
- (void)wPlus:(CGFloat)f;
- (void)wMinus:(CGFloat)f;
#pragma mark (of Height)
- (void)hPlus:(CGFloat)f;
- (void)hMinus:(CGFloat)f;

@end

経験上、
これだけ実装しておけば、
あまり困らないハズ。
とにかく1ステップで位置/サイズ調整が終わるのは、
ソースコードの可読性に歴然と差が出る。
と思う。

それでは。
ちゃお☆


まこぴー。

2014年4月2日水曜日

[iOS][Objective-C]Storyboardの分割



どうも。

未だにStoryboardっつーのが、
よくわからないw
過去のプロジェクトではxibばかり扱ってきたので…。

で、
今のプロジェクトになってようやく、
Storyboardを使っているワケです。

最終的には担当者が自分ひとりになってしまったけど、
最初は3人いて、
ひとつのStoryboardを3人でいじってマージをするという、
悲惨な状況でしたw

根本的に、
xibにせよStoryboardにせよ、
実態はXMLなので、
XMLベースでマージをすればいいんですが、
まぁ大変です。

しかも、
後から画面を付け加えて、
pushViewControllerとかできなかったりして、
直感的なようで直感的でないし、
よくわからない…。

Storyboardをイメージでしか理解していなかったので、
複数人開発には向かないなぁ…。
なんて思っていました。

結局時間もなくて根本改善ができなかったのですが、
個人的にはひとつの答えに行き着きました。

Storyboardを最初から分割すればいいのでは…。
そして、
Push-Pop型の画面遷移の場合、
RootのViewControllerがいるワケで、
そいつからの流れでどんどんPushしてやれば、
Storyboardの見た目上でつながりを持っていなくてもいいのでは…?

で、
Root-Sub1-Sub2-Sub3
と、
それぞれ別のStoryboardにして、
・順番にPushしていって順番にPopできるか?
・popToRootViewControllerできるか?
換言すると、
NavigationControllerによるスタック管理がきちんと成り立つのか?
を検証してみたところ、
オールOK!
期待通りの結果が得られた。

まぁ、
Segueとかいうのを使ってつなげて、
ひとつのStoryboardで全画面をマッピングできるし、
ソースコード量が減らせる。
というのがStoryboardを使う醍醐味のひとつな気もしますがw



それでは。
ちゃお☆


まこぴー。