月別アーカイブ 11月 2019

著者:杉浦

KISS原則のためのデザインパターン

 デザインパターンは今までに至るまで作られてきたコーディングの際のパターンです。大体これは複数ファイルにまたがるつくりになっており、とてもとても複雑な問題をなるべくシンプルにされたパターンを知るだけで解決できるようにします。図は増補改訂版Java言語で学ぶデザインパターン入門 | 結城 浩 |本 | 通販 | Amazonから引用したFacadeパターンのクラス図です。

 KISS原則はKeep is simple stupidの略で「単純な作りを保て」という意味です。短くて意味が明確だと読み解きやすいものです。原則というだけあり、デザインパターンもとてもとても複雑な問題に対してこの原則を実現するために作られていることが多いです。
 KISSの原則 – Wikipedia
 デザインパターンの実現よりも原則の実現を優先すべきです。語弊がある文なので補足ですがコーディングで最も優先すべきは要件の実現です。動くきれいなコード>動く汚いコード>動かないきれいなコード、です。
 Gitのソースコードでも業務範囲でもこの二つについてのアンチパターンに本末転倒なパターンが時折見られます。簡単な作りで済むのにデザインパターンを適用したばかりに読み解くのが面倒になります。例えば、Facadeパターンなら次のようなものです。

// 極端にやってしまったパターン
function client() {
    $fasade = new Fasade()
    $fasade->echoA();
    $fasade->echoB();
    $fasade->echoC();
    $fasade->echoD();
}

class Fasade {
    use EchoA, EchoB, EchoC, EchoD;
}

trait EchoA {
    function echoA() {
        echo 'a';
    }
}
trait EchoB {
    function echoB() {
        echo 'b';
    }
}
trait EchoC {
    function echoC() {
        echo 'c';
    }
}
trait EchoD {
    function echoD() {
        echo 'd';
    }
}
// これで十分だったパターン
function client() {
    echo 'a';
    echo 'b';
    echo 'c';
    echo 'd';
}

 無暗にクラスを分けたせいで複雑化しています。例は極端ですがアクティブレコードのみで十分なのに、コントローラ->コマンド->リポジトリ->モデル->SQL文発行、の様な段階が過度に多いものはままあります。簡単な問題を複雑にしていないか、本当にそこまで大がかりなものを用意する必要があるのか、都度考えてコーディングしたいものです。

  • この記事いいね! (1)
asaba 著者:asaba

【cordova】scriptタグを高速で読み込む方法

アプリの高速化を計るため、devToolでどこが遅いか調べていたところ、

ログインページ辺りでjavascriptの読み込みがやや遅くなっていたので

performancesタブで詳細を取ってみました。するとやはり最初にscriptタグを

読んでいた辺りが怪しかったのでindex.htmlの中のscriptタグの

読み込みを少し修正。。。

 

修正箇所は、”text/javascript” src=”cordova.js”async>

の赤文字の部分。

text/javascriptの部分を抜き取ります

 

</pre>
<script src="cordova.js"async></script>
<pre>

 

HTML5では、scriptタグのtypeのデフォルト値が 最初から”text/javascript” に

設定されており、これが付いている状態で読み込むと、同じ属性”text/javascript” を

二回読むことになってしまい読み込み時間にラグが生まれるという訳です。

 

ここは省略して–script src=”cordova.js”async</script>–のようにすることで

速度の改善を見込むことができます。

正しくはこんな感じ↓

 

</pre>
<script src="cordova.js"async></script>
<pre>

scriptタグが多く設定している時ほど威力を体感できるのではないでしょうか。

後は古い機種(android5.0とか)でも感度良さげですね。

  • この記事いいね! (0)
村上 著者:村上

【Swift】「A background URLSession with identifier [identifier名] already exists!」警告の対処法

Swift の URLSesssion を使った POST 送信の処理を実装した際に発生した警告についての対処法です。
これのせいでアプリがクラッシュするということはないのですが、私の環境では dismiss() が正常に作動せず、前のページに戻れないというバグが発生したので、修正することをお勧めします。

今回参考にさせていただいた記事はこちら。

【Swift】URLSessionまとめ – Qiita
https://qiita.com/shiz/items/09523baf7d1cd37f6dee#1アプリ1URLSession

 

こちらの記事内で、下記のような記述があります。

・同時平行処理される複数のURLSessionTaskは1つのURLSessionで共有可能。
・独自でURLSessionを作成している場合は、finishTasksAndInvalidateやinvalidateAndCancelでデリゲートやコールバックオブジェクトへの参照をクリアしないとメモリーリークを起こす。

で、自分のプロジェクトを確認したところ…セッションを無効にする処理を追加してない!
ということで、下記のコードを追加して修正しました。

// ページが破棄される直前に呼び出される
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // 通信キャンセル&セッション無効
    self.session?.invalidateAndCancel()
}

私の場合は、ページが破棄される直前に Session も無効にしていますが、他のタイミングで実行しても問題ありません。
適宜、環境に合わせて変更してください。

また、URLSession は下記のようにグローバル変数で宣言することをお忘れなく。

var session: URLSession?

URLSession を使った POST 送信処理については、以前に記事を投稿しましたので、そちらを参考にして頂けると嬉しいです。

【Swift】URLSessionを使ってPOST送信を行う方法
https://cpoint-lab.co.jp/article/201911/12524/

 

以上、Swift の URLSession を使用した際に、「A background URLSession with identifier [identifier名] already exists!」という警告が発生する際の対処方法についてでした。
ご参考になれば幸いです。

  • この記事いいね! (0)
asaba 著者:asaba

【cordova】FCMPrugin is not foundの解決法

cordovaでプラグインを入れ直した直後に

ビルドしたところ、FCMPrugin is not foundのエラーに遭遇。

デバッグした結果、この中で一つ当てはまった場合に起きるエラー

のようです。

 

①FCMPruginが正常に読み込まれていない

②tokenが無効な値

③onDeviceReady内で処理をしていない

④FCMPruginが他のプラグインとせり合いを起こしている

 

自分の場合は④で、FCMPruginのバージョンが古かったため

他のfirebaseのプラグインと役割が被っていたのが原因でした。

④に関してはFCMPruginのバージョンを下げて追加

することで解決させることができます。

 

それでも解決しない場合は、platformsを削除、そのあとfirebaseに関わるプラグインを

全て消して再び追加、最後にplatformsを追加

をしてください。

以上がFCMPrugin is not foundの解決方法になります。

 

plugin関係は依存度の理解とかプラグインのエラーの原因と

なったファイルを探す探索力が必要になってくるのですが、

まだまだそこが理解できていない部分があるので

慣れておく必要があると感じました。

 

 

 

 

  • この記事いいね! (0)
takahashi 著者:takahashi

dockerのコンテナ名(container_name)はかぶると共存できない

dockerで使用されるコンテナ(docker上での個々の仮想マシンのこと)にはランダムなIDが割り付けられますが、これとは別に、ユーザーが任意の名前を付けることができます。

そのためわかりやすい名前をつけておけばそれを使用してコンテナの操作を行うことができるためとても楽です。

一方で、このユーザーが指定できるコンテナ名は重複することができず、ほかに同名のコンテナ名が存在すると重複した時点でエラーとなってしまいます。

docker-compose up -d
...省略...
Creating mysql              ... error
ERROR: for mysql  Cannot create container for service mysql: b'Conflict. The container name "/mysql" is already in use by container "コンテナID". You have to remove (or rename) that container to be able to reuse that name.'
...省略...
ERROR: for mysql  Cannot create container for service mysql: b'Conflict. The container name "/mysql" is already in use by container "コンテナID". You have to remove (or rename) that container to be able to reuse that name.'
ERROR: Encountered errors while bringing up the project.
Failed to deploy 'Compose: docker-compose.yml': `docker-compose` process finished with exit code 1

このエラーによく遭遇するシチュエーションとしては、docker-composeを使用した一括構築を行った際、複数のComposeファイルを使用して同じDockerホスト上で構築を行うと、各Composeファイルに記述されているコンテナ名が一般的なもの(例えばMySQLが入っているコンテナには”mysql”という名前がついていたりなど)になっていることがあるため、しばしばエラーとなってしまいます。

この場合は、Composeファイル(docker-compose.yml)内の

“container_name”

ディレクティブを既存のコンテナと被らないように変更すれば、共存させることが可能です。

例えば、複数のdocker-compose.yml内での指定で

...省略...
  mysql:
    container_name: "mysql"
...省略...

のように指定がかぶってしまっている場合は

...省略...
  mysql:
    container_name: "mysql_service1"
...省略...

のように名前を変えておくことで、共存させることが可能になります。

dockerはOSからアプリケーションまでの間の部分の構築もかなり簡単にしてくれるので、複数人で同じ環境を再現して開発を行いたい場合はとてもありがたい仕組みではあるのですが、一部環境に合わせて設定を変更しなければならない場合もあるので、この辺りは注意が必要になりそうですね。

  • この記事いいね! (0)
著者:杉浦

【JavaScript】配列関数を使った読みやすいオブジェクト生成の書き方

 次の様な入出力を実現する関数dictMergeをなるべく読みやすく短い関数で実現するのが目標です。dictMergeは未定義や余分な定義を含む複数の辞書から必要なカテゴリーの語のみをまとめるための関数です。たとえなので辞書ですが今回紹介する配列、オブジェクト操作のようなやり方は色々な場面で使えます。

// 入力
const categoryKeys = ['hoge', 'fuga', 'hogefuga'];
const dictA = {
	hoge : ['hoge1', 'hoge3', 'hoge5'],
	fuga : ['fuga3', 'fuga7', 'fuga13'],
}
const dictB = {
	hoge : ['hoge7', 'hoge9', 'hoge11'],
	bar : ['bar2', 'bar4', 'bar6'],
}
const dictC = {
	hoge : [],
	fuga : ['fuga19', 'fuga29', 'fuga37'],
	hogefuga : ['hogefuga'],
}

// 出力
const mergedDict = dictMerge(categoryKeys, dictA, dictB, dictC);
console.log(mergedDict);
/*
{
	hoge : ['hoge1', 'hoge3', 'hoge5', 'hoge7', 'hoge9', 'hoge11'],
	fuga : ['fuga3', 'fuga7', 'fuga13', 'fuga19', 'fuga29', 'fuga37']
	hogefuga : ['hogefuga']
}
*/

 実装したdictMerge()が次になります。コードの細部はコメントの通りです。

/**
 * @param {Array} categoryKeys
 * @param {Array} dicts
 * @return {Object}
 */
function dictMerge(categoryKeys, ...dicts) { // 第二引数以下を変数dictに配列として格納する. 今回ならdicts = [dictA, dictB, dictC]
    return categoryKeys.reduce((mergedDict, category) => { // reduce関数で配列を一つのオブジェクトに変換する。詳しくは下記
        return {
            ...mergedDict, // スプレッド構文でreduceの前のループ結果を引き継がせる
            [category]: dicts.map(dict => dict[category]) // [category]で変数をキー名にする。map関数で扱う配列を[dictA[category], dictB[category], dictC[category]]にする
                .flat() // 配列の中身を一段flatにする。例えば[Array(3),Array(3),Array(0)]の二次元配列がArray(6)の一次元配列になる
                .filter(v => v !== undefined)// 未定義でない値のみを配列の中身に残す
        }
    }, {});
}

 上記の様にJavascriptに用意されている関数をチェーンするだけで少々複雑な問題も簡易な記述で解けます。この様なやり方を用いる際に注意する必要があるのはreduce関数です。reduce関数はJavaScriptの配列操作においてとても汎用的であり、書こうとさえ思えばほぼ全ての配列操作関数をreduce関数で表現できます。これを使うと次のように黒魔術的もかくやというコードを書けます。

// dictMergeと等価
function dictMergeByReduce(categoryKeys, ...dicts) {
    return categoryKeys.reduce((mergedDict, category) => {
        return {
            ...mergedDict,
            [category]: dicts.reduce((acc, value, index) => {
                acc[index] = value[category];
                return acc;
            }, [])
                .reduce((acc, value) => acc.concat(value), [])
                .reduce((acc, value) => {
                    if (value !== undefined) {
                        acc.push(value);
                    }
                    return acc;
                }, [])
        }
    }, {});
}

 なるべくreduceを使わず特化的な名前の少ない引数の配列関数を使うとわかりやすいコードになります。最初の例も正直reduceを使うまでもありません。reduce((o, k)=>{return {…o, [k]:kに関する何か}},object型引数)という構文は便利なのですが慣れない人に渡すとけっこう混乱します。注意するべきでしょう。

// 多くの人がわかりやすく読みやすいであろう版
function dictMergeSimple(categoryKeys, ...dicts) {
    const mergedDict = {};
    categoryKeys.forEach(category => {
        mergedDict[category] = dicts.map(dict => dict[category])
                .flat()
                .filter(v => v !== undefined)   
    });
    
    return mergedDict;
}
  • この記事いいね! (1)
村上 著者:村上

【Swift】読み込み中を表すインジゲーターを表示する方法

タイトル通り、iOS アプリにインジゲーターを表示する方法です。
予想以上に簡単だったので、備忘録兼、今後のコピペ元にするためにまとめ。

参考にさせていただいた記事はこちら。

【Swift】UIActivityIndicatorViewを使う【ぐるぐる】 – Qiita
https://qiita.com/wai21/items/aa105f167bc94a94fe51

 

まず、viewDidLoad() 内に下記を追加し、インジケーターの位置や色などの設定を行います。

// インジゲーターの設定
var indicator = UIActivityIndicatorView()
// 表示位置を設定(画面中央)
indicator.center = view.center
// インジケーターのスタイルを指定(白色&大きいサイズ)
indicator.style = .whiteLarge
// インジケーターの色を設定(青色)
indicator.color = UIColor(red: 44/255, green: 169/255, blue: 225/255, alpha: 1)
// インジケーターを View に追加
view.addSubview(indicator)

今回は、標準サイズよりも大きめのサイズで、色は iOS で使われている青色にしています。
基本的な設定は上記だけで OK です。
なお、.color については .style の後に記述しないと無効になってしまうそうなのでご注意ください。

が、これだけだと表示されないので、インジケーターを表示したいタイミングで下記を呼び出します。

// インジケーターを表示&アニメーション開始
indicator.startAnimating()

で、終了したいタイミングで下記を呼び出します。

// インジケーターを非表示&アニメーション終了
indicator.stopAnimating()

実装についてはこれだけです!

なお、インジケーターの表示/非表示はメインスレッドで行う必要があるので、状況に合わせて下記のように記述するようにしてください。

DispatchQueue.main.async {
    // メインスレッドでインジケーターを停止する
    indicator.stopAnimating()
}

他にも、.isAnimating プロパティでアニメーションがされているかを取得したり、.hidesWhenStopped でアニメーションしていない時に非表示にするかどうかを指定できます。
こちらも環境に合わせて指定してください。

 

以上、Swift で読み込み中を表すインジケーターを表示する方法でした。
ご参考になれば幸いです。

  • この記事いいね! (0)
asaba 著者:asaba

【cordova】Cannot add task ‘:processDebugGoogleServices’の対処法

gradleのバージョンを5.4.1にしてビルドをすると

cordova-plugin-firebaseとcordova-plugin-fcmが競り合いを起こして

Cannot add task ‘:processDebugGoogleServices’のエラーとなります。

 

解決方法は

 

①cordova-plugin-firebaseとcordova-plugin-fcmをそれぞれcordova plugin rm 〇〇する

②cordova platforms rm android

③cordova platforms add android@6.4.0をadd

④cordova-plugin-firebaseとcordova-plugin-fcmをそれぞれcordova plugin add 〇〇する

⑤ビルド後gradleの変更(android Gradle Plugin Vesion 3.0.1 Gradle Version 4.1)

⑥gradle-wrapper.propertiesの6行目 gradle-4.1-all.zipに変更

⑦ビルドでエラーが出なければ成功

 

cordova platforms add androidの際は、何も指定しないでaddを

すると、android6.3.0がインストールされますが、このバージョンだと

cordovaのライブラリで一部競り合いが起こるものが入っているので

避けた方が良いです。代わりに一つ上の6.4.0を使うことで

ライブラリ系のトラブルを減らすことができます。

  • この記事いいね! (0)
著者:杉浦

PlantUMLをwebページ的にダンプする

 具体的には各UML図をハイパーリンクによってつなげたSVGファイルにしてブラウザ上で見れるようにします。目的はアクティビティ図->シーケンス図、ER図全体->ER図部分の様な関連した別UML図を閲覧者が探しやすくすることです(WWWの元ネタそのまま)。この様にできるとPUMLを一つの場所に過度に巨大に濃密に記述する必要がなくなります。
 実際にこれを行うための手順は、PlantUMLを記述する際に出力状態のSVGリンクを相対パスで作る、階層構造を維持したままSVGにダンプする、の2ステップだけです。これによってGIFの様にできます。

 PlantUMLに次のように書くことで図のようにハイパーリンク付きの文字列を作れます。使える場所はかなり広く文字列を表示する場所ならどこでもOKな印象です。今回はpumlファイルのあるフォルダと同じ場所にsvgファイルを展開するつもりなので、記述中のpumlファイルの場所からみた特定のpumlファイルへの相対パスを記述します。

:[[greeting/HelloWorld.svg Hello world]];

 特定のディレクトリ下にsvgとしてpumlをダンプするコマンドは次になります。

java -jar plantuml.jar ./**.puml -tsvg -charset utf-8

 現在のディレクトリ以下の.pumlファイル全てを文字コードutf-8のSVGにして.pumlのあるフォルダと同じ場所にダンプします。これだけでできます。特定フォルダ以下に出力するには-oオプションを用います

java -jar plantuml.jar ./**.puml -tsvg -charset utf-8 -o svg

ならば./svg以下にSVGファイルが出力されます。

  • この記事いいね! (1)
村上 著者:村上

【Swift】「SFSafariViewController」でWebページが表示されない時の対処法

iOS アプリで Webページを表示させたくて SFSafariViewController を使用していたのですが、開こうとしても白紙のページが表示されるだけという現象が発生しました。
1回目は成功するのですが、一度ページを閉じた後、再度開くと白紙になる…という状況です。
時間を開ければ表示されたりもするのですが、それだと使えないので対処法を調査しました。

 

さて対処法ですが、結局 SFSafariViewController をやめて WKWebView を使うのが一番手っ取り早いようでしたので、そちらの方法を採用することにしました。
ということで、実装のサンプルコードです。

まず、遷移先の WebView ページのコードがこちら。
地図を表示させているので、MapController.swift という名前になっています。
こちらは適宜変更してください。

import UIKit
import WebKit

class MapController: UIViewController, WKNavigationDelegate, WKUIDelegate {
    var webView: WKWebView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "マップ"
        let rightReloadButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(reloadPage(_:)))
        self.navigationItem.setRightBarButtonItems([rightReloadButton], animated: true)
        // WebView の設定
        webView = WKWebView(frame:CGRect(x:0, y:0, width:self.view.bounds.size.width, height:self.view.bounds.size.height))
        webView?.uiDelegate = self
        webView?.navigationDelegate = self
        self.view.addSubview(webView!)

        // URL設定
        let urlString = [表示したいWebページのURL]
        let url = URL(string: urlString)
        let request = URLRequest(url: url!)
        webView!.load(request)
    }
    
    // リロード処理
    @objc func reloadPage(_ sender: UIButton) {
        if webView?.url != nil {
            webView?.reload()
        }
    }
}

ナビゲーションバーの右端にはリロードボタンも追加しています。
こちらは必要がなければ削除してください。

そして、上記のページを開く時のコードはこちら

// 遷移するViewを定義する.
let mapController: MapController = MapController()
// View を移動する.
self.navigationController?.pushViewController(mapController, animated: true)

コメントを抜かせば、たった 2行でOKです。

 

もちろん、SFSafariViewController を使用すれば MapController.swift を用意しなくて済むのでコード行数も少なくて済むのですが、原因のわからないエラーが発生したので作り直してしまった方が簡単でした。
しかし、別の問題もあり、WebView を開いた数十秒後に下記のような警告が発生しました…。

Could not signal service com.apple.WebKit.Networking: 113: Could not find specified service

警告なので無視しても大丈夫そうではありますが…ちょっと気になるので、調べて見てすぐ解決できそうなら対応したいと思います。

 

以上、Swift の SFSafariViewController で Web ページが表示できない時の対処法でした。
根本的な解決にはなっていませんが…ご参考になれば幸いです。

  • この記事いいね! (0)