https://cloud.google.com/endpoints/?hl=ja
CloudEndpointsを使うことで簡単にGAE(GoogleAppEngine)にAPIサーバを作ることが出来る。
Extensible Service Proxyとうなんかすごいやつが、認証やモニタリング、ロギングなどをやってくれ、AppEngineの画面やEndpointの画面でいろいろ見れる
スタンダード環境とフレキシブル環境があり、スタンダードはJavaとPython2.7
フレキシブルは、Go, PHP, node.js, ruby Python3, .NET等がある(β)
ざっくり説明
Swagger(OpenAPI)ベースのAPI定義をCloudEndpointにデプロイ(上の図だとGoogleServiceManagementって部分)して、
それに対応したサーバーサイドを書き、GAEにデプロイする(上の図のAPIContainer)感じになる。
GAEにデプロイするサーバサイドのフレームワークは、PythonだとFlaskとか、PubyだとSinatraとか、NodeだとExpressとかになる。
サンプルコードはチュートリアルからDL出来るので、動かすには困らないし、そのまま拡張しちゃえばいい。
APIキーはGCPのコンソールで発行できる。
しかし、APIキーだけでは認証が弱いので、組み合わせて認証を利用できる
それが
JWT(json web token) ・・・ アプリケーション同士の認証に有効
GoogleIDAuth、FirebaseAuth、Auth0 ・・・ ユーザの認証に有効
となっている。
JWTもまた、GCPコンソールから発行可能なので、キーの発行にコマンドなども必要ないし、管理もGCP上で出来る
認証はAPIキーと、上記2つのどちらかを組み合わせてやる形になる。
この2つの組み合わせが正しくなければ動かない、というふうに作る。
ここまでこなせば、あとはSwaggerのルールにそいながら設定し、実装していけばできていくんだと思う。
Endpointが問題なく動作すれば、モニタリング画面が自動でできる
APIの仕様書もできる
やってて詰まったポイント
プロジェクト外のCloudSQLにつなごうとする場合
IAMの設定
つなぎに行くがわのCloudSQL APIの有効化
が必要となる。
前者は別プロジェクトにアクセスしに行くためににAppEngineのIDを通しておく、ということ
後者はCloudSqlProxyをうごかすためにある
参考:https://cloud.google.com/sql/docs/app-engine-connect?hl=ja
あと、Swaggerをある程度わかったほうがいい。全く知らずに取り組んで少し困った。
チュートリアルはこちら
https://cloud.google.com/endpoints/docs/openapi/tutorials?hl=ja
沿っていけば動く元は作れるはず。別プロジェクトのCloudSQLにつなぐなら前項 を気をつけること
ある日突然、ボットサーバーとして使っていたBluemixのNode-RED starterのサーバーが動かなくなりました。
表示は「未実行」ログには「異常な終了」となっており、いまいち原因がつかめず…。
再起動してみても、同じく異常終了して未実行のまま動作しない。
ということで、IBMCloudの「開始」に書いてあるコマンドライン操作から、ファイルをアップし直してみると解消しました。
一体何が問題だったのか…謎は深まりますが、とりあえずこれで問題なく動作します。
なんなんだ一体…。
pip でインストールしても、numpyがなんとかのエラーが出てインポートすらままならない。
以下を動かすといける
この記事を元に、ノードを作ってったんだけど、ちゃんと作るぞー!と思って色々やってると、ちょっとハマった部分があったのでメモしておこうと思う。
ちなみに今回、LINE BOTを動かすためのノードを作りました。よかったら使ってね。
https://flows.nodered.org/node/node-red-contrib-linebot
ノードを作ってノード配置を完了した後に、名前の変更をしようとするとエラーになる問題
とりあえずノードを作ったあとに「あ、ノード名コレじゃないわ」ってなってから名前を変え、Node-REDを再起動すると「Waiting for missing types to be registered:」と出てきてうまく反映されない事がある。
これを解消するには、~/.node-red/の中にある xxxxxxx.local.jsonを消すと反映される。但し、ローカルに配置したノードは消えるので、JSONでバックアップをとっておくことをおすすめします。
ウェブサービスを作ることになって、さて何を使おうかと考えて、最終的にdjangoを使ってみよう、ということにした。
なぜdjangoにしたかというと、キャリアとしてPythonを知っていることが今後重要になると感じたから。PythonはWeb、ロボット、機械学習、スタンドアロン、なんにでも使えるしPython3を使うのにもいい感じになってきているらしいので。
とりあえず最新版の1.11.x系を触った。
djangoの特徴
MVT
MVT(Model, View, Template)になっている。言葉が違うだけでだいたいMVCと同じ。
Mは同じくModelだけど、ViewはMVCで言うControllerの役目。TemplateがMVCのViewを担う感じ。
ある程度の規約はあるが、比較的自由に書ける。Templateの命名ルールもデフォルト値はあるが簡単に書き換えも可能。しかし規約どおりに作るとコードがどんどん減っていく作りになっている。
form・modelクラス
modelクラスはDBとのつなぎ役をやってくれるわけだけど、formクラスはウェブフォームを定義するクラス。テンプレートで使用するフォームを定義し、テンプレートの読み込ませるとその定義どおりのフォームを自動生成してくれる。
そしてそのformクラスとmodelクラスを組み合わせた、ModelFormというクラスが有り、これを定義すると、モデルとフォームのヒモ付が完了したフォームが自動生成される。
汎用ビュークラス(generic view)
よく使う機能をまるっと集めたクラス。
各CRUDやList、Detail、Redirectなどよく使う機能をお手軽に生成できる。
これを定義する時に、先に紹介したFormやModelを変数に入れるだけで画面ができてしまう。とても簡略的に書くと
class xxxcreateview(CreateView) :
model = XXXX
form = XXXX
template = XXXX
こんなかんじ。手っ取り早く機能だけをズバーっと作りたいときにとても便利。
adminページ
DBをコントロールすることの出来る管理ページが自動生成される。
開発用にDBいじりたいわーとか、テスト用のデータ作りたいわーなんて言う時に便利。
テスターの人に「こういうデータ作って」なんて言われることが減ります。ユーザ発行して作ってもらえば良いんだし。
python manager.py runserver
開発用サーバ立ち上げコマンド。このコマンドを実行すると開発用サーバが立ち上がり、プレビューが可能になる。このコマンドの場合、コードが一部変更されると自動的にサーバが再起動するので、確認が楽。
ちなみに、python manage.pyにはいろんなコマンドがあり、マイグレーションやアプリケーションの作成なども含まれる。
djangoのやりにくさ
利用実績&最新バージョンの日本語情報は少ない
採用しているサービスが少ないがゆえの宿命だと思う。機械学習が流行ってるから、ぼちぼちdjango使ってみたよーって人は増えてくると思うけど、やっぱり情報が少ない。書籍もほぼ見かけない。公式ドキュメントとstackoverflowと友だちになろう。
プロジェクトの構造どうするのが最適なんだろう?
django-admin のコマンドでプロジェクト生成すると、なーんか気持ちの良くない構造が最初に生成される。VisualStudioでDjangoのプロジェクトを生成するとまた違うプロジェクト構造が生まれてくる。どれがベストプラクティスなん?っていうのも明確にないし、公式が発行しているプロジェクト構造もなんかなーという感じ。ここは模索していくことになるだろう。
エディタ
多分pycharmが最強なんだろうけど、高いし、visual studio codeで書いてるけどいまいち補完が役に立たなかったりする。インポートとかも結構めんどくさかったりもするので、そのへん、いい感じのないのかなー。とおもうと pycharmなんだよなー…。という感じに。
VisualStudioはどうなんだろう。使ってみなければ。
ライブラリ
PHPとかRailsとかに比べればライブラリの数は少ないと思う。なければ作る!の気概がある程度必要。
まとめ
概ね気に入ってます。
初めて触ったWebフレームワークはRailsだったけど、正直肌に合わなかった。理由は使い方以前に規約が多すぎて覚えらんなかったから。djangoは規約はあれどもそこまで強いものではないし、書きやすい。
特に汎用ビューの使い方がわかってくると、どんどん機能を作っていけるようになるから学んでいて楽しいしね。
次からは使い方周りとかをまとめていこうと思います。
(Node-REDアドベントカレンダー 12/6分)
この前、iOSコンソーシアムという集まりのハッカソンに参加してきました。
そこで、雰囲気メガネをビーコンとiOSを組み合わせたものを作ったのですが、その中でふと「サーバーサイドってまともにやったこと無いし、やってみようかな」と思って「サーバーサイドやりまーす」と手を挙げ、APIを作ってみることにしました。
そのイベントの中で「Node-RED」の説明があり、同じチーム人から「簡単にAPI作れるよ」と言われて「簡単ならそれを使うに決まってんじゃん!」とそれを使うことにしました。
そこでNode-REDの説明をして、「どこで困ったか」という点と「ここが良かった」という点、そして「ここがこうなるといいのになー」という点をまとめて書こうと思います。
そもそもNode-REDってなんだ?
Node-REDは、IBMのBluemixで使えるフロー式のロジック作成ツールのようなもので、ノードを繋いでロジックを作り、コードを書かなくてもそこそこ動くものが作れちゃうツールです。
↓こんなの
サーバーサイドのサの字くらいは知ってる、っていう僕でもなんとなーくで使えるツールです。
以前UnrealEngine4のBluePrintを使っていたので、なんとなーく使い方はわかる感じでした。
APIをどんな風に作るのか?
上の絵で説明すると、左上に[get]/user_new ってのがありますが、あんな風にアクセスのURLを指定して、そこにアクセスがあったらどのように動作させるか?というのを、ノードで処理を繋いでいくという感じです。
使ってみてどう思ったか?
困った所
Cloudantを使うとAPIネタがぐっと減る
データベースを、とりあえずそこにあったCloudantを使いました。そしてNode-REDのAPIについて調べていくと、大体がMySQLを使ってるんですよね。「CloudantってJsonで保存してくれるんでしょ?だったらそれに突っ込めばいいや」ってかんじでCloudantを使ったんですが、もうちょっと考えるべきだったかな、と思いました。
JSを書いた場合、どこでデバッグすればいいの?
中身はnode.jsなので、コードはJSで書くわけですが、そうすると「あれ?このオブジェクトの構造みたいけどどうしたら良いんだ?」とか「ここのロジックのconsole.logとかどうやって見るの?」とか分からなくって、でもハッカソンだから時間がなくて、とりあえずエイヤーで動かしたんですが、実際の所どうするのが効率的なのか分からなかった。
フォーム送信した後の送信完了画面の出し方
ある程度の最低限機能ができて、完了画面くらい出すかー、と思ってノードを繋いでたら思った以上にできなくって、チューターの人に聞いてもわかんなくて、、と結構時間を食った。
結局、ノードをつなぐポイントが誤っていた、というのが原因だったんだけど、ノードを繋いでいても実行されない、ということが起こるとは思わなくて混乱した。
ちなみに、コレだと動いて
コレだと動かなかった
クエリのあとにDoneをやろうとすると動作しないんです「クエリ完了したからDone出すだろ!」ってイメージだったんだけど、その常識は通じなかった。
良かった所
楽!
環境つくって、コードの種類を覚えて、書き方覚えて…と考える必要なく、手順を作っていけばとりあえずはなんとかなるのが良いところ。
最初のその学習ハードルを超えて、とりあえずサクッと動かせるのは「作りたい」の気持ちを満たせるという点でとても良い。
先の興味へつながる
ただ、動かしていると「ここでエラーが出た場合は分岐したくて」とかの気持ちが出てくる。
頭のなかで「ここでswitchなりifが使えれば…」と思うけど、それってどう作るんだ?そもそもエラーコードはどう返ってくる??とかその辺が想像がつきにくくなっている。
それ故に「やっぱちゃんとコードで書けるようになりたい」という、知る動機付けにもなるなーと感じた。
これらをNode-RED上で実装できないのは僕がNode-REDのことをちゃんと知らないからかもしれないけど「全部コレでいいや」にならない分、良い影響を持っていると思う。
コピペしやすい
UnrealEngine4を使っていた頃は、ウェブ上で実装方法を調べても、そのノード構成をコピペで持ってくることは不可能でした(今はどうか知らないけど)。
それに対してNode-REDはノードの配置や内容をJsonデータをエクスポート・インポートが可能になっていることから、とても他の人のノードを参考にしやすい。これは良い。
こう改善されるといいなー
JSの実行コンソールがほしい
上でも書いたけど、js部分の動作が見えないから確認できるようになって欲しい
HTML部分でもifとかforとか使いたい
せっかく変数を受け取れるんだから、それを利用してhtmlの表示の制御がやりたい。手軽に。
チューターの人に聞いたら「JSでなんとかするしか無い」って言われて「えー」って思った。
Macの場合、カーソルがずれてて使いにくい
使った人みんな思っただろうけど、これほんと困る。
まとめ
学びはじめのハードルを低く、手軽に実装できるところはとっても良いと思います。
これって最終的に何を目指すものなんだろ?プロトタイピングツール?サービス実装までのレベルを目指すものなのかな?それによって今後の期待も変わるけど、モックを作るとか、とりあえず見せるものを作るのにいいなーって思います。
2007年から登録していたニコニコプレミアムを解約した。
僕はニコニコは見る専なんだけど、理由はあんまり見てて面白くなくなってきたなーとぼんやり思ったからだ。
そこで、ニコニコプレミアムを解約した。
そして解約して2週間くらいは経ったけど、解約してからかなり動画を見る数が減った。
理由のひとつとして、
「動画を見るのがさっぱり快適ではない」
というのがある。
プレミアムじゃないとニコニコ動画は
・シーク出来ない
・回線速度が遅い
という利用制限が発生するんだけど、
その「シーク出来ない」がクッッッッッソすぎる。
前まではそれなりに面白くない動画でも、シークさせながら最後までなんとなーく見ていたんだけど、プレミアムじゃなくなりそれが出来ないとなると、つまんなくなってきたらソッコーで動画を閉じる、ということが起きる。
そしてそれを繰り返していると、気づくのが
「ほんとに面白いと思ってるモノしか見なくなる」ってことが起きる。
快適じゃないから「とりあえず見てみるかー」が発生しないんですね。
おかげでぼんやりなんとなく動画を見る時間が減り、
何か行動する時間が増え、
部屋を掃除し、
ご飯を作り、
洗濯をし、
出かけて、
本を読むようになった。
あれ?
動画見てた時間ってほんとに時間の無駄だったんじゃねーか?
ニコニコが好きだったから2007年から9年間毎月500円を払ってきた。
6000円 * 9 = 54000円を払ってきたわけだけど、
実は時間とお金を浪費しただけだったのでは、と思い始めてきた。
この状況を振り返ると、小中学生たちがYoutubeを見るという気持ちは凄くよく分かる。
ニコニコは月額を払うことの出来る大人たちのメディアになり、
Youtubeは無料ながらも広告を見ることで快適な動画環境で動画を見ることができる。
だってニコニコユーザーチャンネルは更に月額払わないと見れないしさ。
そんなのを子どもたちはどうやって見るのさ。
ニコニコが古いのではなく、ニコニコが新しい子どもたちを拒絶しているのではないか?
という気がする。
その月額500円で儲けてきたから今更離れられないのかもしれないけど、ニコニコが廃れていくのはしょうがないのかなーと思った。
CouchbaseMeetUpで発表してきました。
簡単にまとめると
・SyncするならCBL
・しないならRealm
って結論に至った。
ちょっとハマったからメモ。
「~」
Unicode (UTF-8) – utf-8
判定詳細
キリル言語 (Windows) – windows-1251
→読み上げない
「〜」
Unicode (UTF-8) – utf-8
判定詳細
OEM キリル – IBM855
→「から」と読み上げる
後者でやる分には全く問題ないんだけど、前者は文字列に1文字でも入っていると、文章全体を読み上げないという事態になります。
pythonのreplaceで適当な文字列に変換してあげると良いです。
OVRPlayerControllerをWindowsで360コントローラ使用時、下ボタンを押すと震えます。
これは、XBox360コントローラの下ボタンに、向きをリセットする機能が割り当てられているためです。
メニューを動かすためにこの360コントローラの十字ボタンを使っていたんだけど、動かすたびに震えるので抜くことにしました。
対処する場所は、
OVR > Scripts > OVRMainMenu.cs
の1042行目をコメントアウトするとこの機能を消すことが出来る。
正直ココを消すことで何か問題になるかどうかまだちゃんと調べきれていないけど、今のところ何ら問題なく動いてます。
何か問題が出た場合は修正します。
もしくは問題があるよとご存じの方はコメントくれたりすると嬉しいです。
ともかく今のところこれで対処しておる。
こちらのページを参考にしました。
http://naichilab.blogspot.jp/2013/07/unity.html
カメラを複数配置して、上に重ねたいサブカメラのdepthを、メインカメラより大きくする。
(メインカメラが0なら、重ねるカメラは1以上にする)
そしたら、サブカメラのClearFlagsをDepthOnlyに変更することで可能になる。
GUIを最前面に常においておきたいときなどにとっても便利。
ちなみにOculusRiftを利用時、OVRCameraControllerを使ってでも利用可能。
SkyboxMesh.csを利用している場合は、OVRCameraControllerのカメラがMainCameraタグになっているとバグるので、別のタグにしてやる必要がある。
具体的に追加されてたものだけの紹介です。
他にももちろんいろんな機能があります。
今はローカルのDBってそこまで活用されてはいないかもしれませんが、ずいぶん手軽に使えるので、これがきっかけに使い始められるといいなあ。
どうも、unity初心者です。
さっき書いた方法だと、移動させることはできるけど、当たり判定とかMassとかを無視して動かしちゃうから、色々と都合がわるいことが起きた。
調べてたらJointって機能を使ってみればいけるんじゃ?と考え試してみたらビンゴだった。
これで、スカイリムとかオブリビオンとか、FallOutみたいな物の移動ができる!やったぞー!
以下ソース
※前提として、カメラにFixedJointを追加し、Is Kineticをtrueにしています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma strict | |
private var center : Vector3; | |
private var target : GameObject; | |
private var moving : boolean; | |
private var fixJoint : FixedJoint; | |
function Start () { | |
center = Vector3(Screen.width/2, Screen.height/2, 0); | |
moving = false; | |
} | |
function Update () { | |
Screen.lockCursor = true; | |
if (Input.GetButtonDown("Fire1")) { | |
move(); | |
} | |
} | |
function move() { | |
var ray : Ray; | |
var hit : RaycastHit; | |
ray = Camera.main.ScreenPointToRay(center); | |
// 何かにぶつかったら gameObject を取得 | |
if (Physics.Raycast(ray, hit, 30)) { | |
print("hit!"); | |
target = hit.collider.gameObject; | |
Debug.Log(target.tag); | |
if(target.tag == "Target"){ | |
var camera : GameObject; | |
camera = GameObject.FindWithTag("MainCamera"); | |
fixJoint = camera.GetComponent("FixedJoint"); | |
if(fixJoint.connectedBody && moving){ | |
moveOut(); | |
} | |
else{ | |
fixJoint.connectedBody = target.rigidbody; | |
moving = true; | |
} | |
} | |
else{ | |
moveOut(); | |
} | |
} else { | |
print("not hit!"); | |
if(moving){ | |
moveOut(); | |
} | |
} | |
} | |
function moveOut(){ | |
fixJoint.connectedBody = null; | |
target = null; | |
moving = false; | |
} | |
これで、Massも、当たり判定もすべて考慮されてものを動かすことが出来る。
ブルブルするけどねw
オブリビオンみたいに、物を持ち上げられないかなーと研究中。
オブリビオンみたいにはできてないけど、暫定で出来たものが以下。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma strict | |
private var center : Vector3; | |
private var target : GameObject; | |
private var moving : boolean; | |
function Start () { | |
center = Vector3(Screen.width/2, Screen.height/2, 0); | |
moving = false; | |
} | |
function Update () { | |
Screen.lockCursor = true; | |
if (Input.GetButtonDown("Fire1")) { | |
move(); | |
} | |
else if(Input.GetButtonDown("Fire2")){ | |
if(moving){ | |
targetThrow(); | |
} | |
} | |
} | |
function move() { | |
var ray : Ray; | |
var hit : RaycastHit; | |
ray = Camera.main.ScreenPointToRay(center); | |
// 何かにぶつかったら gameObject を取得 | |
if (Physics.Raycast(ray, hit, 10)) { | |
print("hit!"); | |
target = hit.collider.gameObject; | |
//targetGameObject = hit.collider.gameObject; | |
Debug.Log(target.tag); | |
if(target.tag == "Target"){ | |
var camera : GameObject; | |
camera = GameObject.FindWithTag("MainCamera"); | |
if(target.transform.parent == camera.transform){ | |
var movable : GameObject; | |
movable = GameObject.FindWithTag("MovableObjects"); | |
target.transform.parent = movable.transform; | |
target.rigidbody.isKinematic = false; | |
target = null; | |
moving = false; | |
} | |
else{ | |
target.transform.parent = camera.transform; | |
target.rigidbody.isKinematic = true; | |
moving = true; | |
} | |
} | |
} else { | |
//targetGameObject = null; | |
print("not hit!"); | |
} | |
} | |
function targetThrow(){ | |
var movable : GameObject; | |
movable = GameObject.FindWithTag("MovableObjects"); | |
target.transform.parent = movable.transform; | |
target.rigidbody.isKinematic = false; | |
// 放り投げる | |
var direction : Vector3 = transform.forward; | |
target.rigidbody.velocity = direction * 10; | |
target = null; | |
moving = false; | |
} |
これだと、対象のものを持ち上げて移動し、落とすことは出来るんだけど、持ち上げている時にメインカメラの子にしているから(?)持ち上げ時にものを通り抜けてしまう。
やりたいのは、持ち上げた時にものが通らなかったら通らなくしたい。スターウォーズのフォースみたいに遠隔で持ち上げて操作するっていうのがしたいんだけどどうしたらいいんだろ…。うーん…。
新たに追加されたクラス、CBLUITableSourceを動かしてみた。
どういうものかというと、UITableViewのDataSourceとCouchbaseLiteのLiveQueryを組み合わせたもの。
CouchbaseLiteにドキュメントを追加すれば、すぐにそれをTableViewが検知して、UITableViewにそのデータが反映される。
Couchbaseが配布している、ToDoLiteにも使われているやつです。
XIBでの利用を前提に作っているらしいのでそれにしたがって設定してみた。
各種環境はできている前提です。
動かしてみよう
まずはプロパティ宣言。
IBOutletを忘れずに。
@property (nonatomic) IBOutlet CBLUITableSource* dataSource;
そしてXIBを開き、ViewにUITableViewを配置。
そしてObjectsにNSObjectを配置し、ClassをCBLUITableSourceに変更します。
そして、さっき配置したUITableViewを以下のように接続
★TableView:
dataSource:さっき配置したCBLUITableSource
delegate:FilesOwner
tableView:CBLUITableSource内にあるtableView
★FilesOwner
dataSource:さっき配置したCBLUITableSource
そして最後に、ViewDidLoadなどで
self.dataSource.labelProperty = @"text"; // Cellに表示したいドキュメントのプロパティ名 AppDelegate *ap = ApplicationDelegate; if (_dataSource){ _dataSource.query = [[ap.database queryAllDocuments] asLiveQuery]; //ここがライブクエリ設定部分 _dataSource.query.descending = NO; }
としてあげれば準備完了です。
あとはドキュメントを追加したりすれば勝手にUITableViewの更新などをやってくれます。お手軽!
UITableViewの見た目や設定を変更したければ、Delegateメソッド用意しているからオーバーライドしていじってみてね、と公式にはあります。
手軽なだけ?
で、手軽にここまで出来るのはいいんですが、実際カスタムするとなると今まで慣れ親しんできたやり方でやりたいなーと思った。
この実装がこの手法でしかできないかって言うとそういうわけでもないですし。
普通に、UITableView配置して、LiveQuery作成して、その通知拾って、更新かける、というのを自分で書いちゃえばいいし、そっちのが今ロールしやすくない?って思っちゃう。
もしかするとそのデリゲートメソッドになにか便利なものがあるかもしれないからなんとも言えないですけどね。
ともかく今回は触ってみたってことで。
いま、ごちゃごちゃと触って記事をまとめているのですが、面白かったからこれだけ先に書きたい。
Couchbase LiteのベータがCouchbaseから配布されています。
以前、Couchbase Meet Up で発表をしたのですが、その時からパワーアップしていました。
ほんでちょいちょい触ってみたのですが、CBLModelを利用したリレーションシップが面白い!
そもそも、前回発表した時は、ドキュメント自体はModelを利用せず、NSDictionaryを利用して使っていました。
しかし、CBLModelを利用すれば、
@interface ShoppingItem : CBLModel @property BOOL check; @property (nonatomic, copy) NSString *type; @property (nonatomic, copy) NSString *text; @property (nonatomic, copy) NSDictionary *dictionary; @property (nonatomic, copy) NSArray *array; @property (nonatomic, strong) NSDate *created_at; @end @implementation ShoppingItem @dynamic type, check, text, created_at, dictionary, array; @end
とすれば、簡単にモデル化でき、あとはこのモデルを初期化して、値を入れてセーブすれば保存が完了します。
以下のように
// カラのmodelを作成 ShoppingItem *item = [[ShoppingItem alloc] initWithNewDocumentInDatabase:ap.database]; // プロパティに各種値を入れる item.type = @"spItem"; item.text = numberStr; item.check = NO; item.created_at = [NSDate date]; item.dictionary = @{@"key": @"value", @"key2":@"value2"}; item.array = @[@"obj1",@"obj2",@"obj3",@"obj4",@"obj5"]; // Save NSError *error; BOOL ok = [item save:&error];
NSDictionaryで管理するよりめっちゃ簡単。CBLModelを利用すれば、NSDataだって、かってにbase64化してくれるので画像保存もできます。
そして面白いのが、リレーションシップを組めるということです!
新たに別のモデル、ShoppingItemHoldというモデルを作成します。
これは、上記のShoppingItemというモデルに一つだけBoolを追加したものとします。
その場合、以下のように宣言します。
@class ShoppingItem; @interface ShoppingItemHold : CBLModel @property (nonatomic, copy) NSString *type; @property (assign) ShoppingItem *item; @property BOOL hold; @end @implementation ShoppingItemHold @dynamic type, item, hold; @end
先ほど作ったModelをそのまんまプロパティとして宣言します(ここはassignのほうが良いと公式でありました)
そうすると、同期先(CouchDBとか)では、その対象となるDocumentIDが保存され、Xcodeプロジェクト内では、そのIDのShoppingItemが勝手に生成されます。
以前NSDictionaryで組んでいた自分としては衝撃的です。だってすんごいめんどくさかったんだもん。
それをたったこれだけで解決できるなんて…。なんていいやつなんだ!
NoSQL、とくにCouchbaseはリレーションが組みにくいって話を1年位前に聞きましたが、こうやってサクッとリレーションが組めるように成って来ているのはいい流れですね。
ドキュメントの構成も考えやすくなるね。
iOS7で、画面左上を起点とするレイアウトが追加されてレイアウトがずれたりします。
今まで知らずに、無理やりレイアウト変えてたんですが、以下でさっくりできました
if ([self respondsToSelector:@selector(edgesForExtendedLayout)]){ self.edgesForExtendedLayout = UIRectEdgeNone; }
iOS7で「edgesForExtendedLayout」というプロパティが追加されてたんですね。知らんかったわ…。
金額とかにカンマ入れますよね。
それって実は簡単にできるようになってた。
以下な感じ。
int num; // 数値にカンマを追加 NSNumberFormatter *formatter = [NSNumberFormatter new]; [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; NSString *sPformatted = [formatter stringFromNumber:[NSNumber numberWithInteger:num]]; NSString *price = [NSString stringWithFormat:@"¥%@",sPformatted];
;
調べてみるとこのNSNumberFormatterって色々できるのね。すごい。
その他は以下のほうが詳しい。とても参考になります。
UIWebViewだってトークン付けたい!
そんな時には以下のようにすれば付与して動作させることができます。
簡単にロジックを説明すると、shouldStartLoadWithRequest時に、トークンがヘッダについてなければ付けて再リクエストしてReturn NO。
そしてついていればYES、とする。
2回リクエスト投げる感じになるのがなんだか気持ち悪い感じになるけど、これで動作はする。
何か他にいい方法ないのかなあ。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ // リクエストにトークンを付与する。 // ヘッダ内にトークンがあればYES。なければトークンを付与してもう一度投げる NSDictionary *headers = [request allHTTPHeaderFields]; BOOL hasToken = NO; for (NSString *key in [headers allKeys]) { if ([key isEqualToString:@"AccessToken"]) { hasToken = YES; break; } } if (!hasToken) { // いまのHTTPメソッドを保持 NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:request.URL]; req = [Global addTokenAndKEY:req]; // トークン付与メソッド if (!req) { return YES; } [req setHTTPMethod:@"POST"]; [req setHTTPBody:request.HTTPBody]; [webView loadRequest:req]; return NO; } return YES; }
iOS7リーク問題2。
これはシミュレータだけで起きる問題だけど、
[UIImage imageNamed:]
これをすることで一気にCGホニャララ系のリークが出ます。
これのとりあえずの解決法です。
簡単に言うと
・DeploymentTargetを7にして
・Asset Catalogを利用する
ことで解決します。
過去のアプリのアップデートで無理に対応しようとすると、今までプロジェクトに置いていた画像系をみんなImageAssetに変更してやる必要があります。
実機では起きないリークなので、無理に対処する必要はないかもしれませんが…。