Goalist Developers Blog

Doma-GenでMySQLのテーブルCOMMENTを取得する(Doma2)

どうも。ゴーリストのJPです。

今回はDoma2でJavaソースを自動生成する際に、MySQLのテーブルCOMMENTが取れなくて困ったので、解決方法を書きたいと思います。

Doma2とは

JavaのORマッパーです。
DBを読み込み、EntityやDaoクラスのJavaソースの自動生成もしてくれます。
Welcome to Doma — Doma 2.0 ドキュメント

環境情報

  • MacOS Sierra 10.12.6
  • Java8
  • doma-gen 2.16.1
  • mysql 5.6.29

何が起きたのか

Entityクラスのテンプレートファイル(Free Marker)のJavadocにテーブルCOMMENTを使用します。
テーブルCOMMENTにはテーブルの論理名が登録されています。

  • テーブル名:sample_table
  • テーブルCOMMENT:サンプル

entity.ftl

/**
 * ${comment}テーブルエンティティ
 */
// @アノテーションは省略
public class <#if entityPrefix??>${entityPrefix}</#if>${simpleName} implements BaseTableEntity, EntityTrail {

実行結果

/**
 * テーブルエンティティ
 */
// @アノテーションは省略
public class SampleTable implements BaseTableEntity, EntityTrail {

テーブルCOMMENTが反映されていません。。。
本来なら「サンプルテーブルエンティティ」となってほしいです。

原因

MySQL用のjdbcDriverの不具合のようです。

MySQL Bugs: #65213: Connector/J does not retrieve the table comment in a InnoDB table

対策

DB接続時のuseInformationSchemaプロパティを「true」に指定すると不具合を回避できるようです。
具体的には、以下の手順で指定します。

GlobalFactoryを自作する

GlobalFactoryとはGenタスクで使用する各種クラスを生成するクラスです。

doma-gen/GlobalFactory.java at master · domaframework/doma-gen · GitHub

上記のGlobalFactoryを継承して、DataSourceクラスを生成するメソッドをOverrideします。
ここで、useInformationSchemaプロパティを「true」に指定します。

public class SampleGlobalFactory extends GlobalFactory {

  @Override
  public DataSource createDataSource(Driver driver, String user, String password, String url) {
    MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    mysqlXaDataSource.setUseInformationSchema(true);
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    mysqlXaDataSource.setUrl(url + "?useUnicode=true&characterEncoding=UTF-8");
    mysqlXaDataSource.setUser(user);
    mysqlXaDataSource.setPassword(password);
    return mysqlXaDataSource;
  }

}

Genタスクのパラメータを追加する

Genタスクの書かれたbuild.gradleにglobalFactoryClassNameパラメータを追加します。

task gen << {
  // 省略
  ant.gen(
        url: 'jdbc: sample',
        // 省略
        globalFactoryClassName: 'path.to.SampleGlobalFactory'
  )
}

Genタスクで使用するGlobalFactorySampleGlobalFactoryに変更してくれます。
これにより、useInformationSchemaプロパティが「true」のDataSourceを使用してMySQLに接続してくれるようになります。

再実行

ちゃんと出ました。

/**
 * サンプルテーブルエンティティ
 */
// @アノテーションは省略
public class SampleTable implements BaseTableEntity, EntityTrail {

まとめ

意外と調べてもこの方法が出てこなかったので、書いてみました。
調べるまでもないことなのか、テーブルCOMMENTはGenタスクで使われないのか、調べる能力が足りないのか、、、
この記事が誰かの役に立つことを願っています。

ファイルを出力してアプリ内に保存してみた@Swift3

こんちは。渡部です。

f:id:watabe1028:20170915114242p:plain

現在社内アプリを(勝手に)作っています。
そこでファイル出力させようとしたんですが
Objective-CではよくやってたんですがSwiftになってからは初めてだったんで
まとめてみました。
今回は任意のタイミングでテキストファイルやPDFファイルを作成する方法です。
アプリ内に保存した後、そのファイルの確認方法もついでに載せときます。
 

 

準備

まずは適当なプロジェクトのViewControllerに弊社サービスを牛耳る(三回目)ふろぐんのimageViewを貼り付けます。
ついでにUILabelも。
 
f:id:watabe1028:20170914150407p:plain

準備はこれだけです。
このあとはコーディングです。

 
 

文字列をテキストファイルに出力する方法

適当な文字列をテキストファイルに出力します。
今回は「HRogよろしくね!」という文字列を「sample.text」というファイルに出力します。

override func viewDidLoad() {
    super.viewDidLoad()
        
    self.createTextFromString(aString: "HRogよろしくね!", saveToDocumentsWithFileName: "sample.text")
}

// textファイルの出力
func createTextFromString(aString: String, saveToDocumentsWithFileName fileName: String) {
        
    if let documentDirectoryFileURL = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last {
            
        // 書き込むファイルのパス
        let targetTextFilePath = documentDirectoryFileURL + "/" + fileName
            
        do {
            try aString.write(toFile: targetTextFilePath, atomically: true, encoding: String.Encoding.utf8)
        } catch let error as NSError {
            print("failed to write: \(error)")
        }
    }
}

パスの指定はいくつか方法がありますが今回はStringで指定しています。
基本的にはStringかURLで指定するのが一般的だと思います。多分。
 
 

表示中の画面をPDFファイルに出力する方法

現在表示中の画面をPDFファイルに出力します。
「現在表示中の画面==self.view」ということにします。
ファイル名は「view.pdf」です。

override func viewDidLoad() {
    super.viewDidLoad()
                
    // textファイルの出力
    self.createTextFromString(aString: "HRogよろしくね!", saveToDocumentsWithFileName: "sample.text")

    // PDFファイルの出力
    self.createPdfFromView(aView: self.view, saveToDocumentsWithFileName: "view.pdf")
}

// pdfファイルの出力
func createPdfFromView(aView: UIView, saveToDocumentsWithFileName fileName: String) {
        
    let pdfData = NSMutableData()
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil)
    UIGraphicsBeginPDFPage()
        
    guard let pdfContext = UIGraphicsGetCurrentContext() else { return }
        
    aView.layer.render(in: pdfContext)
    UIGraphicsEndPDFContext()
        
    if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
        let documentsFileName = documentDirectories + "/" + fileName
        pdfData.write(toFile: documentsFileName, atomically: true)
    }
}

テキストファイルとは違い
・StringではなくData
・UIGraphicsBeginPDFContextToData
を使います。

基本的にソースをコピペすればそのまま使えるように書いてるので
詳細な説明は面倒なのでしません。

このソースで実行するとアプリ起動時にファイルを作成し、
アプリ内に保存されます。  
 

アプリ内に保存したファイルを確認する方法

先ほど保存したアプリ内のファイルをMacで取り出して確認します。
想定した通りに動けば「Documents」直下に「sample.text」と「view.pdf」が保存されてるはずです。
それでは確認手順です。
今回は実機で試した場合です。

iPhoneをMacに繋いだ状態で
Xcodeの「Window」「Devices」を選択します。

f:id:watabe1028:20170914154617p:plain

すると「Devices」ウィンドウが開くので
左ペインの「DEVICES」で自分のiPhoneを選択します。
「Installed Apps」から実行したアプリを選択し、
下の設定ボタンを押下。

f:id:watabe1028:20170914154642p:plain

「Download Container」で適当な場所にファイルをダウンロードします。

f:id:watabe1028:20170914153602p:plain

ダウンロードしたファイルを右クリックし、
「パッケージの内容を表示」を選択します。

f:id:watabe1028:20170914153637p:plain

するとアプリ内のファイル格納ディレクトリがあるので
「Documents」まで行くと、、、

f:id:watabe1028:20170914154034p:plain

テキストファイル、ちゃんとできてる!

f:id:watabe1028:20170914153725p:plain

PDFもできてる!

f:id:watabe1028:20170914153742p:plain

うぇい
 
 

ちなみにアプリ内にファイルを保存できるディレクトリは3つあります。
各ディレクトリに意味があるので注意が必要です。
もちろんリジェクト対象です。
 
Documents
・ユーザが作成したデータを保存する場所
・ユーザがアクセスできる
・ゆえに見せても構わないファイルのみにすること
・iTunesでバックアップされる

Library
・ユーザのデータファイル以外を保存する場所
・ユーザに見せたくないファイルを保存する
・ユーザデータのファイル保存には使ってはいけない
・iTunesでバックアップされる

temp
・一時ファイルを書き込むための場所
・次に起動するときには消えてる
・アプリが動作していないとき、システムが全て消去することがある
・iTunesでバックアップされない

 
 

まとめ

基本的にユーザのデータはUserDefaultsに保存してますが
たまにファイルに書き出して保存したい時があります。
そんなときにファイルどうやって書き出すんだっけ?というのを防ぐために
自分のためだけに書きました。
うぇい!

数学オタクが、開発ブログで、...書いたぁ~!

初めまして,ゴーリストの新卒エンジニアのナカノと申します.

突然名乗ったからに「えっ,君誰やねん」と思われるかもしれません.こんな奴です.

f:id:r-nakano:20170912105906j:plain

これは、課題発表時の画像なのですが,何だか「ラッパーか,君は?」という感じですよね….

…というわけで,今回は初回投稿ということで私に関することを幾つか述べようかなと思います.

目 次
  • 自己紹介   
    • プロフィール
    • 大学で何してたの?
    • 数理病棟で何してたの?
  • 入社までの経緯
    • 就活をするも,博士進学を希望するようになる
    • 再び就活をするも,数学ロスな状態に陥る
    • 数学ロスから脱出し,ゴーリストへ入社する
  • 実際,ゴーリストってどうなの?
    • 個性的な人
    • 受容力ある人
  • 入社後から今に至るまで
    • 技術研修
    • 職種別研修
    • そして現在へ
  • 今後について

自己紹介

プロフィール

  • 年齢:26歳

  • 最終学歴:修士(某大学の数理病棟を退院)

  • 趣味:読書,視聴(音楽・動画・アニメ・映画),酒を飲む,数学質問掲示板での回答

  • 性格:MBTIによれば,INTJまたはINTPとのこと

大学で何してたの?

大学では,数学科に所属していました.数学科では,主に以下のカリキュラムで専門講義を学習しました.

  • 1年次:基礎講義(数学の入門的内容),線形代数学,微分積分学

  • 2年次:線形代数学,微分積分学,集合論,位相論,複素関数論

  • 3年次:代数学,解析学,幾何学,複素解析学,確率論,数理論理学

  • 4年次:ゼミ

上記は必須のものですが,これら以外にも「専門講義の選択講義」や「集中講義」なども都度に受講しておりました.

ゼミについてですが,数学科の場合は「輪読形式」で進められます.また,指導教官の専門分野の関係で,鏡映群とコクセター群(Reflection groups and Coxeter groups)に関することを1年間勉強しておりました.

数理病棟で何してたの?

数理病棟では,専門講義・集中講義への受講,自主セミナーへの参加,ゼミでの勉強・研究の活動を行っておりました.ゼミの内容に関しては,概ね次の通りです.


修士1年~修士2年の前半

シンプレクティック幾何学(Symplectic geometry)の基礎事項の学習

修士2年の後半

反復積分とモノドロミー表現(Iterated path integrals and monodromy representations)に関する研究


修士2年の前半までをシンプレクティック幾何学について勉強していたのは,指導教官がその分野の専門家だったからです.

入院時はこの分野に対してワクワクした気持ちで一杯でしたが,その難しさにより知識の習得が非常に難航しました.

そのことも理由としてあるのですが,個人的な興味もあって,修士2年の後半では「異分野の内容」を研究内容として選びました.

修論のネタ探しは本来は独力で行わなければならないのですが,教授のご助言により何とかネタを見つけることが出来て,拙いながらも「修士論文の作成・提出・発表」を完遂することが出来ました.

入社までの経緯

就活をするも,博士進学を希望するようになる

大学に残るか,それとも就職するかで非常に迷いました.それで,一旦は修士2年の時に就活をしていました.

ところが,時間が経つにつれて進学したい気持ちが強くなり,最終的には「博士課程への進学」を希望するようになり,修論提出後に「博士課程の進学試験」を受けました.

再び就活をするも,数学ロスな状態に陥る

しかし,残念ながら不合格となりました.合格発表の紙を見た時はまさに茫然とした状態になりましたが,学部と院で奨学金を借りていたこともあり再び就活を始めることになりました.

大学院を出た後は就活を行っていたのですが,徐々に数学ロス(数学を研究する環境から離れたことで喪失感を抱き,虚無的な状態に陥るさま)な状態になってしまいました.

心の中では「数学ロスを解消しようとする願望」「現状を何とかすべきだという思い」が共存し,それらの葛藤に苛まれ,非常に辛いものがありました.

数学ロスから脱出し,ゴーリストへ入社する

そんな中,ある時に「とある就活イベント」があることを偶然知りました.怪しげに思う一方で興味があり,思い切ってそこに飛び込んでみました.

その場では様々な興味深い方々と出会うことが出来て,企業とのセッションの際にゴーリストのCOOの方とイイオさんに出会いました.そこからは何とか最終面接まで進み,ご縁があってゴーリストから内定を頂くことが出来ました.

その後,入社に関する諸手続きを行い,物件探し・引越しの準備などに追われました.特に,後者に関しては両親や祖父母に協力して頂いた部分が多々あり,今でも感謝しています.そして,4月にゴーリストに無事入社することが出来ました.  

実際,ゴーリストってどうなの?

個性的な人

COOの方からは「うちは個性的な人ばかりだから」と聞いていたのですが,何人かはマトモな方がいらっしゃるだろうと思っていました.そして,いざ入社してみると全員個性的でした.笑

これは別にディスっているわけではなくて,各々の活躍しやすいフィールドで各々が本来的な能力を活かせている,という意味で全員が個性的だということです.

受容力ある人

また,受容力のある人が多いなと思いました.ここでの受容力とは「あなたが,あなたらしいという部分を以って,あなたとしてそこにいる」ということを「在るがままに認識し,当然の如く認める」ということが出来る能力のことです.

相手のことを受け入れるか否かではなく相手を認める,ということは意外にもなされていない様に思います.

しかし,ゴーリストの方々はどんな人であろうとまず相手のことを認め,そして相手と向き合うということが出来る方ばかりです.

だからこそ,各々が個性的であっても全体としてうまく機能しているのだろうと思います.逆説的ですが,「集団」として上手く機能するためにはその構成要素である「個」が個性的でなければならない,ということを再認識しました.

入社後から今に至るまで

技術研修

入社後の約2カ月間は,エンジニアとしての基礎を学ぶ日々に追われました.技術研修では,プログラミング演習・Crawling/Scraping・インフラ・DB・API・アプリケーション開発など,一通りのことを学習しました.

特に,プログラミングに関しては論理を構築することは容易かったのですが,構築された論理に対応するプログラムの作成が思いの外難しかったです.

また,Crawlingに関するサービスの開発課題に取り組んだ時は,目標がなかなか定まらず苦戦を強いられました.

ただ,この経験があって,アプリケーション開発の課題では「目標を定めて逆算的に取り組む」ことが出来たかなと思います.

職種別研修

職種別研修では,データをCrawlingしてScrapingする作業を体験しました.そこでは,主に設定の仕方設定の追求の難しさを体感しました.

そして,研修の後は同職種でより実践的な内容に取り組みました.研修時とは異なり様々な要素が実務に絡んでくるため,「前提を鵜呑みにせずに常に疑う」ことや「物事を曖昧にしない様に他者と一緒に明らかにしていく」ことを学びました.

そして現在へ

実は偶然にもチャンスが到来し,現在は日々をインフラエンジニアの修行に励んでおります.今のところは,AWS・Infrastructure as Code・Securityなどに関してキャッチアップし,実務をこなしております.

毎日が勉強の連続であり大変ですが,周りにいらっしゃる優秀な方々のお陰で修行になんとかついていけています.

また,物を開発するよりも論理体系を構築することに元々興味があったため,修行を楽しんで取り組めています.

今後について

現在はインフラエンジニアの卵として日々を過ごしています.ですが,将来は「一つのことに長けたエンジニア」ではなく「様々なことをある程度の深さまで熟知している分野横断的なエンジニア」になることを目指しています.

一般に,研究者の場合は「一つのことに深く秀でているかどうか」という深淵的な専門性が非常に求められます.

しかし一方で,開発者の場合は「辺り一面を広く見渡し,様々なことに熟知している」という俯瞰的な専門性が重要であると思われます.

ゴーリストの掲げる「T字型人間」を体現する存在となるべく日々の中で俯瞰的な専門性を鍛え,何処に行っても活躍できるエンジニアになりたいです.

中東採用のエンジニアの初ブログ!

初めまして!

ゴーリストに最近入社した、アメリカ生まれのヨルダン人です!Tinker(ティンカー)と呼びたい人は是非!

プロとしてのエンジニア歴が4年で、日本や海外の様々な会社で働いてきました。そして、望んでいたものが見つかり、ゴーリストに入社しました。 周りのエンジニアと会話し、学び、成長し、やりたいことをチャレンジ出来る環境と知識のパワーハウス。なかなか見つけられない場所です!

アバウトミー

  • 年齢:27歳

  • 学歴:ヨルダンでコンピューターサイエンスのBA(学士)を取得

  • 趣味:アニメ、ゲーム、開発、サッカー、卓球、ライフトーク

仕事で今まで触れてきた技術

  • 言語:Java, Perl, Ruby, PHP, C++, Javascript.

  • フレームワーク:Spark, Android, Rails, Sledge, CakePHP, FuelPHP.

  • CI: CircleCI.

  • DB: MySQL, PostgreSQL, Oracle DB.

  • CDN: Cloudfront.

  • VCS: Github, Gitlab.

  • サーバー環境:EC2.

ワーキングバックグラウンド

  • 運送系システムの開発(ウェブ)

  • コンテントマネージメントシステム(SNS系システム)の開発(モバイル/ウェブ)

  • 介護システムの開発(ウェブ)

  • モバイルキャリア会社のVAS(Value Added Services)の開発(ウェブ)

  • ECシステムのポイント制度の開発(ウェブ)

色んな技術に触れてみましたが、やっぱりまだまだITは新しいものが当たり前なように増えていく業界であり、様々なことを勉強できてめっちゃ楽しいです!!

本題のネタは、、、AndroidをKotlinで書く!

今までみてきた公開されているプロジェクトを参考にしつつ、色んな方に相談して開発のチュートリアルシリーズを作ろうと思っています。 そのためにこのシリーズ用のGithubアカウントを作っていましたが、まだリポジトリを作ってないのです。

後ほどリポジトリにコードを追加しながらブログでフォローしようと思っていますので、楽しみにしてください!
名付けて、、、”飯食い怪獣のKotlin入門”です。

f:id:TinkerJack:20170907160809j:plain

Kotlinとは?

簡単に言うと、Kotlinは2011に公開された静的型付き言語です。 グーグルがKotlinをAndroidの正式な開発言語として認め、発表したのでAndroidStudio 3.0からデフォルトでKotlinサポートが入っています!!

”そんなのWikipediaを見ればわかるわ!”と思う方もいらっしゃると思うので、次の文書を読んでみてください!

なぜKotlinが流行っているのか?

  • Javaより綺麗なコードが書けるからです。

  • Kotlinだと関数型プログラミングも出来るし、オブジェクト指向プログラミングも出来ます!

  • KotlinはJavaと同じくJVM上で動いているので、上の二つの利益を得るためにJavaを楽にKotlinにインテグレーションすることが出来ます。

Kotlinってどんな形をとってる?

伝統的なアプローチで申し訳ありませんが、簡単なハローワールドで行きまーす

Java

// Hello World in Java
public static void main(String args[]){
  String world = "World";
  System.out.println("Hello," + world + "!");
}

Kotlin

// Hello World in Kotlin
fun main(args: Array<String>) {
   val scope = "world"
   println("Hello, $scope!")
 }

初記事が終わりを迎えますが、また次から本格的なKotlinの開発をやるのでお楽しみにしてください!

記事用のGithub URL:

github.com

ではでは、また後ほど!

P.S:タイトルの漢字ミスはわざとなPlay on Wordsですw。

ゴーリストエンジニア
イムラーン

teamLab Jungle in 渋谷ヒカリエ に行ってきた

こんちは。渡部です。

f:id:watabe1028:20170904161106j:plain

今回はイベントレポートです。
勉強会でもなんでもないただの遊びですw

こちらに行ってきました。

www.team-lab.com

ネタバレ注意です。
また使ってはいけない画像があったらご指摘ください。
すぐに削除します。

学ぶ!未来の遊園地

まずは「学ぶ!未来の遊園地」。
遊園地でお絵かき。

この絵が・・・
f:id:watabe1028:20170904160913j:plain

こうなった!
f:id:watabe1028:20170904160953j:plain

どうなるかわかっていても楽しい!
ただし見つけるのが難しい!
弊社のキャラクター「ふろぐん」を描いて見たものの、
見つけることができず。。。
きっとワニに捕食されたのでしょう。。。

Art Night

「Art Night(アートナイト)」(夜の部)が始まりました。
「光のアートに包まれる超幻想空間での体験型音楽フェスティバル」
とのことで光と音楽でテンション上がります!
f:id:watabe1028:20170904161202p:plain

カラフルになったり、
f:id:watabe1028:20170904161230p:plain

ドクロがきたり、
f:id:watabe1028:20170904161300p:plain

お花がきたりしました!
f:id:watabe1028:20170904161333p:plain

きました、チームラボボール
f:id:watabe1028:20170904161414p:plain

すごく綺麗!
みんなのテンションが一気に上がります。
f:id:watabe1028:20170904161447p:plain

すぐにチームラボボールで埋め尽くされます。
我先にと手を伸ばします。
f:id:watabe1028:20170904161531p:plain

そしてでっかいミラーボール!
煽りも入って飛び跳ねたり踊ったり!
f:id:watabe1028:20170904161559p:plain

光が反射してめっちゃかっこいい!
f:id:watabe1028:20170904161632p:plain

感想

入り口には「まだかみさまがいたるところにいたころのものがたり」があって
そこで1時間くらい時間が潰せそうです。
「小人が住まうテーブル」、「つながる!積み木のまち」も大の大人が夢中になって遊びましたw
「すべって育てる! フルーツ畑」のすべり台が結構な角度で軽くビビり、
ラストのライブで感動して帰る、といった感じです。
「RAIZIN BAR」で一杯やりたかったですが時間がないのでスルー。。。
が、帰りにRAIZINを1本もらえました。

仕事帰りに2時間ちょっと行っただけでは味わい尽くせない感じでした。
こちらのイベントは9/10までやっているので皆さんも時間があったら是非行って見てください!
(あと数日しかないww)

バックグラウンド時に画面を隠す方法

こんちは。渡部です。

今回はアプリがバックグラウンドに行った時に表示されていた画面をマスクする方法です。
多分10分あればできちゃいます。

手順

・適当に画面を用意する
・コードを書く
・おしまい

適当に画面を用意する

まず適当なプロジェクトのViewControllerに 弊社サービスを牛耳る(二回目)ふろぐんのimageViewを貼り付けます。

f:id:watabe1028:20170831164204p:plain

普通にこのままバックグラウンドに行くとこうなります。

f:id:watabe1028:20170831164219p:plain

バックグラウンドに行ったことを検知するのはAppDelegateのお仕事です。

バックグラウンドに行った際にマスクするviewを表示し、 アクティブに復帰した際にマスクするviewを非表示にします。

コードを書く

バックグラウンドに行く際に呼ばれるのが
applicationWillResignActive

復帰する際に呼ばれるのが
applicationDidBecomeActive

これらに追加していきます。
AppDelegateにviewと処理を追加します。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var backView: UIView? // マスクにするview

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        // viewを黒色に指定
        self.backView?.backgroundColor = UIColor.black
        
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // アクティブじゃなくなる際にbackViewをaddする
        self.window?.addSubview(backView!)
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // 追加したbackViewをremoveする
        self.backView?.removeFromSuperview()
    }
}

すると、バックグラウンドに行く際にこうなります。

f:id:watabe1028:20170831164237p:plain

うぇい

おわりに

むかーしむかし、セキュリティが厳しい会社のアプリを作った時、
この機能をつけた記憶があります。
この機能でセキュリティが担保できるとは思いませんが、
視覚的には効果があるので(簡単ですし)やってみるのも良いかもしれません。

iOS10でプッシュ通知を実装してみた

こんちは。渡部です。

今回はプッシュ通知についてです。
プッシュ通知はリモートだと証明書やらで非常に面倒になります。
なので比較的に簡単な実装方法を紹介します。

f:id:watabe1028:20170831132743j:plain

プッシュ通知とは?

プッシュ通知とは、システム側が外部のサーバーと連携して能動的に情報を取得してユーザーに通知する方式のことである。

www.weblio.jp

要は外部から通知が届く仕組みです。

LINEとかメッセージ届くときますよね?
あれです。

動作環境

・macOS Sierra(10.12.6)

・Xcode8.3.2(古い!)

・Swift3.0(古い!)

・実機(iPhone SE iOS10.2 古い!)

注意点

・シミュレータでは検証できないので実機でおねしゃす!

・AppleDeveloperProgramには加入済み前提で進めます。
・証明書CSRファイルは取得済み前提です。

・今回はAdHocでやります。

・訳あってiOS、Xcodeとも古いままです。

・サーバサイドはノータッチ!

プッシュ通知の種類

リモートとローカル

簡単に言うと、ローカルはアプリ側のプログラムで通知が完結するもの

リモートは外部のサーバを使って通知を可能にするもの

と自分は定義しています。

プッシュ?リッチ?ペイロード?なんぞ?


通常のプッシュ通知のみの実装がプッシュ通知、
プッシュ通知を開くとWebViewが表示できるのがリッチプッシュ、
プッシュ通知からメッセージなどのデータをペイロード取得
する通知があります。

今回は面倒なのでシンプルなリモートのプッシュ通知です。
次回はローカルの記事を書くかもしれません。
書かないかもしれません。
詳しい仕組みは偉人たちの記事を参照してください(投げやり) 。

チャット風メッセージアプリを作ります。
Web側のメッセージ送信時にアプリがフォアグラウンドにない場合に
通知を表示させるだけの簡単な仕様です。
チャット? どうやって作んの?の人はこちら

今回は画像付きで長くなるで!

手順


・APNs証明書作成
・アプリの設定

・コーディング

・実行

APNs証明書作成

AppleDeveloperにログインし、「Certificates,IDs & Profiles」を選択します。
右ペインのメニューから「Certificates」を選択し、右上の「+」ボタンで証明書を作成します。

Apple Push Notification service SSL(Sandbox)を選択し

f:id:watabe1028:20170831152540p:plain

使いたいアプリのApp IDを選択します。

f:id:watabe1028:20170831152601p:plain

このページは華麗にスルー

f:id:watabe1028:20170831152616p:plain

CSRファイルを選択すればおけ!

f:id:watabe1028:20170831152632p:plain

アプリの設定

まずプロジェクトでプッシュ通知を使うための設定をします。
プロジェクトを選択し、「Capabilities」から「Push Notifications」と「Background Modes」をONにします。
f:id:watabe1028:20170831132308p:plain

「Background Modes」は「Background fetch」と「Remote Notifications」にチェックを入れます。 f:id:watabe1028:20170831132324p:plain

コーディング


プッシュ通知の基本ですが通知を受け取るには許可が必要です。

許可をもらった後に、デバイストークンを取得してサーバサイドに渡す必要があります。
いくつか注意点があるのでコメントを読んでください。

まずはインポートとDelegateの設定です。

import UIKit
import UserNotifications  // インポートする

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, networkManagerDelegate, UNUserNotificationCenterDelegate { // Delegateの追加を忘れずに

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

didFinishLaunchingWithOptionsで通知の設定をします。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
                
        if #available(iOS 10.0, *) {
            
            //ios10
            let center = UNUserNotificationCenter.current()
            center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { (granted, error) in
                if error != nil {
                    return
                }
                if granted {
                    debugPrint("通知許可")
                    center.delegate = self
                    application.registerForRemoteNotifications()
                } else {
                    debugPrint("通知拒否")
                }
            })
            
        } else {
            // ios9
            let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil)
            UIApplication.shared.registerUserNotificationSettings(settings)
            UIApplication.shared.registerForRemoteNotifications()
        }
        
        return true
    }

通知が許可された場合の処理です。
ここでデバイストークンを取得します。
以降もずっとこのデバイストークンを使うのでUserDefaults等に保存しておきます。

    //リモート通知
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        
        // そのままだと「32bit」という文字列なので以下の処理を行います
        let deviceTokenString: String = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
        print("deviceTokenString \(deviceTokenString)")
        util.setUserDefaultsObject(value: deviceTokenString, key: udDeviceToken) // これは自前
    }

逆に通知が許可されなかった場合の処理です。

    //リモート通知を拒否したときの動作
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        
        debugPrint("リモート通知の設定は拒否されました")
    }

ここまで書けば一応動きます。
通知が来た時、通知から起動した時は
withCompletionHandlerなどを使います。
もちろん、はしょります。

実行


ビルドして実機にインストールします。
ここでビルド成功後にエラーが出たりしますがそこは頑張ってください。

多分プロビジョニングとかが悪さしてることが多いです。
今回は速攻で通知の許可アラートが表示されるはずです。
f:id:watabe1028:20170831131756p:plain

許可した後にアプリを一度バックグラウンドにします。
Web側でメッセージを送信! f:id:watabe1028:20170831131816p:plain

来た!
f:id:watabe1028:20170831131829p:plain

おまけ


サーバサイドはノータッチとしてましたが
今回はAWSのこちらを使っています。
aws.amazon.com

まとめ

準備から実装まで面倒なプッシュ通知ですが
ユーザーのアプリ復帰率を上げるためには必須と言えます。
通知自体もだんだんリッチになるので
今のうちに簡単な通知ができるようになった方が良いな、と思いこの記事を書きました。
どこかの誰かのお役に立てればと思います。