@blog.justoneplanet.info

日々勉強

Objective-CでJSONをパースする

SBJSONを使う。

■実装

まず、以下のようにインポートする。

#import "JSON.h"

次に以下のように使用する。

SBJSON *parser     = [[SBJSON alloc] init];
NSDictionary *json = [parser objectWithString:str error:nil];

簡単。(●´ω`●)

UIButtonTypeRoundedRectのボタンをカスタマイズする

■透明度

以下の記述によって透明度を設定することができる。

UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.alpha = 0.80;

■タッチ

以下の記述によってタッチを無効化することができる。

UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.userInteractionEnabled = NO;

Objective-Cでapplication/json形式のPOSTをする

0からOAuthのクライアントコードを書くのは面倒なので、oauthconsumerを使用する。

実はライブラリのページには以下のような質問が掲載されているが返答がされていないようだ。

Hello, I am trying to submit some JSON data to a web service using a POST request. For some reason however, the framework seems to look for additional parameters in the HTTP body where I put my JSON data. This results in an index-out-of-range exception since it is unable to split the HTTP body into parameter components.

以下のように実装すると、application/jsonでPOSTすることができる。

NSURL *url = [NSURL URLWithString:@"https://example.com/user/1/flights/"];
OAConsumer *consumer = [[OAConsumer alloc] initWithKey:@"mykey" secret:@"mysecret"];
OAToken *accessToken = [[OAToken alloc] initWithKeychainUsingAppName:@"MyApp" serviceProviderName:@"Example.com"];

OAMutableURLRequest  *req = [[OAMutableURLRequest alloc] initWithURL:url consumer:consumer token:accessToken realm:nil signatureProvider:nil];
[req setHTTPMethod:@"POST"];
[req prepare];
[req setHTTPBody:[(NSString*)jsonStr dataUsingEncoding:NSUTF8StringEncoding]];
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

OADataFetcher *fetcher = [[OADataFetcher alloc] init];
[fetcher fetchDataWithRequest:request delegate:self didFinishSelector:@selector(apiTicket:didFinishWithData:) didFailSelector:@selector(apiTicket:didFailWithError:)];

ちなみに通常のPOSTをしたい場合は、ドキュメントにもあるように以下のようにする。

NSURL *url = [NSURL URLWithString:@"https://example.com/user/1/flights/"];
OAMutableURLRequest *request = [[OAMutableURLRequest alloc] initWithURL:url consumer:consumer token:accessToken realm:nil signatureProvider:[[OAPlaintextSignatureProvider alloc] init]];
OARequestParameter *nameParam = [[OARequestParameter alloc] initWithName:@"title" value:@"My Page"];
OARequestParameter *descParam = [[OARequestParameter alloc] initWithName:@"description" value:@"My Page Holds Text"];
NSArray *params = [NSArray arrayWithObjects:nameParam, descParam, nil];
[request setParameters:params];

OADataFetcher *fetcher = [[OADataFetcher alloc] init];
[fetcher fetchDataWithRequest:request delegate:self didFinishSelector:@selector(apiTicket:didFinishWithData:) didFailSelector:@selector(apiTicket:didFailWithError:)];

PHP

ちなみにPHPで同じことをする場合は以下のように記述する。

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://justoneplanet.info');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"hogehoge" : "fugafuga"}');
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_HEADER, TRUE);
$result = curl_exec($ch);

CakePHPでログインフォームを作る

<?php
if($this->Session->check('Message.auth')){
    echo $this->Session->flash();// error msg
    echo $this->Session->flash('auth');// error msg
}
echo $form->create(
    'Admin',
    array(
        'url' => array(
            'controller' => 'users',
            'action'     => 'login'
        )
    )
);
?>
<div data-role="fieldcontain">
<dl>
<dt><label for="name">ユーザ名</label></dt>
<dd><?php
echo $form->input(
    'name',
    array(
        'div'   => false,
        'label' => false,
        'id'    => 'name'
    )
);
?></dd>
</dl>
</div>
<div data-role="fieldcontain">
<dl>
<dt><label for="password">パスワード</label></dt>
<dd><?php
echo $form->input(
    'password',
    array(
        'div'   => false,
        'label' => false,
        'id'    => 'password',
        'value' => ''
    )
);
?></dd>
</dl>
<p><?php
echo $form->submit(
    'ログイン',
    array(
        'div'   => false,
        'label' => false,
    )
);
?></p>
<?php
echo $form->end();
?>

UITextViewの角を丸くする

QuartzCoreをインポートする。

#import <QuartzCore/QuartzCore.h>

以下のように記述する。

[textview.layer setCornerRadius:8.0f];

ボタンの画像を伸縮して貼り付ける

めも。

UIImage *bg = [[UIImage imageNamed:@"button.png"] stretchableImageWithLeftCapWidth:10 topCapHeight:10];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(20, 366, 136, 38)];
[btn setBackgroundImage:btnBg forState:UIControlStateNormal];

MacのXAMPPにXdebugを入れる

UNIXベースなので以下のコマンドでインストールしてみる。

/Applications/XAMPP/xamppfiles/bin/pear upgrade pear channel-update pear.php.net
/Applications/XAMPP/xamppfiles/bin/pear upgrade pear upgrade PEAR
/Applications/XAMPP/xamppfiles/bin/pecl install xdebug

コンパイルで大量のエラーが出て上手くいかないはずだ。

■解決策

Komodo Remote Debugging Package Downloadsで、「Downloads > PHP Remote Debugging Client > Mac OS X (universal)」をクリックしてダウンロードし、PHPのバージョンに合ったファイルを以下のコマンドでコピーする。

cp /Users/[user]/Downloads/Komodo-PHPRemoteDebugging-6.1.0-60797-macosx/5.x/xdebug.so /Applications/XAMPP/xamppfiles/lib/php/php-5.x.x/extensions/no-debug-non-zts-20xxxxxx/xdebug.so

設定ファイル

以下のコマンドを実行する。

vi /Applications/XAMPP/xamppfiles/etc/php.ini

次に、以下の一行を加える。

[xdebug]
zend_extension ="/Application/XAMPP/xamppfiles/lib/php/php-5.x.x/extensions/no-debug-non-zts-20xxxxxx/xdebug.so"
xdebug.remote_enable = 1
xdebug.remote_host = 127.0.0.1
xdebug.remote_port = 9000
xdebug.remote_handler = dbgp
xdebug.profiler_enable = 1
xdebug.profiler_output_dir = "/Applications/XAMPP/xamppfiles/htdocs/_xdebug_profiler"
xdebug.profiler_output_name = cachegrind.out.%s
基本設定

ついでにマルチバイト系の設定をし忘れてたのでやっておく。

mbstring.language = Japanese
mbstring.internal_encoding = UTF-8
mbstring.http_output = UTF-8
expose_php = Off

500個めの記事でしたーσ°▽°)σ

ネストしたクラスとインターフェース

ネストした型はエンクロージング型にアクセスできる場合にだけアクセスできる。また、アクセスの観点などから1レベルだけのネストが推奨される。

■staticのネストしたクラス

public class BankAccount {
    private long number;
    private long balance;
    public static class Permissions {
        public boolean canDeposit, canWithdraw, canClose;
    }
}

■ネストしたインターフェース

ネストしたインターフェースは常にstaticであるが慣習的に省略される。

■内部クラス

staticでないクラスメンバーはそのクラスのインスタンスと関連付けされている。内部クラスのオブジェクトは常にエンクロージングインスタンスと関連付けされている。

public class BankAccount {
    private long number;
    private long balance;
    private Action lastAction;
    public class Action {
        private String action;
        private long amount;
        Action(String action, long amount) {
            this.action = action;
            this.amount = amount;
        }
        public String toString() {
            return number + ": " + action + " " + amount;
        }
    }
    public void deposit(long amount) {
        balance += amount;
        lastAction = new Action("deposit", amount);
    }
    public void withdraw(long amount) {
        balance -= amount;
        lastAction = new Action("withdraw", amount);
    }
}

通常、内部クラスのオブジェクトはdepositやwithdrawの中で行われたようにエンクロージングクラスのインスタンスメソッドの内部で生成される。

lastAction = new Action("deposit", amount);

上述のコードを厳密に書くと以下のようになる。

lastAction = this.new Action("deposit", amount);

制限

staticフィールドを持つことができないのでstatic finalにするかインスタンスフィールドにする必要がある。

アクセス

以下のようにエンクロージングクラスは明示的な参照を通して、内部クラスのprivateメンバにアクセスできる。

public void deposit(long amount) {
    balance += amount;
    lastAction = new Action("deposit", amount);
    String str = lastAction.action;
}

以下のように内部クラスからエンクロージングクラスにアクセスすることができる。

public String toString() {
    return number + ": " + action + " " + amount;
}

また以下のように限定的なthisを用いて記述することもできる。

public String toString() {
    return BankAccount.this.number + ": " + action + " " + amount;
}

但し、staticメソッドなどから限定的なthisを用いることはできない。

■拡張

以下のようなクラスを考える。

class Outer {
    class Inner {}
}

以下のようにして拡張することができる。

class ExOuter extends Outer {
    class ExInner extends Inner {}
}

内部サブクラスのエンクロージングクラスがOuterのサブクラスでない場合、内部サブクラス自身が内部クラスでない場合、superを通してInnerのコンストラクタを呼び出す際にOuterオブジェクトへの明示的な参照を提供しなくてはならない。

class Unrelated extends Outer.Inner {
    Unrelated(Outer ref) {
        ref.super();
    }
}

■隠蔽

以下のように同名のメソッドを内部クラスで定義すると、引数に関係なくエンクロージングクラスのメソッドが隠蔽される。

class Outer {
    void print() {}
    void print(int val) {}
    class Inner {
        void print() {}// エンクロージングクラスの全てのprintメソッドを隠蔽する
        void show() {
            print(1);// コンパイルエラー
        }
    }
}

■ローカル内部クラス

以下のようにメソッド本体(コンストラクタや初期化ブロックなど)のコードブロックの中で内部クラスを定義することができる。

public void onClick(View v) {
    class BankAccount {}
    BankAccount bankAccount = new BankAccount();
}

コードブロックの外からはアクセスできず完全にローカルである。また、staticとする事はできない。

■無名内部クラス

以下のようにして無名内部クラスを定義することができる。無名内部クラスはコンストラクタを持つことができない。

private static Iterator<Object> walk(final Object[] objs) {
    return new Iterator<Object>() {
        private int position = 0;
        @Override
        public boolean hasNext() {
            return (position < objs.length);
        }
        @Override
        public Object next() {
            if (position >= objs.length) {
                throw new NoSuchElementException();
            }
            return objs[position++];
        }
        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    };
}

明示的なコンストラクタを必要とする場合はローカル内部クラスを使用するべきである。また、6行を超えるような無名内部クラスは可読性の観点から避けるべきである。

■ネストした型の継承

以下のようなコードを考える。

abstract class Device {
    abstract class Port {

    }
}
class Printer extends Device {
    class SerialPort extends Port {

    }
    Port serial = new SerialPort();
}
class HighSpeedPrinter extends Printer {
    class SerialPort extends Printer.SerialPort {

    }
    // 継承されたメンバserialは親クラスのSerialPortのインスタンスである
}

一方で以下のようにファクトリーメソッドを用いてオブジェクトを生成する場合を考える。

class Printer extends Device {
    class SerialPort extends Port {

    }
    Port serial = createSerialPort();
    protected Port createSerialPort() {
        return new SerialPort();
    }
}
class HighSpeedPrinter extends Printer {
    class EnhancedSerialPort extends SerialPort {

    }
    // 継承されたメンバserialは初期化の際にカレントクラスでOverrideされたcreateSerialPortが呼ばれる
    protected Port createSerialPort() {
        return new EnhancedSerialPort();
    }
}

■インターフェース内でのネスト

以下のようにしてインターフェース内でネストしたクラスを定義することができる。

interface Changeable {
    class Record {
        public Object changer;
        public String changeDesc;
    }
    Record getLastChange();
}

暗黙的にpublicかつstaticになる。

インターフェース内での変更可能な変数

以下のようにすることでSharedDataを利用しているコードは、data参照を通して共通の状態を共有できる。

interface SharedData {
    class Data {// 暗黙的にstatic
        private int x = 0;
        public int getX() {return x;}
        public void setX(int x){this.x = x;}
    }
    Data data = new Data();
}

Objective-Cのプロトコルとカテゴリ

■プロトコル

JavaやPHPのインターフェース的なもの。

Animal.h

動物という種は存在しないので、以下のようにプロトコルとして定義した。

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

Dog.h

上述で定義したプロトコルを以下のようにして実装する。

#import "Animal.h"
@interface Dog:NSObject<Animal>
@end

Dog.m

以下のように実装する。

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

ちなみに、プロトコルで宣言したメソッドが実装されていない場合、warningが発生する。

■カテゴリ

複数のクラスに分割したり、クラスに新しい機能を追加したりできる。

trim.h

#import <Foundation/Foundation.h>
@interface NSString(Trim)
- (NSString *) trim;
@end

trim.m

#import "trim.h"
@implementation NSString(Trim)
- (NSString *) trim{
    return [self stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
@end

上述の記述によって、NSStringクラスにTrimメソッドが追加される。文法は全然違うけどJSのprototype的な発想かもしれん。