@blog.justoneplanet.info

日々勉強

Objective-Cの配列とコレクション

型、云々でJavaに似ている感触だなぁと勝手に解釈。

■配列

以下のようにして配列を定義する。

NSString *str[3];
str[0] = @"hoge";
str[1] = @"fuga";
str[2] = @"piyo";
for(int i = 0; i < 3; i++){
    NSLog(@"%@", str[i]);
}

ちなみに、以下のようにして値がない要素にアクセスした場合、アプリケーションが終了してしまった。

NSString *str[3];
str[0] = @"hoge";
str[1] = @"fuga";
str[2] = @"piyo";
for(int i = 0; i < 4; i++){
    NSLog(@"%@", str[i]);
}

当然だけど固定長だよ。

■可変長配列

NSMutableArrayクラスを使用する。可変長配列に対するアプローチもJavaなんかと似てる感触。以下のようにして定義する。

NSMutableArray *ary = [NSMutableArray arrayWithObjects: @"hoge", @"fuga", @"piyo", nil];
for(int i = 0; i < [ary count]; i++){
    NSLog(@"%@", [ary objectAtIndex: (NSUInteger) i]);
}

また、以下のようにして要素を追加・削除することができる。

NSMutableArray *ary = [NSMutableArray arrayWithObjects: @"hoge", @"fuga", @"piyo", nil];
[ary addObject: @"baca"];
[ary removeObjectAtIndex: (NSUInteger) 0];
for(int i = 0; i < [ary count]; i++){
    NSLog(@"%@", [ary objectAtIndex: (NSUInteger) i]);
}

上述のように、要素数はcountで取得することができる。

■ディクショナリ

ハッシュって言っちゃダメなんだろうか。とりあえず以下のようにして定義する。

NSMutableDictionary *hash = [NSMutableDictionary dictionaryWithObjectsAndKeys:
    @"hoge", @"key1",
    @"fuga", @"key2",
    @"piyo", @"key3",
    nil
];
NSArray *keys = [hash allKeys];
for(int i = 0; i < [keys count]; i++){
    NSLog(@"%@", [hash objectForKey: [keys objectAtIndex:(NSUInteger)i]]);
}

要素の追加・削除は以下のように記述する。

NSMutableDictionary *hash = [NSMutableDictionary dictionaryWithObjectsAndKeys:
    @"hoge", @"key1",
    @"fuga", @"key2",
    @"piyo", @"key3",
    nil
];
[hash setObject: @"baca" forKey: @"key4"];
[hash removeObjectForKey: @"key1"];
NSArray *keys = [hash allKeys];
for(int i = 0; i < [keys count]; i++){
    NSLog(@"%@", [hash objectForKey: [keys objectAtIndex:(NSUInteger)i]]);
}

キーを後ろに記述する点には注意が必要だ。ちなみに、キーは日本語でも大丈夫なようだ。

Objective-Cの制御構造

まぁ、特に目新しいこともない。

■for

for(int i = 0; i < 10; i++){
    NSLog(@"%d", i * i);
}

書籍を参照すると、ループ変数が外で定義されてるのも見受けられる。

int i;
for(i = 0; i < 10; i++){
    NSLog(@"%d", i * i);
}

■while

int i = 0;
while(i < 100){
    NSLog(@"%d", i);
    i++;
}

breakを使用してループから抜け出すこともできる。

int i = 0;
while (YES) {
    NSLog(@"%d", i);
    i++;
    if(i > 100){
        break;
    }
}

また、continueを使用して処理をスキップすることもできる。

■if

int num = 5;
if(num == 3){
    NSLog(@"3です");
}
else if(num == 5){
    NSLog(@"5です");
}
else{
    NSLog(@"3でも5でもないです");
}

■switch

以下のように乱数を発生させて、switch文で分岐させることも可能だ。

srand(time(0));
int num = abs(arc4random()) % 4;
switch(num){
    case 0:
        NSLog(@"0です");
        break;
    case 1:
        NSLog(@"1です");
        break;
    case 2:
        NSLog(@"2です");
        break;
    case 3:
        NSLog(@"3です");
        break;
    default:
        NSLog(@"いくつだろうね");
        break;
}

乱数の取得以外は眠い。(。´-д-)。o○Zzz

GHUnitを導入する

■ダウンロード

gabriel / gh-unit

■導入

ターゲットの追加

  1. Project navigatorでProjectを選択し、Add Targetを選択
  2. Window Based ApplicationでProduct NameをTestsとして適当にターゲットを追加

frameworkの追加

Project navigator > Projectを選択 > Target > Testsを選択 > Build Phases > Link Binary With Libraries

ダウンロードしてできたframeworkを追加する。

Info.plist

Test/Supporting Files/Test-Info.plistを選択してMain nib file base nameの属性を削除する。

ファイルの削除

Test配下の以下のファイルを削除する。

  • TestAppDelegate.h
  • TestAppDelegate.m
  • MainWindow.xib
  • Supporting Files以外のディレクトリ

リンク

Project navigator > Projectを選択 > Target > Testsを選択 > Build Setting > Other Linker Flag

以下の記述を加えてください。

  • -ObjC
  • -all_load

Test/main.m

以下の部分を

int retVal = UIApplicationMain(argc, argv, nil, nil);

以下のように変更する。

int retVal = UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate");

■テストケース

Test配下にテストを配置する。但し、ターゲットはTestsにしてファイルを新しく追加する。

Test/MyTest.m

#import <GHUnitIOS/GHUnit.h>
@interface MyTest : GHTestCase {   
}
@end

@implementation MyTest
- (void)testStrings {
    NSString *string1 = @"a string";
    GHTestLog(@"I can log to the GHUnit test console: %@", string1);
    
    // Assert string1 is not NULL, with no custom error description
    GHAssertNotNULL(string1, nil);
    
    // Assert equal objects, add custom error description
    NSString *string2 = @"a string";
    GHAssertEqualObjects(string1, string2, @"A custom error message. string1 should be equal to: %@.", string2);
    GHAssertEquals(1, 1, @"test");
}

// わざとエラーを出してみる
- (void)testNums {
    GHAssertEquals(1, 2, @"test");
}
@end

Objective-Cの文字列

■定義

以下のようにして文字列を定義する。

NSString *str = [[NSString alloc] initWithString:@"hogehoge"];

但し、この場合は使い終わったらreleaseしなければならないので、以下のようにstringWithStringメソッドを用いるとautoreleaseされる。

NSString *str = [NSString stringWithString:@"hogehoge"];

以下のようにして、文字コードを指定することができる。

NSString *str = [NSString initWithCString:"にほんご" encoding:NSUTF8StringEncoding];

autoreleaseについて

autoreleaseはNSAutoreleasePoolによって実現されているが、テンプレートからプロジェクトを作った場合は、既にmain.mに以下のようなコードが記述されている。

int main(int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

■各種操作

結合

文字列を結合する場合、以下のようにstringByAppendingStringメソッドを使用する。

NSString *hoge = @"hogehoge";
NSString *fuga = @"fugafuga";
NSLog(@"%@", [hoge stringByAppendingString: fuga]);// hogehogefugafuga

以下のように、日本語でも使用することができる。

NSString *hoge = @"これは";
NSString *fuga = @"日本語です";
NSLog(@"%@", [hoge stringByAppendingString: fuga]);// これは日本語です

長さ

文字列の長さを取得する場合、以下のようにlengthメソッドを使用する。

NSString *hoge = @"hogehoge";
NSLog(@"%d", [hoge length]);// 8

目を疑ってしまったが、以下のようなマルチバイトの文字列でも正しく文字数をカウントする。

NSString *hoge = @"これは日本語です";
NSLog(@"%d", [hoge length]);// 8

置換

文字列を置換する場合、以下のようにstringByReplacingOccurrencesOfStringメソッドを使用する。

NSString *hoge = @"hogehoge";
NSLog([hoge stringByReplacingOccurrencesOfString: @"hoge" withString: @"piyo"]);// piyopiyo

以下のように、日本語でも使用することができる。

NSString *hoge = @"これは日本語です";
NSLog([hoge stringByReplacingOccurrencesOfString: @"です" withString: @"ですか"]);// これは日本語ですか

抽出

文字列を抽出する場合、以下のようにsubstringWithRangeメソッドを使用する。他にもsubstring系メソッドはある。

NSString *hoge = @"hogehoge";
NSLog(@"%@", [hoge substringWithRange: NSMakeRange(2, 4)]);// geho

以下のように、日本語でも使用することができる。

NSString *hoge = @"これは日本語です";
NSLog(@"%@", [hoge substringWithRange: NSMakeRange(2, 4)]);// は日本語

分割

文字列を分割する場合、以下のようにcomponentsSeparatedByStringメソッドを使用する。

NSString *hoge = @"hogehoge";
NSLog(@"%@", [[hoge componentsSeparatedByString: @"e"] description]);// hog, hog, ""

以下のように、日本語でも使用することができる。

NSString *hoge = @"これは日本語です";
NSLog(@"%@", [[hoge componentsSeparatedByString: @"語"] description]);// "\U3053\U308c\U306f\U65e5\U672c","\U3067\U3059"

比較

文字列を比較する場合、以下のようにisEqualToStringメソッドを使用する。

NSString *hoge = @"hogehoge";
NSString *fuga = @"fugafuga";
if([hoge isEqualToString:fuga]){
    NSLog(@"%@", @"equal");
}
else {
    NSLog(@"%@", @"not Equal");
}
// not Equal

以下のように、日本語でも使用することができる。

NSString *hoge = @"これは日本語です";
NSString *fuga = @"これは日本語ですß";
if([hoge isEqualToString:fuga]){
    NSLog(@"%@", @"equal");
}
else {
    NSLog(@"%@", @"not Equal");
}
// equal

Objective-Cのメモリ管理

iOSではガーベジコレクションが使用できないらしいので、メモリ管理をしなくちゃならん。JS、PHP、Java、Pythonを使ってきた自分的には煩わしいと思ってしまう部分ではある。

■autorelease

Dog *pochi = [[[Dog alloc] init: @"pochi"] autorelease];

■retainとrelease

内部的には参照カウンタを使用している。カウンタが0になり参照がなくなるとメモリが開放される。

retain

以下のようにretainを使用し、参照カウンタをインクリメントする。

Dog *pochi = [[Dog alloc] init: @"pochi"];
_tmp = [pochi retain];

release

以下のようにreleaseを使用し、参照カウンタをデクリメントする。

-(void)dealloc{
    [pochi release];
    [super dealloc];
}

■文字列について

@””で生成した文字列については、retainやreleaseは必要としない。

メモリリークについて

アプリケーションの終了時、deallocは処理されずに全てのメモリが開放される。但し、マルチタスク端末ではバックグラウンドにメモリが足りなくなるまで残る。

実態は1つ。そこに紐づく変数ができたらretain。紐づく変数を使い終わったらrelease。そんな感じ。autoreleaseをGCのように考えてはいけない!

Objective-Cのオブジェクトとクラス

■サンプルコード

こんな感じ。

Util.h

ヘッダ部分。

#import <Foundation/Foundation.h>

@interface Util/*クラス名*/ : NSObject/*親クラス*/ {
    // インスタンス変数の宣言
    float num;
}
// プロパティの宣言
@property float num;

// メソッドの宣言
+ (void)showAlertWithTitle:(NSString *)title withText:(NSString *)text;
@end

Util.m

実装部分。

@implementation Util
// プロパティの実装指示
@synthesize size;
+(void)showAlertWithTitle:(NSString *)title withText:(NSString *)text
{
    // 実装
}
@end

■サンプルコード

とりあえず書いてみる。

Animal.h

メソッドの宣言を記述する必要がある。

#import <Foundation/Foundation.h>
@interface Animal:NSObject {
    id _name;
}
@property(copy, readwrite) NSString *_name;
-(id)         init:(NSString *)name;// initialize
-(NSString *) cry: (NSString *)name;// instance method
+(NSString *) getKind;              // class method
@end

-がインスタンスのメソッドで、+がクラスメソッドでPHPのstaticのような扱いになる。

Animal.m

実装部分。本題から逸れるが、文字列結合部分の処理はPHPのsprintfのような扱いになる。

#import "Animal.h"
@implementation Animal
@synthesize _name;
- (id) init: (id) name {
    [super init];
    self._name = name;
    return self;
}
-(NSString *)cry: (NSString *)name{
    return [NSString stringWithFormat:@"cry! from %@ to %@", self._name, name];
}
+(NSString *)getKind{
    return @"Animal";
}
@end

宣言したときと引数の型まで一緒になるようにしないとね!

Dog.h

Animalクラスを継承する。プライベート変数を記述しない場合は、以下のように{}を記述しなくても良い。

#import "Animal.h"
@interface Dog : Animal
@end

Dog.m

特に変わった事をする必要はない。同名のメソッドを定義するとオーバーライドされる。

#import "Dog.h"
@implementation Dog
-(NSString *)cry: (NSString *)name{
    return [NSString stringWithFormat:@"bow! from %@ to %@", self._name, name];
}
+(NSString *)getKind{
    return @"Dog";
}
@end

クライアントコード

以下のようにして定義したクラスを使用する。

Dog *pochi = [[Dog alloc] init: @"pochi"];
NSString *voice = [pochi cry:(NSString *)@"hoge"];
NSString *kind  = [Dog getKind];
[pochi release];

allocで領域を確保したものは必ずreleaseする必要がある。また、以下のようにして現在の処理を抜けたときに自動的にreleaseすることもできる。

Dog *pochi = [[[Dog alloc] init: @"pochi"] autorelease];
NSString *voice = [pochi cry:(NSString *)@"hoge"];
NSString *kind  = [Dog getKind];

allocメソッドでインスタンスを生成し、initメソッドでインスタンスを初期化している。

■メソッドの使用

以下のようにしてメソッドを使用する。

[objName methodName]

ターゲットにメッセージを送るメッセージングという形式だ。引数を渡す場合は、以下のようにする。

[objName methodName:arg1]

引数が2つ以上ある場合、以下のようにラベルを使用する。

[objName methodName:arg1 label1:arg2]

インスタンスメソッドとクラスメソッドで特に違いはない。

■プロパティへのアクセス

以下のようにしてインスタンスのプロパティにアクセスすることができる。

pochi._name

アクセサメソッド(getterやsetter)を用いる方がスマートではある。

■親クラスかどうかを調べる

以下のようにisSubclassOfClassを使用する。

if([[obj class] isSubclassOfClass:[NSDictionary class]]){
    // something
}

■インスタンスかどうかを調べる

以下のようにしてインスタンスの基底クラスを調べる。

if([obj isMemberOfClass:[NSDictionary class]]){
    // something
}

■プロトコルを実装しているかどうかを調べる

以下のようにしてプロトコルを実装しているかどうかを調べる。

if([obj conformsToProtocol:@protocol(NSObject)]){
    // something
}

Objective-Cの変数

■宣言

型と変数名を以下のように記述して宣言する。

int x;
int a, b, c;

ちなみに、以下のように変数を再定義することはできない。

int x = 3;
int x = 4;

変数名

変数名は[a-zA-Z¥$_][a-zA-Z0-9¥$_]となる。

■型

以下はC言語の型である。

整数型

short(unsigned short)
16bit
int(unsined int)
32bit
long(unsigned long)
64bit

浮動小数点型

float
32bit
double
64bit

文字型

char(unsined char)
8bit

型なし

void
型なし

さらに、Objective-Cでは以下の型が追加された。

基本データ型

BOOL
「YES」もしくは「NO」
NSInteger
CPUのビット長に応じた型(int or long)
NSUInteger
CPUのビット長に応じた型(unsigned int or unsigned long)
CGFloat
CPUのビット長に応じた型(float or double)

オブジェクト型

id
オブジェクトの参照を保持する型
SEL
メソッドの参照を保持する型
IMP
idを戻り値とするメソッドの参照を保持する型

androidで別スレッドからUIを変更する

メモ。AsyncTaskは別記事で今回はHandler使おうってことで。

■実装

以下のようにすると強制終了する。

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;

public class MainActivity extends Activity {
    private ProgressDialog dialog;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // dialogの生成
        dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setMessage("wait...");
        dialog.show();
        
        // 別スレッド
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    updateProgress(i);
                }
                dialog.dismiss();
            }

            private void updateProgress(int i) {
                try {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                dialog.setMessage(i + " seconds");
            }
        }).start();
    }
}

以下のようにすることで問題なく処理することができる。

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;

public class MainActivity extends Activity {
    private ProgressDialog dialog;
    // Handlerクラスをインスタンス化すると、同スレッドのLooperオブジェクトとメッセージキューに紐付けられる
    Handler handler = new Handler();
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // ダイアログの生成
        dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setMessage("wait...");
        dialog.show();
        
        // 別スレッド
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    updateProgress(i);
                }
                dialog.dismiss();
            }

            private void updateProgress(final int i) {
                try {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // メッセージキューに処理を投げる => メインのLooperへ
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        dialog.setMessage(i + " seconds");
                    }
                });
            }
        }).start();
    }
}

Runnableとは

スレッドを作るためのインターフェースで、RunnableインターフェースのrunメソッドをオーバーライドしてThreadのコンストラクタに渡すした時に、Threadクラスのstartメソッドを呼ぶことでrunメソッドが別のスレッドで実行される。

Androidでのメッセージキューの処理は、android.os.Looperとandroid.os.Handlerの2つのクラスが担っている。

Objective-Cの基本

■コメント

// 一行の場合
/*
複数行の場合
*/

■ステートメント

// 一行の場合
/*
複数行の場合
*/

■ファイル

インターフェースファイル

.hの拡張子ファイルのもので、クラスの定義を記述する。

#import <Foundation/Foundation.h>
@interface Animal:NSObject {
    id _name;
}
@property(copy, readwrite) NSString *_name;
-(id)         init:(NSString *)name;// initialize
-(NSString *) cry: (NSString *)name;// instance method
+(NSString *) getKind;              // class method
@end

実装ファイル

#import "Animal.h"
@implementation Animal
@synthesize _name;
- (id) init: (id) name {
    [super init];
    self._name = name;
    return self;
}
-(NSString *)cry: (NSString *)name{
    return [NSString stringWithFormat:@"cry! from %@ to %@", self._name, name];
}
+(NSString *)getKind{
    return @"Animal";
}
@end

コンストラクタというものはない。イニシャライズというのがそれに近い気がする。

■インポート

以下のように記述して外部ファイルをインポートする。

標準ライブラリのディレクトリから

#import <lib.h>

例えば以下のように記述する。

#import <UIKit/UIKit.h>

現在のディレクトリから

#import "filename.h"

PHPのrequire_onceのように、一度インポートされたら再度インポートされない。C言語のincludeとは、その点が異なる。

■コンパイラディレクティブ

宣言

クラスの宣言
@interface
クラスの実装
@implementation
プロトコル宣言
@protocol
上述の宣言の終了
@end

可視性

// 宣言したクラスのみ
@private

// 宣言したクラスとサブクラス
@protected

// すべてのクラス
@public

指定しない場合はprotectedとなりsetterやgetterを使ってアクセスすることになる。

例外

以下のように記述して例外をキャッチする。

@try {
    // something
}
@catch (NSException *exception) {
    NSLog(@"main: Caught %@: %@", [exception name], [exception reason]);
}
@finally {
    // something
}

また、以下のようにして例外をスローする。

NSException *exception = [NSException exceptionWithName:@"HogeException" reason:@"No, hogehoge!" userInfo:nil];
@throw exception;

他の場所で定義されたクラスの名前を宣言

@class

指定されたメソッドを識別するセレクタ

@selector(method)

指定された名前のプロトコル

@protocol(name)

指定された構造体をエンコードする文字列

@encode(spec)

指定されたクラスのインスタンスの内部データ構造

@defs(className)

NSStringオブジェクト

@"hogehoge"

1スレッドのみ実行可能なブロックの定義

@synchronized