カテゴリー別アーカイブ: objective-c

NSStringからnull文字を削除

デバイス制御系の処理を書いているとデバイスのSDKから渡されたNSStringが変な動きをすることがあります。
stringWithFormatで処理しても途中で切れたり、NSMutableStringのappend系で追加しても切れたり。。
私が直面した現象の原因は単純で、null文字がNSStringに含まれていることなんですけど、なんつーかFucking!ですよね。

いくら舌打ちしたところでSDKを修正してもらえるわけではありませんので、対応を。

下記のコードでNSStringに含まれているnull文字を削除できます。

-(NSString*)trimNullString:(NSString*)val
{
  if (!val) return nil;

  NSInteger length = [val length];
  unichar buffer[length];
  [val getCharacters:buffer range:NSMakeRange(0, length)];

  int endpos = 0;
  for (int i=0; i<length; i++) {
    unichar c = buffer[i];
    if (c == 0x0){
      endpos = i;
      break;
    }
  }

  if (endpos > 0){
    NSString *result = [NSString stringWithCharacters:(const unichar *)buffer  length:(NSUInteger)endpos];
    return result;
  } else {
    return @"";
  }
}

iOS8で NSDecimalNumber が変な値を返してくる!(怒)

iOS8上でなぜか変な挙動を示すアプリがあったので原因を調査してみたところ、NSDecimalNumber の integerValue や longValue が変な値を返すようになっていた。

検証コード

NSDecimalNumber* n = [NSDecimalNumber decimalNumberWithString:@"123.123456789012345678"];
if ([n intValue] !=[n integerValue]){
    NSLog(@"What?!");
    NSLog(@"decimal %@", n);           
    NSLog(@"int %d", [n intValue]);
    NSLog(@"integer %ld",[n integerValue]);
    NSLog(@"long %ld", [n longValue]); 
    NSLog(@"longlong %lld", [n longLongValue]);
    NSLog(@"uinteger %ld",[n unsignedIntegerValue]);
 } else {
    NSLog(@"success!!");
 }

このコードを実行すると iOS7.1(64bit)では次のように素直な値が表示されます。

decimal 123.123456789012345678
int 123
long 123
longlong 123
integer 123
uinteger 123

でもこれがiOS8だと・・
64bit環境では
decimal 123.123456789012345678
int 123
integer -6
long -6
longlong -6
uinteger 12

32bit環境では
decimal 123.123456789012345678
int 123
integer 0
long 0
longlong -6
uinteger 0

って結果となる。なんでやねん!!

NSDecimalNumberを”123.123456″とかで初期化するとこの問題は発生しないんだよね。
桁数の問題であれば NSDecimalNumber を初期化する際にまるめておけば(有効桁数が十分なのであれば)解決するんだけど、どうだんんだろ?

なんにせよ、iOS8のバグだよね。きっと。

[iOS]Objective-cでBASE64変換 (iOS7以降)

以前こんな投稿をしていたんですけど、先日NSDataのメンバを探していたら ん! ってビックリしました。

どうやらiOS7では正式なBASE64変換がサポートされたようです。
NSDataの
 base64EncodedStringWithOptions
とか
 initWithBase64EncodedString
ね。
リファレンス

さらに、iOS4以上で使える(iOS7ではdeprecatedだけど)base64Encoding とか initWithBase64Encoding と言うメソッドも発見!
こんなのあったっけ??

iOS7.1からAdHoc配信が出来なくなった!

今までフツーに出来ていたアドホック&OTAでのアプリの配布(開発版ね)が急に出来なくなった。

「・・・の証明書が有効ではないため、Appをインストールできません。」

とか。

はぁ?
ちょっと焦りましたけど、どうやらiOS7.1からはHTTPSサイトのみでOTA配信が許されるようです。

配布サイトにSSL証明書(共有SSLでもOK)を設定して無事解決!

[objective-c] subviewのz-orderを入れ替える

UIViewの表示順(z-order とか z-index とか言われるヤツ)を入れ替える方法です。

表示順を変えるだけでしたら

#import “QuartzCore/QuartzCore.h”

subview1.layer.zPosition = 2;
subview2.layer.zPosition = 1;

のようにlayerのzPositionを入れ替えればOKです。
ただし、このzPositionは初期値で0ですので最初から表示順を指定していた場合には有効ですが、
後から変更する場合にはちょっと都合が悪い。

ですので、そういった場合にはsuperviewに追加されているsubviewのindexを直接変更した方が楽チンです。

で、どうするのかと言うと。
まず、入れ替えたいsubviewのindexを取得します。

UIView *superview = subview1.superview;
int idx1 = [superview.subviews indexOfObject:subview1];
int idx2 = [superview.subviews indexOfObject:subview2];

その上で入れ替えます。

[superview exchangeSubviewAtIndex: idx1 withSubviewAtIndex: idx2];

これでsubviewの表示順が入れ替わってくれますよ。

やっぱりバグだったのか。。。

XCode5にしてからなぜかデバッグ中に落ちる事が頻発してたんです。
今まで正常に動いてたプログラムも含めて落ちまくる!

デバッガを使わずに実行すると特にエラーもなく実行できていたのですが、なんとも気持ち悪い。
傾向としてはiOS7用の端末では落ちないけど、iOS6以前の端末では落ちる。
なんかフレームワーク的な部分でかわっちゃったんだろな〜(遠い目
と対応方法に苦慮していたのですが、アップデート画面でこんなものが。。。

xcodeupdate20131112

・Fixes a crash that could occur while debugging on devices running iOS6.

やっぱりね!

Xcode5でDeploymentTargetを変更する方法

Xcode5で新規プロジェクトを作るとDeploymentTargetが7.0になっています。
まぁそれはいいんですけど、さすがに7.0以降しか対応しないアプリはまだ時期尚早だと思うので、6.0とかに変更したいのですがプルダウンからは7.0しか選択出来ず。。。

どーしたらいいんだよ〜〜〜!
アップルをちょっと憎たらしく思いながら調べてみると、どうやら対象のアーキテクチャにarm64が入っている時は64bit対応の7.0以降しか選択できないようになっているみたいです。

Build SettingsのArchitecturesを「Standard architectures(armv7,armv7s)」に変更すると昔のOSをDeploymentTargetで選択できるようになりました。
よかったよかった。

NSNotificationCenterを使った通知のサンプル

オブジェクト間で通信、通知を行いたい事って頻繁にありますよね。
解決方法として色んなパターンがあると思いますが、cocoaフレームワークには大変便利な通知システムが用意されています。

使い方も簡単で、しかもオブジェクト間の関連性が薄いので大変使いやすいです。

通知は文字列の名称を使って識別されますので、ヘッダに定数を宣言しておきます。
通知を発行するクラスのヘッダに書くのが良いのではないでしょうか?

#define kDataManagerFinishLoading @"DataManagerFinishLoadingMsgKey"

通知を発行する側はこんな感じで簡単に通知できます。

// 引き渡しパラメータの作成
// Dictionaryでなんでも渡せます
NSDictionary* info = @{ 
  @"date":[NSDate date],
  @"title":@"目くじら", 
  @"count":[NSNumber numberWithInt:12] 
};

// 通知                    
[[NSNotificationCenter defaultCenter] 
    postNotificationName:kDataManagerFinishLoading  
    object:self 
    userInfo:info];

通知を受け取る側のコードはこんな感じです

最初に、NSNotificationCenter に通知の監視を登録します。
self.dataManagerってのが通知を発行するオブジェクトで、通知を受け取るオブジェクトがselfだとすると。

[[NSNotificationCenter defaultCenter] 
    addObserver:self 
    selector:@selector(handleDataManagerFinishLoading:) //←通知を受け取るセレクタ
    name:kDataManagerFinishLoading 
    object:self.dataManager];

受け取った時の処理を書いておきます

// kDataManagerFinishLoading の通知ハンドラ
-(void)handleDataManagerFinishLoading:(NSNotification *)aNotification
{
    //userInfoはDictionaryなので色んな情報を受け取る事ができます。   
    NSDate* date = [[aNotification userInfo] objectForKey:@"date"];
    NSString* title = [[aNotification userInfo] objectForKey:@"title"];
    NSNumber* count = [[aNotification userInfo] objectForKey:@"count"];
    
    //なにかしら処理

}

通知が不要になったら解除しておきます。
dealloc等に書いておくのがよいのではないでしょうか?

[[NSNotificationCenter defaultCenter] removeObserver:self];

以上

オブジェクトのプロパティを監視する

あるオブジェクトのプロパティ値が変更された時に何かしら処理を行いたい場合のサンプルです。

対象オブジェクト objA が title というプロパティを持っている場合を例とすると下記のような感じです。

objAにプロパティの変更通知を登録 (objAを生成したタイミングなどで)

// 
[objA addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];

通知はaddObserverで登録したオブジェクトの下記のメソッドが呼び出されます

// 監視対象が変更された時に呼び出されるコールバック
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object 
			change:(NSDictionary *)change 
		       context:(void *)context
{
    if ([keyPath isEqual:@"title"])
    {
	NSString* newTitle = [[change objectForKey:NSKeyValueChangeNewKey] stringValue];
        NSLog(@"newTitle :%@", newTitle);

        //変更された時の処理を書く

    }
}

通知が不要となったら登録を解除 (objAを廃棄する前に実行します)

//
[objA removeObserver:self forKeyPath:@"title" context:NULL];

cocoaフレームワーク、、便利過ぎる。。