2016年8月17日水曜日

CocoaPods で undefined method inherit が出た場合の対処方法

オフィス狛 技術部です。

今回は iOS のライブラリ管理ツールである CocoaPods で発生したエラーについて書いてみます。

※CocoaPods とは、Androidでいうところの Gradle みたいなもので、
ライブラリ管理においては、非常に有用なツールです。


弊社のGit管理上、Pods 以下は gitignoreに入れています。
その為、Gitからプロジェクトをクローンして、最初にやるのが、ターミナルでの
pod update
になります。

今回も、意気揚々とコマンド入力したら・・・

[!] Invalid `Podfile` file: undefined method `inherit!' for #. Updating CocoaPods might fix the issue.

 #  from /Users/officekoma/testproj/Podfile:18
 #  -------------------------------------------
 #    target 'SampleTests' do
 >      inherit! :search_paths
 #      # Pods for testing
 #  -------------------------------------------

[!] Your Podfile has had smart quotes sanitised. To avoid issues in the future, you should not use TextEdit for editing it. If you are not using TextEdit, you should turn off smart quotes in your editor of choice.

あらら。「inherit なんてメソッド知らない!」と怒られてしまいました。
調べてみると、inherit は「cocoapods-1.1.0.beta.1」より追加されたメソッドのようです。
という訳で、ターミナルで
pod --version
と打ってみます。
結果は・・・1.0.1

どうやら、このプロジェクトを作成した端末の CocoaPods と、
私の端末の CocoaPods はバージョン違っているようです。

では、バージョンアップしようという事で、今度はターミナルで、
sudo gem install cocoapods --pre
と打ってみます。(「--pre」はbeta版をインストールする為に必要となります。)

ERROR:  While executing gem ... (Errno::EPERM)
    Operation not permitted - /usr/bin/xcodeproj

またエラー・・・

ただ、このエラーは割と有名です。
mac の OS が El Capitan 以降の場合、セキュリティ上の仕様で、
「/usr/bin」にはroot権限でもアクセス制限が掛かってしまいます。

インストールディレクトリを変える必要があるので、ターミナルで、
sudo gem install -n /usr/local/bin cocoapods --pre
と入力し、今度こそ 1.1.0.beta.1 のインストールに成功。

「pod setup」で、セットアップを行い、バージョンを確認すると・・・1.0.1
・・・あれ?確かにインストールは成功したのに・・・。
デフォルトで適用されるものを最新バージョンで差し替えてくれる訳では無いのですね。
(まあ、そりゃそうですよね。)

という訳で、ターミナルで
sudo gem uninstall cocoapods
と入力し、

Select gem to uninstall:
 1. cocoapods-0.39.0
 2. cocoapods-1.0.1
 3. cocoapods-1.1.0.beta.1

1.1.0.beta.1 以外のバージョンを削除します。
これで無事に「pod update」まで出来ました。

後書き

上記はあまり良い方法とは言えませんのでご注意下さい。
複数メンバーで開発していると、 CocoaPods のバージョンが異なる事はよくあります。
(今回は、undefined method でそれが発覚しました)

通常は、それを避ける為に bundler を導入します。

・・・が、そもそも、
CocoaPodsの開発元は、Pods 以下は gitignoreに入れない事を推奨しています。

CocoaPods Guides -Using CocoaPods

確かに、Gitからクローンしただけで、ビルド出来るのが最適ですからね。

2016年8月11日木曜日

小石につまずくとけっこう痛い Codeigniter3 複数ファイル同時アップロード

オフィス狛 技術部です。

前回(小石につまずくとけっこう痛い Codeigniter3 ファイル命名規約)に引き続き、
今回も PHP ( Codeigniter3 )で地味につまずいたことを書こうと思います。

何度も言いますが
 Codeigniter は日本語の情報が少ない!!!
ということでよくつまずきます。

なので、自分の調べた備忘録も兼ねて、
ここにつまずきろく(記録)をためていきたいと思います。

今回は、
『 複数のファイルをまとめてアップロードする 』
です。

1つのファイルをアップロードする例は本家サイトにも載っているので割愛。
https://www.codeigniter.com/userguide3/libraries/file_uploading.html

何が困ったかというと、

1. そもそも複数のファイルデータってどうやってとるの?

2. アップロードメソッドに値ってどうやって設定するの?

という2点。


結論から言うと、どちらも単純な PHP の話。

1. そもそも複数のファイルデータってどうやってとるの?

<input name="images[]" type="file" />

であれば、

$_FILES['images']でファイルデータの配列が取れます。

2. アップロードメソッドに値ってどうやって設定するの?

アップロードメソッド( $this->upload->do_upload() )
は1つずつのアップロードしか対応していないようなので、
ファイルデータの配列から1つずつループしてアップロードしていく必要があります。

$config = array(
   'upload_path' => './uploads/',
   'allowed_types' => 'jpg|gif|png',
   'overwrite' => true,
);

foreach ($files['name'] as $key => $image) {
   $_FILES['images']['name']= $files['name'][$key];
   $_FILES['images']['type']= $files['type'][$key];
   $_FILES['images']['tmp_name']= $files['tmp_name'][$key];
   $_FILES['images']['error']= $files['error'][$key];
   $_FILES['images']['size']= $files['size'][$key];
   $config['file_name'] = $image;
   $this->upload->initialize($config);
   if ($this->upload->do_upload('images')) {
    $this->upload->data();
   } else {
    return false;
   }
}

1つ1つ値を設定し直してアップロードしていくだけの話でした。


フレームワークを使っているとデータを勝手に設定してくれたりするので、
『枠内』だけで考えてしまいがちになってしまう今日この頃。


足元の確認は大事ですね。

,

2016年8月2日火曜日

URLConnection(HttpURLConnection)と向き合おう~POSTメソッドでJSONを送信する~

オフィス狛 技術部です。

URLConnection(HttpURLConnection)と向き合うために、今回はPOSTメソッドを使います。
POSTはPOSTでも特にHTMLのフォームタグでPOSTを行ったときと同様のデータを送信します。

今回の目標

  • HttpURLConnectionを使ってPOSTメソッドを行う。
  • ContentTypeはapplication/jsonの形式にする。
  • POSTメソッドのレスポンスデータを取得する。

HttpURLConnectionを使ったPOSTのやり方

HttpURLConnection はGET/POSTに関係なくメソッドの呼び出し順序は大体決まっています。
順序は下記のとおりです。
  • ステップ1 :接続URLを決める。
  • ステップ2 :URLへのコネクションを取得する。
  • ステップ3 :接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。
  • ステップ4 :コネクションを開く
  • ステップ5 :リクエストボディの書き込みを行う。(POSTのみ行う)
  • ステップ6 :レスポンスの読み出しを行う。
  • ステップ7 :コネクションを閉じる。

ステップ1:接続URLを決める。

接続先URLを決めるには java.net.URL を使います。
URL url = new URL("[接続先のURLを設定してください。]");

ステップ2:URLへのコネクションを取得する。

接続URLのコネクションを取得することができます。
URL url = [ステップ1:接続URLを決めるを参照してください];
HttpURLConnection urlConnection = null;
try {
    urlConnection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
    e.printStackTrace();
}

ステップ3:接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。

今回はPOSTを使ったデータの送受信を行うので特に下記の設定を行う必要があります。
  • setRequestMethod を使いPOSTを使う設定をする
  • リクエストヘッダーの Content-Type を application/json に設定する。
  • リクエストボディにデータを書き込むため、setDoOutput を使い許可をする。
  • レスポンスボディのデータを読み出しするため、setDoInput を使い許可をする。
//接続タイムアウトを設定する。
urlConnection.setConnectTimeout(100000);
//レスポンスデータ読み取りタイムアウトを設定する。
urlConnection.setReadTimeout(100000);
//ヘッダーにUser-Agentを設定する。
urlConnection.setRequestProperty("User-Agent", "Android");
//ヘッダーにAccept-Languageを設定する。
urlConnection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
//ヘッダーにContent-Typeを設定する
urlConnection.addRequestProperty("Content-Type", "application/json; charset=UTF-8");
//HTTPのメソッドをPOSTに設定する。
urlConnection.setRequestMethod("POST");
//リクエストのボディ送信を許可する
urlConnection.setDoOutput(true);
//レスポンスのボディ受信を許可する
urlConnection.setDoInput(true);

ステップ4:コネクションを開く

コネクションを設定した内容で接続し、実際にデータの送受信ができる状態にします。
このメソッド以降は「接続先にデータを送信する OutputStream 」と「接続先からデータを受信する InputStream 」を使うことができるようになります。
//ステップ4.コネクションを開く
urlConnection.connect();

ステップ5:リクエストボディの書き込みを行う。

「application/json」の送信データはRFC7159に準拠した文字列を送信する必要があります。
今回はorg.json.JSONObjectクラスを使いHashMapをJSON文字列に変換します。
//ステップ5:リクエストボディの書き出しを行う。
OutputStream outputStream = urlConnection.getOutputStream();
HashMap<String, Object> jsonMap = new HashMap<>();
jsonMap.put("text" , "value");
ArrayList<String> array = new ArrayList<>();
array.add("array001");
array.add("array002");
array.add("array003");
jsonMap.put("array" , array);
if (jsonMap.size() > 0) {
    //JSON形式の文字列に変換する。
    JSONObject responseJsonObject = new JSONObject(jsonMap);
    String jsonText = responseJsonObject.toString();
    PrintStream ps = new PrintStream(urlConnection.getOutputStream());
    ps.print(jsonText);
    ps.close();
}
outputStream.close();

ステップ6:レスポンスボディの読み出しを行う。

主なレスポンスデータは下記のメソッドを使って取得します。
  • getResponseCode を使うことでHTTPステータスコードを取得できます。
  • getInputStream を使うことレスポンスボディのデータストリームを取得できます。

//ステップ6:レスポンスボディの読み出しを行う。
int statusCode = urlConnection.getResponseCode();

String responseData = "";
InputStream stream = urlConnection.getInputStream();
StringBuffer sb = new StringBuffer();
String line = "";
BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
while ((line = br.readLine()) != null) {
    sb.append(line);
}
try {
    stream.close();
} catch (Exception e) {
    e.printStackTrace();
}
responseData = sb.toString();
今回はレスポンスボディのデータストリームを文字列に変換しています。

ステップ7:コネクションを閉じる。

使ったコネクションはしっかりと閉じましょう。
//ステップ7:コネクションを閉じる。
urlConnection.disconnect();

サンプルコード

今回のサンプルコードの仕様は下記のとおりです。

  • URLとハッシュマップを引数で渡す。
  • 指定したURLにPOSTメソッドで接続する。
  • Content-Typeはapplication/jsonを使用する。
  • org.json.JSONObjectを使用してハッシュマップをJSON文字列に変換する。
  • リクエストボディにapplication/jsonに適合する文字列を設定する。
  • レスポンスボディデータは文字列として扱う。
  • 非同期処理から呼び出されることを想定している。
  • 呼び出し側には文字列を返却する。
    public String execute(String argStrApiUrl, HashMap<String,Object> jsonMap) {
        String ret = "";
        HttpURLConnection urlConnection = null;
        try {
            //ステップ1.接続URLを決める。
            URL url = new URL(argStrApiUrl);

            //ステップ2.URLへのコネクションを取得する。
            urlConnection = (HttpURLConnection) url.openConnection();

            //ステップ3.接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。
            //接続タイムアウトを設定する。
            urlConnection.setConnectTimeout(100000);
            //レスポンスデータ読み取りタイムアウトを設定する。
            urlConnection.setReadTimeout(100000);
            //ヘッダーにUser-Agentを設定する。
            urlConnection.setRequestProperty("User-Agent", "Android");
            //ヘッダーにAccept-Languageを設定する。
            urlConnection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
            //ヘッダーにContent-Typeを設定する
            urlConnection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            //HTTPのメソッドをPOSTに設定する。
            urlConnection.setRequestMethod("POST");
            //リクエストのボディ送信を許可する
            urlConnection.setDoOutput(true);
            //レスポンスのボディ受信を許可する
            urlConnection.setDoInput(true);

            //ステップ4.コネクションを開く
            urlConnection.connect();

            //ステップ5:リクエストボディの書き出しを行う。
            OutputStream outputStream = urlConnection.getOutputStream();
            if (jsonMap.size() > 0) {
                //JSON形式の文字列に変換する。
                JSONObject responseJsonObject = new JSONObject(jsonMap);
                String jsonText = responseJsonObject.toString();
                PrintStream ps = new PrintStream(urlConnection.getOutputStream());
                ps.print(jsonText);
                ps.close();
            }
            outputStream.close();

            //ステップ6.レスポンスボディの読み出しを行う。
            int responseCode = urlConnection.getResponseCode();
            ret = convertToString(urlConnection.getInputStream());
            Log.d("execute", "URL:" + argStrApiUrl);
            Log.d("execute", "HttpStatusCode:" + responseCode);
            Log.d("execute", "ResponseData:" + ret);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                //7.コネクションを閉じる。
                urlConnection.disconnect();
            }
        }
        return ret;
    }

    public String convertToString(InputStream stream) throws IOException {
        StringBuffer sb = new StringBuffer();
        String line = "";
        BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        try {
            stream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

今回のまとめ

  • POSTメソッドではリクエストボディを使ったデータ送信を許可するために setDoOutput にtrueを渡すこと。
  • application/json に適合した送信文字列を作成するときはorg.json.JSONObjectクラスなどのJSON文字列を取り扱うクラスを使用すること。
  • テキストとファイルを同時に送信する Content-Type「multipart/form-data」とは異なるので注意すること。