2014年3月1日土曜日

[iOS][Objective-C]NSLogをマクロで使う(その2)


どうも。

以前に、

NSLogはリリース後にもOrganizer等から見ることができるので、
<AppName>-prefix.pchにマクロを記述しておき、
リリースビルド時には自動的に無効化されるようにしておきましょう。
という記事を書いたのですが。

↓↓↓↓↓
http://synonym-of-raspberry.blogspot.jp/2014/01/iosobjective-cnslog.html

内容自体は変わらないのですが、
各プロジェクトでの使い回しなんかを考えると、
ヘッダファイルに切り出して、
そのヘッダを<AppName>-prefix.pchで#importする方が、
有用な気がした。

ので、
そんなヘッダを作ってみる。

[DebugLog.h]

//
//  DebugLog.h
//


#ifndef __DEBUG_LOG_H__

#define __DEBUG_LOG_H__

// ↓↓↓↓↓ DEBUG用ログ出力定義 ↓↓↓↓↓ //
// ※本番用のアーカイブビルドの際には自動で無効化される
//
#ifdef DEBUG
#define LOG(...) NSLog(__VA_ARGS__)
#define LOG_PRINTF(FORMAT, ...) printf("%s\n", [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
#define LOG_METHOD NSLog(@"%s", __func__)
#define LOG_METHOD_AND_ABORT LOG_METHOD; abort()
#else
#define LOG(...)
#define LOG_PRINTF(FORMAT, ...)
#define LOG_METHOD
#define LOG_METHOD_AND_ABORT
#endif

#ifdef DEBUG
#define LOG_POINT(p) NSLog(@"%f, %f", p.x, p.y)
#define LOG_SIZE(p) NSLog(@"%f, %f", p.width, p.height)
#define LOG_RECT(p) NSLog(@"%f, %f - %f, %f", p.origin.x, p.origin.y, p.size.width, p.size.height)
#else
#define LOG_POINT(p)
#define LOG_SIZE(p)
#define LOG_RECT(p)
#endif
//
//
// ↑↑↑↑↑ DEBUG用ログ出力定義 ↑↑↑↑↑ //

#endif  // #ifndef __DEBUG_LOG_H__

こういうのを、
組織で共有できると有意義ですが、
いかんせん会社にはiOSのエンジニアは自分しかいないし、
その自分は外に出っぱなしだし、
どうにもこうにも、にんともかんとも…。

まぁ、
NSString+Additionも含めて、
暇を見て個人でgitにでもアップしとこうかな。


それでは。
ちゃお☆


まこぴー。

2014年2月27日木曜日

[iOS][Objective-C]NSString+Addition


どうも。

コードスニペットとしては、
utilのようなクラスに書く前提の方が楽ですが、
実際の使用感としては、
カテゴリとして定義した方が直感的だし、
メソッド名としてもスッキリさせることができる。

ということで、
以前にスニペット的に覚え書きしておいた、
下記のトピックをカテゴリに定義しておく。

http://synonym-of-raspberry.blogspot.jp/2014/02/iosobjective-cnsstringlength.html
http://synonym-of-raspberry.blogspot.jp/2014/02/iosobjective-c_16.html

カテゴリ名は"Addition"とでもしておきます。
(まぁ、そんなネーミングにしたら手広そうってくらいのノリw)

[NSString+Addition.h]

//
//  NSString+Addition.h
//


#import <Foundation/Foundation.h>

@interface NSString (Addition)

- (NSUInteger)realLength;
- (BOOL)isWhiteSpaceOnly;


@end

[NSString+Addition.m]

//
//  NSString+Addition.m
//


#import "NSString+Addition.h"

@implementation NSString (Addition)


- (NSUInteger)realLength {
    
    NSUInteger count = 0;
    
    if (self == nil) {
        
        return count;
    }
    
    int length = [self length];
    
    for (int i = 0; i < length; i++) {
        
        unichar uchar = [self characterAtIndex:i];
        
        if (CFStringIsSurrogateHighCharacter(uchar)) {
            
            // サロゲートペア:上位サロゲート
            i++;
            ++count;
            
        } else if (CFStringIsSurrogateLowCharacter(uchar)) {
            
            // サロゲートペア:下位サロゲート
            // 無視(何もしない)
            
        } else {
            
            ++count;
        }
    }
    
    return count;
}


- (BOOL)isWhiteSpaceOnly {
    
    BOOL ret = NO;
    NSString *newText = @"";
    
    if (self == nil || [self isEqualToString:@""]) {
        
        ret = YES;
        
    } else {
        
        newText = [self stringByTrimmingCharactersInSet:
                   [NSCharacterSet whitespaceAndNewlineCharacterSet]];
        
        if ([newText length] == 0) {
            
            ret = YES;
        }
    }
    
    return ret;
}


@end

上記例の内容だと、
ソースファイルを取り込んで、
ヘッダをimportしてやって、
色んなプロジェクトでそのまま流用できます。
util的なノリです。

プロジェクト独自ではあるが、
そのプロジェクト内では色んなトコで使うようなものは、
また別のカテゴリにまとめるようにしておく。
色んなところで使えそうなものは、
NSString+Additionに追記していく。
という整理をすれば、
NSString+Additionを育てながら、
色んなプロジェクトで使い回していけるワケです。


それでは。
ちゃお☆


まこぴー。

2014年2月26日水曜日

[iOS][Objective-C]座標/サイズ構造体(CGRect, CGPoint, CGSize)のログ出力


どうも。

UIを扱う場合なんかには、
座標系の構造体、
CGRect, CGPoint, CGSizeを扱うことが多い。
で、
位置とかサイズが可変になってくると、
ログ出しして確認したいのだが…。

CGRectを例にとってみると。

NSLog(@"xxxView.frame:%f, %f, %f, %f",
          xxxView.frame.origin.x,
          xxxView.frame.origin.y,
          xxxView.frame.size.width,
          xxxView.frame.size.height);

なんてやってたら、
面倒で仕方ない。

そんなとき、
NSStringFromCGRectというマクロがあって、
それを使うと…。

NSLog(@"xxxView.frame:%@", NSStringFromCGRect(xxxView.frame));

非常に簡潔に書ける!
こうすると、
xxxView.frame:{{0, 20}, {320, 548}}
といったログがコンソールに出る。
これは、
{{x, y}, {width, height}}
の形式。

CGPointやCGSizeも同じく、
NSStringFromCGPoint
NSStringFromCGSize
といったマクロが用意されている。

もっと出力を分かりやすくするなら、
CGRectCreateDictionaryRepresentationを使って、

NSLog(@"xxxView.frame:%@", CGRectCreateDictionaryRepresentation(xxxView.frame));

なんて書くと、

xxxView.frame:{
    Height = 548
    Width = 320;
    X = 0;
    Y = 20;
}
と、
ログが出ます。
heightとかWidthとか出てくれるんで一見分かりやすいですが…。
CGRectMakeするときなんかもそうなんですが、
一般にX, Y, Width, Heightの順番で捉えているので、
違和感はありますね。

こちらも同じく、
CGPointやCGSizeには、
CGPointCreateDictionaryRepresentation
CGSizeCreateDictionaryRepresentation
が用意されています。

個人的には、
上記のような座標やサイズのログはデバッグ用の使い捨てなので、
NSStringFromCGRectなんかでサクッとやっちゃうのが楽かと☆
小数できちんと確認したいなら自前で書くしかないですが、
そういうシーンにはほとんど出くわしたことない。

ちなみに、
NSStringFromXXXといったマクロには、

NSStringFromCGAffineTransform
NSStringFromCGPoint
NSStringFromCGRect
NSStringFromCGSize
NSStringFromClass
NSStringFromProtocol
NSStringFromRange
NSStringFromSelector
NSStringFromUIEdgeInsets

が用意されていて、
XXXFromStringで逆変換が出来ます。
使ったこともないですがw

ClassやProtocol, Selectorは、
変換と逆変換を巧みに使うことで、
リフレクションみたいなことが実現出来るかもしれませんが。


それでは。
ちゃお☆


まこぴー。

[iOS][Objective-C]enumの注意点


どうも。

enum自体は、
C言語から脈々と受け継がれている仕組みで、
細かく語る必要もないんですが…。

のハズが、
軽くハマった((((;゚Д゚)))))))

何でハマったかというと、
「値を指定しない場合は直前の列挙からのインクリメントとなる」
という、
至極当たり前のことw

enumというのは、
値を指定せずに、
シーケンシャルに列挙することができるワケで、
その特性を活かしているときは構わないのですが、
defineの集合体のように、
値を定義しまくって使うこともできる。

とある仕組みで、
モードをenumで列挙して定義しておいたんですが。
これは正の数で、
シーケンシャルな定義。
ただし、
0はモードなしという定義、
最終の列挙は終端という定義をする。

モードが、
0 < x < 終端
であれば、
モードに応じた処理を実行する。

という仕掛けにしておいた。

で、
これを参考に、
同じようなもので、
エラー処理をハンドリングするようなものを作ってね。
とお願いしたところ…。

エラー処理なので、
「モードはマイナス値の定義であるべき」
という一般的なポリシーは守ってくれたんですが。

飛び番号のマイナス値をenumで、
列挙が進むにつれて、
値が小さくなっていくように書かれていた。
で、
エラー定義上最小の値の次に、
終端を値の指定なしに列挙した。

そうすると、
エラー定義上最小の値と終端の値がかぶってしまうため、
モードが有効範囲内にあるか?
という判断をした時に、
エラー定義上最小の値は無効なモードとして認識されてしまう。
といったことがあって、
「同じように作ったのに、どうしても意図した通りに動きません」
という報告を受けて、
結局自分が手直しするハメになりました…。

実際には、
enumの列挙的な問題以前に、
モードが有効か無効かの条件文自体が、
まず間違っていましたが(´・ω・`)


それでは。
ちゃお☆


まこぴー。

2014年2月24日月曜日

[iOS][Objective-C]iOS上でのintとlongの最大値は同じ



どうも。

iOS上でのlongは32bitしか表現できないので、
事実上intと同じです。

INT_MAX:      2147483647
LONG_MAX:  2147483647

となります。
一般的に言うlongの範囲を使いたいなら、
long long
を使わなければなりません。

これは、
処理系が32bitだからということだと思いますが、
少なくとも32bitと64bitの処理系が混在している間には、
注意が必要。

サーバ連携なんかすると、
long値も受け取らなければならない可能性があるので、
そんな場合には、
iOSアプリ側ではlong longで受け取らなければならない。

まぁ、
64bitの端末以外なくなれば、
long longではなく、
普通にlongがlongとして使えるかもしれませんが。


それでは。
ちゃお☆


まこぴー。

2014年2月21日金曜日

[iOS][Objective-C]数値を3桁カンマ区切りのフォーマットにする


どうも。

数値をカンマ区切りで出力する。
というのもちょいちょいありそうなこと。

例えば、
123456789を、
123,456,789と表示してやる。

まぁ、
理屈は分かっていて、
後ろから3桁に区切りながらカンマを挿入してやればいいですが。
こういうのは理屈が分かっていても、
実際に実装するのはめんどくさいw

で、
便利なのがあるんですわ。

NSNumber *number = [NSNumber numberWithInt:123456789];

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setPositiveFormat:@",###"];

NSString *numberStr = [formatter stringForObjectValue:number];

NSLog(@"%@", numberStr);

などと書くと、
コンソールログには、
123,456,789と表示される。

こっちでもいい。

NSNumber *number = [NSNumber numberWithInt:123456789];

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];

NSString *numberStr = [formatter stringFromNumber:number];

NSLog(@"%@", numberStr);

やりやすい方を使えばいいけど、
見た目に理解しやすいのは後者かな。

日付表示のNSDateFormatterなんてのもあって、
日付も出力形式がシーンによって違ったりするので、
併せて頭の片隅に置いといて、
数値や日付の表示に関してはFormatterを使えば楽。

どういう設定にしたらどうフォーマットされるか?
といったようなことは、
Appleのリファレンスや書籍やネット上の情報で調べがつきます。

本エントリに限ったことではないが、
このブログでの覚え書きというのは、
自分自身が分かればいい尺度だったり、
自分自身の考え方の整理として書いているので、
懇切丁寧なリファレンスではありませんw

極端に言えば、
調べるためのキーワードやスニペットの集合体でしかありません。


それでは。
ちゃお☆


まこぴー。

2014年2月16日日曜日

[iOS][Objective-C]文字列が空白文字のみかどうかの判定


どうも。

ユーザが何かしらの入力をした時に、
空白や改行のみの文字列を許容したくない。

というようなことも、
時々あるので、
やはりutilのようなクラスに持っておきたい。

+ (BOOL)isTextWhiteSpaceOnly:(NSString *)text {
 
    BOOL ret = NO;
 
    if (text == nil || [text isEqualToString:@""]) {
     
        ret = YES;
     
    } else {
     
        NSString *newText = [text stringByTrimmingCharactersInSet:
                   [NSCharacterSet whitespaceAndNewlineCharacterSet]];
     
        if ([newText length] == 0) {
         
            ret = YES;
        }
    }
 
    return ret;
}

仕組みは単純で、
空白文字をトリミングしていって、
文字列長が0なら、
空白しかないという判断です。


それでは。
ちゃお☆


まこぴー。