ラベル objective-c の投稿を表示しています。 すべての投稿を表示
ラベル objective-c の投稿を表示しています。 すべての投稿を表示

2017年9月26日火曜日

Xcode8のままiOS11ビルドをする

どうも俺です。


iOS11がリリースされ、それに合わせてXcode v9もリリースされました。
Xcodeをバージョンアップするためには、
先にMacOSをバージョンアップする必要があって...いつもながら超面倒です。

Xcode8のままだとiOS11用にビルドできない...とお思いのみなさん、
裏技を使ってそのままiOS11ビルドできます。

※動作保証はできませんので、自己責任で行って下さい。


(1) Xcode 9をダウンロードする
https://developer.apple.com/download/more/
からXcode 9をダウンロードし解凍します。
結構時間かかります。

(2) Xcode 9.appを適当なディレクトリへおく
/Application/Xcode-9.app などへ移動させましょう。

(3) Xcode 8を適当な名前にリネームしておく。
仮に、Xcode-8.app としておきましょう。

(4) Xcode 8.appにXcode 9.app内にあるiOS11ディスクイメージのシンボリックを貼る

$ sudo ln -s /Applications/Xcode-9.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/11.0\ \(15A5278f\) /Applications/Xcode-8.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/11.0

(5) Xcode 8.appを再起動
再起動したら準備OK。iOS11端末をつなげてビルドできます。




ここに答えが。
Use Xcode 8 with iOS 11 | stack overflow



以上でぇぇぇぇす。

2017年8月8日火曜日

linker command failed with exit code 1が出た時の解決方法案

どうも、俺です。

Xcodeで開発してて目にすると1時間はその解決に費やしてしまうこともある地獄のエラー文言。
そう、それが
linker command failed with exit code 1


※Xcode 8.3.3

今日はこのエラーが出た時「これやったら直った」というあらゆる手段をお伝えします。


■プロビジョニングプロファイルは正しく設定されているか

人により設定方法は様々ですが、僕の場合、
・General>SigningにあるAutomatically manage signingのチェックをはずす

・Build Settings内、
 ProvisioningProfile (Deprecated)とProvisioning Profileの2箇所に適切なプロビジョニングプロファイルを設定。
 Code Signing Identityに正しい証明書を設定。
 最後にDevelopment Teamにチーム名を設定。

としています。
このミスの際は、ちゃんと「プロビジョニングプロファイル間違えているよ」というエラーが出ることが多い...。


■Enable Bitcodeの設定を間違えていないか

Build Settings>Build Options内にあるEnable Bitcodeが「Yes」になっているとアカン場合がある。
組み込んだフレームワークがbitcodeとやらに対応していないかもしれないので、速攻「No」に設定し直す。




■存在しないリソース(画像etc)を取り入れようとしていないか

Build Phases>Copy Bundle Resources内に存在しないリソースがないか要チェック。
無いものが含まれている場合は削除します。



■Deployment Targetを間違えていないか

General>Deployment InfoにあるDeployment Targetに無茶な値を入れていないか。
古すぎるターゲットなどに設定している場合は、よく考えて適切な値にします。



■Info.plistの設定Target Membershipは大丈夫か

Info.plistを選択してTarget Membershipにチェックが入っているとアウト。
チェックを外します。



■Xcodeを再起動しよう

この方法を使うと90%の確率でビルドが成功します。
試してみる価値あり。



■Mac本体ごと再起動しよう

この方法を使うと50%の確率でビルドが成功します。
試してみる価値あり。




上記以外でこのエラーの対処方法があれば教えてください。


※※※2017.08.18追記
このエラーが出た時に詳細ログが見れないのでモヤモヤしてたのですが、ログを見る方法が分かりました!
command+8 !!
LogNavigatorというやつらしいです!
これで具体的にエラー内容確認できます!


以上でぇぇぇぇす。

2015年1月8日木曜日

64bit(arm64)対応するとJSONKitがエラー[iOSアプリ]

どうも、俺です。

Xcode開発でアーキテクチャにarm64を追加すると
組み込んだ3rdパーティ製ツールのJSONKitからエラーが出てビルドできません。



githubを見ると、JSONKitはかなり長い間更新が止まっており
arm64アーキテクチャに対応していないようです。

この場合は、
該当のエラーが出ている箇所にマウスポインタを併せてクリックし、
エラーを修正してしまえば対応できます。


エラー箇所は2箇所あります。


または、
元のJSONKitをフォークして64bit対応したソースが公開されているので、
そちらでも対応出来ると思います。(※未確認)


以上でぇぇぇぇぇぇす。

2013年6月21日金曜日

c++で親クラスのコンストラクタを呼び出す

どうも、俺@家です。

C++でとある基底(親)クラスから派生した子クラスのコンストラクタ内で
親クラスのコンストラクタを呼び出したい場合は、

// 親クラス
class Parent
{
public:
  // 親クラスのコンストラクタ
  Parent()
  {
  };
};


// 子クラス
class Child: public Parent
{
public:
  // 子クラスのコンストラクタ
  Child(): Parent()
  {
  };
};
と書きます。
簡単ですね。

ちなみに子クラスから親クラスの何らかのメソッドを呼び出したい場合は、
// 親クラス
class Parent
{
public:
  // methodParent()の定義
  void methodParent()
  {
  };
};

// 子クラス
class Child: public Parent
{
public:
  void Child::methodChild()
  {
    // 親クラスのmethodParent()を呼びたい
    Parent::methodParent();
  };
};
のように、親クラス名::メソッド名と書きます。
※楽して上のように書きましたが、
 関数の重複定義でコンパイルエラーになる場合があるので、
 宣言する.hファイルと関数の内容を書く.cppファイルとは別けて書きましょう。


ちなみにphpだと
// 親クラス
public class Parent
{
  public function __construct()
  {
  }
}

// 子クラス
public class Child extends Parent
{
  public function __construct()
  {
    parent::__construct();
  }
}
のように、parent::メソッド()のように書きます。


ちなみにObjective-Cはコンストラクタがなくてイニシャライザになりますが、同様に
// 親クラスの.mファイル
@implementation Parent
- (id)init
{
  if ((self = [super init])) {
  }
  return self;
}
@end


// 子クラス .hファイル
@interface Child: Parent
- (id)init;
@end

// 子クラス .mファイル
@implementation Child
- (id)init
{
  if ((self = [super init])) {
  }
  return self;
}
@end
のようにsuperを使って書きます。
そもそもParentクラスも少なくともNSObjectを継承するのでsuperが出てきちゃいますね。


以上でぇぇぇぇえす。

2013年6月4日火曜日

[C++] xcodeで "In-class initializer for static data member of type 'const float' is a GNU extension" というWarningが出る

どうも、俺@仕事中です。
ずっとcocos2d-x(c++)を使っての開発中です。

タイトルの件ですが、Xcodeでビルドしたときに
In-class initializer for static data member of type 'const float' is a GNU extension
というwarningが出て 「なんか気持ち悪いなぁ」となる人へ。

これは直訳すると、
「const floatというというstaticなメンバ変数の初期化をするのはGNU拡張だよ」という意味です。
簡単に言うと、
「const floatは普通は使えへんで、せやけどGNU拡張機能でコンパイルできるようになっとるんやで、感謝せえよアホ」
という意味です。

C++(C言語も?)では
const float
という宣言は仕様で禁止されているそうです。

このワーニングが出る場合、動作に問題はありませんが一応直し方を教えてくれている天才がいました。
にゃんにょんProgramTips

ヘッダファイル(HogeClass.h)
// 宣言
public:
  static const float HOGE_FLOAT;
としておき、
定義ファイル(HogeClass.cpp)
#include "hoge.h"
const float HogeClass::HOGE_FLOAT = 10.0f;

// 以下メソッドの定義等
すばらしい!

まぁ、
#define HOGE_FLOAT 10.0
とかでも良いんでしょうけどね。型が分かりにくいですしね。

以上でぇぇぇぇぇす。

2013年5月24日金曜日

dyld: Symbol not found: _OBJC_CLASS_$_NSJSONSerializationでハマった

どうも、俺@帰宅です。

iOSアプリ開発しててハマったのでφ(..)メモメモ

iOSv5.0以上だと問題なく動作するのに、
iOS4.3端末だとビルドは通りますが実行するとタイトルにもある
dyld: Symbol not found: _OBJC_CLASS_$_NSJSONSerialization
Expected in: /System/Library/Frameworks/Foundation.framework/Foundation
のエラーが出ちゃいました。

あ、先に解決方法から書いちゃいますが、
僕の場合はFacebook-SDKのv3.5.1を使っていた為、iOS4.xサポート外だったというオチです。。
チェンジログをちゃんと読め、ということですね。
なのでここから、iOSv4をサポートしているFacebook-SDKのv3.2.1を落としてきて対応しました。

で、本題の
dyld: Symbol not found: _OBJC_CLASS_$_NSJSONSerialization
ですが、これはそのまま
「NSJSONSerializationが見つからねぇよ!」というエラーです。
なぜiOSv4.3端末で見つからねぇかと言うと、NSJSONSerializatoinクラスはiOSv5以上で利用可能だからです。
参考:Apple開発者サイト

でも、deployment targetを4.3に指定してるとどうしてもiOSv4.3で動作させなければいけません。

もう一つ手がかりとなるのが、Xcode上に表示される
dyld`dyld_fatal_error:
0x2fe01080: trap 
0x2fe01084: mov r0, r0
のようなエラーです。
これはiOSv5.0以上に対応しているが、
それ未満には対応していないframeworkを呼び出したりしている場合に出やすいエラーです。
まさに今回の現象にズバリマッチしています。

そういう場合は読み込んだframeworkを "Optional" にするなどして対応出来るのですが、
今回の僕の場合はどのframeworkもOptionalに出来ませんでした。
で、組み込んだframeworkやSDKを1つずつチェックしてて、
Facebook-SDKのChangelogに気付いたという事でした。

もしFacebook-SDK(iOS)を利用してて同様のエラーに遭遇した人は確認してみてください。


以上でぇぇぇぇえす。

2013年4月8日月曜日

c++ 整数同士で割り算(除算)を行うと結果がゼロになっちゃう

どうも、俺@仕事中です。

C言語またはc++で整数同士の割り算を行った場合、
int a = 3;
int b = 5;
float z = a/b;
変数zの結果が0(ゼロ)になっちゃう!

こういう場合は

float z = (float)a/b;

のように、変数a(または変数b どちらでも)をfloat型またはdouble型でキャストしてやればOKです。
なぜなら、a/bはこの場合3/5 = 0...3 という結果になり、それがint同士で除算しているため
コンパイラに答えが0(ゼロ)と判断されてしまうからです。

以上でぇぇえぇっぇす。

2013年2月26日火曜日

UITableViewで「次を表示..」ページングを実装

どうも、俺@家です。

 今日はUITableView(UITableViewControllerでも同じだけど)を使ってデータを一覧表示させたときに、 「次を表示...」というセルを用意してページングを行う方法をφ(..)メモメモ

 UITableViewに表示させるデータ(dataSource)は、
NSMutableArray *datas
に保持しているものとします。
 まずページング用のセルを表示するための準備。
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section                                 
{
  // データ件数
  int count = [datas count];

  // 初期
  if (count==0) {
    return 0;

  // _offsetCountは現在の画面に表示中のデータ件数
  // 「次を表示」を出すためにcount+1件だけセル数を返すようにする
  } else if (count==_offsetCount) {
    return count+1;
  }

  // 通常はデータ件数を返す
  return count;
}
これで、保持しているデータ+1件目に「次を表示」のセルを出す事ができます。
 次に実際にセルを作ります。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  // まずデータを持った通常のセル
  if (indexPath.row!=[datas count]) {
    // Cell再利用のためのIdentifier
    static NSString *CellIdentifier = @"NormalCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell==nil) {
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseidentifier:CellIdentifier] autorelease];
    }
    // 処理の必要があれば処理を書く
    return cell;

  // ページング用「次を表示」のセル(index.row==[datas count])
  } else {
    static NSString *CellIdentifier = @"NextCell";
    UITableViewCell *nextcell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (nextcell==nil) {
      nextcell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
    nextcell.textLabel.text = @"次を表示";
    return nextcell;
  }
}
最後にタップされたときに次のデータを読み込ませます。
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  // 選択状態解除
  [tableView deselectRowAtIndexPath:indexPath animated:YES];

  // 通常のセルが押された
  if (indexPath.row!=_offsetCount) {
    // ページ遷移などの処理
    XXViewController *viewController = [[XXViewController alloc] initWithNibName:nil bundle:nil];
    [self.navigationController pushViewController:viewController animated:YES];
    [viewController release];

  // 「次を表示」が押された
  } else {
    // 次を読み込ませるメソッド(サーバと通信するなど)
    [self loadNextData];

    // UITableViewを更新させる
    [tableView reloadData];
  }
}
これでざっとページングの完成です!
 よくある実装として、通常のセルと「次を表示」のセルとでサイズが異なる場合などは少しコツが必要なので、
 それはまた今度書きます。

 以上でぇぇえぇぇす。

2013年2月16日土曜日

UINavigationBarのナビゲーションバーに背景画像を設定する

どうも、俺@休み中です。
最近はずっとiOSアプリ開発に勤しんでいます。 

UINavigationBarを使って、ヘッダー部分(ナビゲーションバー)に画像を設定したい場合。 QiitaのUINavigationBarの背景画像を設定する方法を参考にさせてもらいました。
#import <QuartzCore/QuartzCore.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIViewController *viewController = [[UIViewController alloc] initWithNibName:nil bundle:nil];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:vc];

    // OSのバージョン取得
    float osVersion = [[[UIDevice currentDevice] systemVersion] floatValue];

    // iOS5未満
    if (osVersion < 5.0f) {
        UIImageView *imageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"background.png"] stretchableImageWithLeftCapWidth:0 topCapHeight:1]];
        navBg.layer.zPosition = -FLT_MAX;
        [navigationController.navigationBar insertSubView:imageView atIndex:0];

    // iOS5以上
    } else if (osVersion >= 5.0f) {
        navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"background.png"] forBarMetrics:UIBarMetricsDefault];
    }
}
です。
 iOS5以上から
- (void)setBackgroundImage:(UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics

というメソッドが加わったため、iOS5未満の方法で同じように実装しても思ったように表示されません。

以上でぇぇぇえぇぇぇす。

2013年1月21日月曜日

Objective-Cでファイルのタイムスタンプや属性を取得

どうも、俺@家です。
21日ですが、明けましておめでとうございます。
今年もよろしくお願いしマウス。チューチュー。

NSFileManagerでとあるディレクトリ以下にあるファイルを一覧で取得し、作成された日付順に並び替えたい場合。
// Cacheディレクトリ

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];


// ファイル一覧(サブディレクトリは除外)
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];


// 属性情報を格納する配列
NSMutableArray *attributes = [NSMutableArray array];


// ファイル配列をループ
for (NSString *file in files) {

  // ファイル属性にファイルパスを追加するためにDictionaryを用意しておく
  NSMutableDictionary *tmpDictionary = [NSMutableDictionary dictionary];

  NSString *filepath = [path stringByAppendingPathComponent:file];
   // ファイル情報(属性)を取得
   // 取得できる情報については csoulsの日記 を参考に!
  NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:filepath error:nil];


  // tmp配列に属性を格納
  [tmpDictionary setDictionary:attr];

  // tmp配列にファイルパスを格納
  [tmpDictionary setObject:filepath forKey:@"FilePath"];

  [attributes addObject:tmpDictionary];
}


// ソートさせる
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:NSFileCreationDate ascending:NO];
NSArray *sortarray = [NSArray arrayWithObject:sortDescriptor];

// 並び替えられたファイル配列
NSArray *resultarray = [attributes sortedArrayUsingDescriptors:sortarray];

[sortDescriptor release];


なぜわざわざtmpDictionaryを用意するか?についてですが、
属性を取得する
  NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:filepath error:nil];
は、NSDictionaryなためファイルパスが追加できないためです。


以上でぇぇぇえぇす。

2012年12月16日日曜日

NSDictionaryの特定のキーでソートしちゃる

どうも、俺@まだ勉強中です。

例えばNSArray(またはNSMutableArray)内にNSDictionaryが複数入っているとして、そのNSArrayをNSDictionaryのとあるキーでソートしたい場合。

NSArray *array = [NSArray arrayWithObjects:@[
        @{@"id":@100, @"name":@"bob"},
        @{@"id":@14,  @"name":@"ken"},
        @{@"id":@531, @"name":@"john"},
        @{@"id":@32,  @"name":@"mike"},
        @{@"id":@65,  @"name":@"kozy"},
        @{@"id":@174, @"name":@"shorn"},
        @{@"id":@7,   @"name":@"scott"},
        @{@"id":@865, @"name":@"mikeal"},
        @{@"id":@31,  @"name":@"fun"}
    ]];
このarrayを格納しているNSDictionaryのキー:idでソートしちゃりたい!場合は、
// NSSortDescriptorを生成して
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"id" ascending:YES];
// 配列に入れておいて
NSArray *sortarray = [NSArray arrayWithObject:sortDescriptor];
// ソートしちゃる!
NSArray *resultarray = [array sortedArrayUsingDescriptors:sortarray];
// 不要なオブジェクトは解放しちゃる
[sortDescriptor release];
これでキー:idの値でソートされます。


以上でぇぇえぇぇぇえぇす。

Objective-Cの高速列挙は順番保証されない

どうも、俺@勉強中です。

Objective-Cの高速列挙(for ... in ...)は配列全体を走査するのにとても便利なんだけど、取得される順番が保証されません。
# NSDictonaryのキーの順番通りに値を取り出したい!とする。
NSDictionary *dictionary = @{
    @"1": @{@"name":@"bob"},
    @"2": @{@"name":@"ken"},
    @"3": @{@"name":@"john"},
    @"4": @{@"name":@"mike"},
    @"5": @{@"name":@"kozy"},
    @"6": @{@"name":@"shorn"},
    @"7": @{@"name":@"scott"},
    @"8": @{@"name":@"mikeal"},
    @"9": @{@"name":@"fun"}
};

for (id key in dictionary) {
    NSLog(@"%@", key); // 1,2,3,...と出てくれない
}
順番通りに取り出したい時困る。
なので回避策としてNSArray#sortedArrayUsingComparatorを使う方法があります。
// 一旦NSDictionaryのキーを取得して
NSArray *keys = [dictionary allKeys];

// sortedArrayUsingComparatorを使ってキーをソート
keys = [keys sortedArrayUsingComparator:^(id o1, id o2) {
    return [o1 compare:o2];
}];

// 確認すると順番通りそ
NSLog(@"%@", keys);
NSMutableArray *array = [NSMutableArray array];

// あとは順番通りに値を取得する
for (id key in keys) {
    [array addObject:[dictionary objectForKey:key]];
}

// 完璧!
NSLog(@"%@", array);
これで最初のNSDictionaryのキー順番通りに値を取得できます

以上でぇぇえぇぇす。

2012年12月14日金曜日

NSStringのstringWithFormatで小数点以下をええ感じにする

どうも、俺@昼休みです。
 ここ数日はブログへのPOSTが多いですね。開発してると小ネタをよく見つけます。

今日はObjective-CのNSString#stringWithFormatメソッドについてです。
 例えば、100を文字列で表す場合は
[NSString stringWithFormat:@"%d", 100];
ですね。 0.1を表す場合は
[NSString stringWithFormat:@"%.1f", 0.1f];
です。 0.12345の場合は
[NSString stringWithFormat:@"%.5f", 0.12345f];
です。 小数点以下の桁数が決まっている場合は良いのですが、上の0.12345のフォーマットで0.1を表したい場合は、
[NSString stringWithFormat:@"%.5f", 0.1f];
// 結果は0.10000
となってしまいます。 出来れば0.1と表示させたい!という場合に便利なフォーマットは、
[NSString stringWithFormat:@"%g", 0.1f];
// 結果は0.1
[NSString stringWithFormat:@"%g", 0.12345f];
// 結果は0.12345
になります。

 以上でぇぇぇえぇす。

2012年12月13日木曜日

Retinaディスプレイかどうかの判別

どうも、俺@コーディング中です。
ギークな感じでいいでしょう。

Objective-CでRetinaディスプレイかどうかを判別する方法をφ(..)メモメモ。

[UIScreen mainScreen].scale
の値が
1.0なら非Retina
2.0ならRetina
です。

ちなみにOSのバージョンの取得方法は、
[UIDevice currentDevice] systemVersion]
で、
例えば「iOS5.0以上」というような条件分岐をする場合、
float version = [[[UIDevice currentDevice] systemVersion] floatValue];

if (version >= 5.0f) {
  // 5.0以上の処理
}
というように書けます!

以上でぇぇぇぇぇす。

2012年8月7日火曜日

UIViewをcocos2dに配置する

どうも、俺@残業中です。
久々のエントリー。仕事サボってた訳ではございません^^;バタバタしてました!


今日はUIViewをcocos2dで利用する場合のメモメモです。

UIView *myView = [UIView alloc] init];
[[[CCDirector sharedDirector] openGLView] addSubView:myView];
簡単ですね。

cocos2dでゲームアプリを開発しながら、UIViewなオブジェクトを貼り付けたいとき!はこの方法でOK!


以上でぇぇぇえぇす!


2012年5月11日金曜日

Objective-Cで構造体をNSLogする

どうも、俺@仕事中です。
Objective-Cで構造体をNSLogしたり、NSArrayやNSDictionaryで扱いたい場合、
例えばcocos2dのccColor3Bを

ccColor3B color = {0,0,0};
NSLog(@"%@", color);

//または
[dictionary setObject:color forKey:@"colorKey"];
のようにしてもEXC_BAD_ACCESSでエラーになります。

ここはひとつNSValueを使いましょう。
NSValue *colorValue = [NSValue valueWithBytes:&color objCType:@encode(GLubyte)];
これでインスタンス化出来る。

配列へ格納したあとの取り出しは
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:colorValue, @"key", nil];
ccColor3B color;
[(NSValue *)[dictoinary objectForKey:@"key"] getValue:&color];
とします。


以上でぇえぇぇす。

Objective-Cで 'Cannot find interface declaration for 'XXXXClass', superclass of 'ZZZZClass'...'が出る

どうも、俺@昼休みです。
タイトルの通り、Xcodeでコンパイルすると
「Cannot find interface declaration for 'XXXXClass', superclass of 'ZZZZClass'...」のエラーが出て困っていた俺へのめも。
何が起こってるかというと、簡単に言うと循環参照。

Objective-Cのクラスの前方宣言がないと困ること - アールケー開発に同じような内容の詳しい解説があります。

ClassA
#import "ClassB.h"
@interface ClassA: NSObject
{
  ClassB *b;
}
@end

ClassB
#import "ClassA.h"
@interface ClassB: NSObject
{
  ClassA *a;
}
@end
ClassAとClassBがそれぞれのヘッダファイルでお互いをimportしていて、
かつどちらかでどちらかのインスタンス変数を持っている、
またはどちらかのクラスを継承している、
ような場合にタイトルのような問題が発生します。
※コンソールからgccとかで直接コンパイルした場合は、

In file included from ClassA.h:2,
                 from ClassA.m:1:
ClassB.h:6: error: expected specifier-qualifier-list before ‘ClassA'
のようなエラーが出ます。困ったもんだ。

問題の詳細はコンパイルの処理にあり、
■ClassA.hのコンパイル
ClassA.h内で#import "ClassB.h"が見つかった時点でClassB.hをインポート(解析)する。
ClassB.h内にある#import "ClassA.h"が見つかりClassA.hをインポート(解析)するはずだが、#import宣言はネストしながらインポートしないため、実際はClassA.hのインポートは行われない。
ClassB.h内のClassAインスタンス変数の定義(ClassA *a;)で、ClassAの宣言が見つからないのでエラーとなる。
、、、そうです。

こういうような場合、#import宣言をやめて@classを使い前方宣言で対処できるのであればそれで解決できます。

ClassA
@class ClassB;
@interface ClassA: NSObject
{
  ClassB *b;
}
@end

ClassB
@class ClassA;
@interface ClassB: NSObject
{
  ClassA *a;
}
@end


ただし、対象のクラスを継承していたり、処理内で対象のクラスをallocしたりしてる場合は#importを使わなければいけません。


以上でェェエェす。


2012年5月10日木曜日

objective-cのクラスでプロパティをprivateに

どうも、俺@残業中です。
今日はObjective-Cのクラスで、プロパティ(@property)宣言した値をprivateのように扱う方法めもです。

MyClass.h
@interface MyClass {

  // インスタンス変数
  NSString *string;
}
@end

MyClass.m
#import "MyClass.h"
// 無名カテゴリ宣言
@interface MyClass()
@property (nonatomic, retain) NSString *string;
@end

@implementation MyClass
@synthesize string;
@end
これでオッケー。

ヘッダファイルにプロパティを書いてしまうと、外部からそのプロパティにアクセス出来てしまうことが用意に想像できてしまい、意識せずに外部からそのプロパティへアクセスしてしまいがちです。
プログラマが一人であったり、小規模なプロジェクトであれば問題ありませんが、「このインスタンス変数は外部からアクセスしちゃあかんでぇ」と意識させたい場合などにはこの方法が便利です。

ただし、Objective-Cの特性で外部からのアクセスを完全に遮断することは出来ず、やろうと思えばアクセス出来ちゃいますが、こればかりはどうしようもありません。あしからず。


以上でぇぇぇぇえぇす。

2012年3月23日金曜日

CGRect、CGSize、CGPointについて

どうも、俺@絶賛開発中です。
今日は(も)Objective-Cについてです。最近はアプリ(iOS/Android)関連の仕事が多いですね。

Objective-Cで開発してるとよくCGRectやCGPoint、CGSizeなどといったクラス(構造体)を使います。
これについてメモメモ。
ゲームアプリなど座標を細かく管理する開発では必須の知識です。

■CGRect
オブジェクトの座標とサイズを管理します。
// 座標(100, 100)の位置に横50 x 縦50のサイズを表すCGRect
CGRect rect = CGRectMake(100, 100, 50, 50);


■CGPoint
オブジェクトの座標を管理します。
//座標(200, 100)の位置を表すCGPoint
CGPoint point = CGPointMake(200, 100);


■CGSize
オブジェクトのサイズを管理します。
// 横200 x 縦100のサイズを表すCGSize
CGSize size = CGSizeMake(200, 100);


またこれらをコンソール上にログ出力する場合は、

CGRect rect = CGRectMake(0, 0, 30, 30);
NSLog(@"%@", rect);
のようにしても出力されません。

座標情報やサイズをログ出力させるには、
NSStringFromCGRect()
NSStringFromCGPoint()
NSStringFromCGSize()
これらを使います。

CGPoint point = CGPointMake(100, 200);
NSLog(@"%@", NSStringFromPoint(point));


以上でぇぇぇえぇす。

2012年3月13日火曜日

NSStringのstringByAppendingPathComponent注意

どうも、俺@仕事中です。
Objective-Cでゲームとか開発してると、ユーザのちょっとしたデータをファイルに保存したりしますよね。

その時のコードでエラー起こしてしまったので、対処法をメモ。

間違いソースがこれです。

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex: 0];
self.filePath = [directory stringByAppendingPathComponent: @"hoge.plist"];
self.filePathは保存するファイルのパスを格納するNSStringなクラス変数です。

これだとstringByAppendingPathComponentメソッドは結果をautoreleaseしている(と思う)ので、
ココの処理を抜けた時点でself.filePathはリリースされてしまう(はず)。
別のメソッドでself.filePathを使おうとしても、リリースされたオブジェクトにアクセスしようとしてEXC_BAD_ACCESSになっちゃう。


NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex: 0];
self.filePath = [[NSString alloc] initWithString: [directory stringByAppendingPathComponent: @"hoge.plist"]];
にしてやれば、self.filePathを自身のdeallocメソッド内でreleaseすることが出来る!


以上でぇぇえぇす。