yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

iOS環境でのクリップボードコピーのバグ対応

Rails(フロントエンドerb)で作ったサービスでiOSだけバグっていた機能があり修正箇所少ない割にハマって数時間溶かしてしまった。

こんな感じの機能

ボタンをクリック →javascriptでtextareaのvalueを取得 →ajaxAPIにリクエスト送る。レスポンスを受け取ったら取得したtextareaの内容を変換 →変換した内容をクリップボードにコピー

こんな感じで%{name}みたいに変数埋め込んでいい感じに変換してクリップボードに貼れる的なやつ

%{name}様

予約が完了しました。

iOSクリップボード

ググったらiOS10以降だと色々制限があるみたいだったので一つ一つ条件見直してみた。。が、この辺りは全部問題なさそう。

参考 テキストをクリップボードにコピーする | cly7796.net

iOSでも対応する場合の補足として、iOSでコピーしたい場合は以下のような条件があるようです。

  • inputとtextareaからのみコピーが可能
  • form内にない場合はcontenteditable属性がtrueである必要がある
  • readonly属性がtrueの場合はコピーできない
  • コピーするテキストが選択されている状態である

解決策

ajaxの非同期設定をfalseにしたら直った。なんだそれ。。

$.ajaxSetup({async : false}); // これを追加
$.ajax({
  type: "post",
  url: "/api/hoge",
  data: {
     name: name
  },
}).done(function(data){
  navigator.clipboard.writeText(value);
})

写経しながら実践Rustプログラミング入門を読んだ(環境構築〜基本文法)

Rustが気になるので写経しながら「実践Rustプログラミング入門」を読んだ。

www.shuwasystem.co.jp

写経したコードと解説の切り抜きをメモがてら書いてみる。

環境構築

以下のサイトを開いてインストールコマンド実行

www.rust-lang.org

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1

カスタムインストールの確認があるけどとりあえずはデフォルトの”1”でOK。

インストール後以下のコマンド実行

source $HOME/.cargo/env

これでcargoというツールを使ってRustのプロジェクトを作成できるようになる

# プロジェクト作成
cargo new hello_world
# 実行
cd hello_world
cargo run

開発環境の構築

rlsのインストール

Rust Language Server(rls)をインストールする。

goto definitionや型情報の表示、コード補完を提供してくれる

rustup component add rls rust-analysis rust-src

その後エディタがVSCodeの場合Rustの拡張ツールをインストールする。

cargo-editのインストール

であるcargo-editはクレート(Rustのライブラリをクレートと呼ぶ)を追加する時に便利なツール。

cargo install cargo-edit

cargoのサブコマンドとしてadd run rmが使えるようになる。yarnとかnpmでライブラリ管理するのと同じ感覚っぽい。

基本的な型

Rustにはコアライブラリと標準ライブラリがある。最も標準的な機能が含まれているのがコアライブラリで標準ライブラリはコアライブラリに次いで大切なライブラリ

文字列型

コアライブラリで定義されている文字列型はstrのみ。標準ライブラリではStringという型も定義されている。 strはメモリ上の文字列のスタート地点と長さを示すものなので文字列そのものの変更はできない。

Stringは文字列データの変更や長さの変更が可能。なのでStringを使うケースの方が多い。

Stringをstrに変換するときはポインタと文字列長をコピーしてスライスを作るのでメモリを圧迫しない。 しかしstrをStringに変換するときはメモリの確保が行われる。

# String <=> strのコピー
let s1: String = String::from("Hello World");
let s2: &str = &s1;
let s3: String = s2.to_string();

タプル型

異なる型を収めることができる集合

let mut t = (1, "2");
t.0 = 2;
t.1 = "3";

配列

特定の型の値を連続に収めた集合。配列のサイズはコンパイル時に決まっている必要あり。

let mut a: [i32; 3] = [0, 1, 2];
let b: [i32; 3] = [0; 3];
a[1] = b[1];
a[2] - b[2];
println!("{:?}", &a[1..3]);

ユーザ定義型

構造体と列挙型がユーザ定義できる型となる。

構造体はstructを用いて定義することができる。

struct Person {
    name: String,
    age: u32,
}
let p = Person {
    name: String::from("John"),
    age: 8,
};

列挙型はenumを用いて定義することができる。

enum Event {
    Quit,
    KeyDown(u8),
    MouseDown { x: i32, y: i32 }
}
let e1 = Event::Quit;
let e2 = Event::MouseDown { x: 10, y: 10 };

Option

Optionはデータが存在する場合と存在しない場合を表現できる列挙型。

pub enum Option<T> {
    None,
    Some(T),
}

Result

処理の結果が成功がエラーかを表現できる列挙型

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

matchやif letを使ってパターンマッチングして使う

let Result: Result<i32, String> = Ok(200);

match Result {
    Ok(code) => println!("code: {}", code),
    Err(err) => println("Err: {}", err)
}

if let Ok(code) = result {
    println!("code: {}", code)
}

matchやif letはコードのネストが深くなったり重厚な印象を与えるため回避するための書き方が用意されている。

let result: Result<i32, String> = Ok(200);
println!("code: {}", result.unwrap_or(-1));
let result: Result<i32, String> = Err("error".to_string());
println!("code: {}", result.unwrap_or(-1));

and_thenはOKだった場合だけ指定した関数を実行できる

fn func(code: i32) -> Result<i32, String> {
    println!("code: {}", code);
    Ok(100)
}

let next_result = result.and_then(func);
let result: Result<i32, String> = Err("error".to_string());
let next_result = result.and_then(func);

?演算子はOkだった場合に値を展開、Errだった場合はそのErrをそのままreturnする

fn error_handling(result: Result<i32>, String) -> Result<i32, String> {
    let code = result?;
    println!("code: {}", code);
    Ok(100)
}

Vec

配列とは違い、内部に収められる要素の数を増減させることができる。

let v1 = vec![1, 2, 3, 4, 5];
let v2 = vec![0; 5];

for文で繰り返し処理ができる。

let v = vec![1, 2, 3, 4, 5];
for element in &v {
    println!("{}", element)
}

Box

Rustの値は多くの場合メモリのスタック領域に確保される。スタック上のデータはコンパイル時にサイズがわかっており固定サイズでなくてはいけない。

Boxを使うと値はヒープ領域に確保される。ヒープ領域に確保する値はコンパイル時にサイズがわかってなくても問題ない。 Boxを用いて次のようなことができる

  • コンパイル時にサイズがわからない型を格納すること
  • 大きなサイズの型を渡す際にデータの中身をコピーせずポインタで渡すこと
  • 共通のトレイトを実装した様々な型を画一的にポインタで扱うこと
let byte_array = [b'h', b'e', b'1', b'1', b'o'];
print(Box::new(byte_array));
let byte_array = [b'w', b'o', b'r', b'1', b'd', b'!'];
print(Box::new(byte_array));

fn print(s: Box<u8>) {
    println!("{:?}", s);
}

printに渡すときもポインタで渡しているのでどのようなサイズでも渡すことができる。 バイト列が長いものだったとしてもポインタで渡すため値のコピーを行わない。

変数宣言

let、mut

letは変更不可能、変更可能にしたい場合は変数宣言位mutをつける。

let immut_val = 10;
let mut mut_val = 20;

mut_val += immut_val;

定数

定数はconstとstaticで宣言できる。 constは常に変更不可。staticは変更可能にできる。

制御構文

if文

他の言語同様if文が使える。ただしRustにおけるif文は式なので評価した値を変数に束縛することや関数の引数にすることが可能。

let number = 1;
let result = if 0 <= number {
    number
} else {
    -number
};

ループ

loop、for、whileという2種類のループが存在する。

# loop
let mut count = 0;
let result = loop {
    println!("{}", count);
    count += 1;
    if count == 10 {
        break count;
    }
};

# while
let mut count = 0;
while count < 10 {
    println!("count: {}", count);
    count += 1;
}

# for
let count: i32;
for count in 0..10 {
    println!("count: {}", count);
}

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for element in &array {
    println!("element: {}", element)
}

loop、for、whileにはラベルをつけることができ、breakするときにそのラベルを指定して繰り返しを抜けることができる。

'main: loop {
    println!("main loop start");
    'sub: loop {
        println!("sub loop start");

        break 'main;

    println!("sub loop end");
    }
    println!("main loop end");
}

match

C言語のswitch文に似たmatch文が存在する。ただしmatchはswitchよりも強力な「パターン」による分岐ができる。 Rustにおける「パターン」という言葉は型の構造に一致しているか確認するための記法を指す。パターンは、数字や文字列のような単純な型だけでなく列挙型、タプル、構造体などの比較をしたり値の範囲やワイルドカードを使って広範囲に比較させることもできる。

let i: i32 = 1;
match i {
    1 => println!("1"),
    2 => println!("2"),
    3 => println!("3"),
    _ => println!("misc"), // アンダースコアはあらゆる値にマッチする 
}

enum Color {
    Red,
    Blue,
    Green,
}

let c = Color::Red;
match c {
    Color::Red => println!("Red"),
    Color::Blue => println!("Blue"),
    // Color::Green => println!("Greeen")
    // Greenをコメントアウトするとmatchが列挙の要素を全て網羅していないことになりエラーになる
}

Range

forループで特定の範囲の数値を指定するときにはRange型を使う。

for member in 1..5 {
    println("{}", number)
}

Iterator

自作した型にIteratorトレイトを追加すればforループで繰り返し処理ができて便利

fn main() {
    let it = Iter {
        current: 0,
        max: 10,
    };
    for num in it {
        println!("{}", num)
    }
}

struct Iter {
    current: usize,
    max: usize
}

impl Iterator for Iter {
    type Item = usize;

    fn next(&mut self) -> Option<usize> {
        self.current += 1;
        if self.current -1 < self.max {
            Some(self.current - 1)
        } else {
            None
        }
    }
}

関数

fnキーワードを使って関数を定義してimplキーワードを使って構造体にメソッドを紐付けることができる。 Goと同じ仕組みっぽい。

C言語Pythonのようにreturnによる記述をしなくても関数の最後にセミコロンなしで記述された値を戻り値として扱うルールがある。 処理の途中で戻り値を返したい場合はreturnを使って記述する。

struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn say_name(&self) {
        println!("I am {}", self.name);
    }

    fn say_age(&self) {
        println!("I am {} year(s) old.", self.age);
    }
}

fn main() {
    let p = Person {
        name: String::from("Taro"),
        age: 20,
    };

    p.say_name();

    p.say_age();
}

メソッドの戻り値に自分自身の型を指定することでメソッドチェーンを使うことができる。

struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn say_name(&self) -> &Self {
        println!("I am {}", self.name);
        self
    }

    fn say_age(&self) -> &Self {
        println!("I am {} year(s) old.", self.age);
        self
    }
}

fn main() {
    let p = Person {
        name: String::from("Taro"),
        age: 20,
    };

    p.say_name().say_age();
}

第一引数にselfを使わなかった場合、それは関連関数になる。関連関数はインスタンスからメソッドを呼ぶのではなく型から関数を呼ぶ形式で定義される関数のこと。

以下のnewが関連関数

impl Person {
    fn say_name(&self) -> &Self {
        println!("I am {}", self.name);
        self
    }

    fn say_age(&self) -> &Self {
        println!("I am {} year(s) old.", self.age);
        self
    }

    fn new(name: &str, age: u32) -> Person {
        Person {
            name: String::from(name),
            age: age
        }
    }
}

struct Person {
    name: String,
    age: u32,
}

fn main() {
    let p = Person::new("Taro", 20);

    p.say_name();
}

マクロ

マクロ呼び出しには!がつき関数呼び出しと見た目で区別できるようになっている。 Rustの構文と見た目が違うような括弧が慣例的に使われる。例えば関数呼び出し的なマクロなら()、コードブロックを引数に取るマクロなら{}など。

format!、concat!は文字列操作のマクロ。println!やwrite!、dbg!はデータ出力用のマクロ。

use std::io::Write;

fn main() {
    // データ出力マクロ
    print!("hello");
    print!("hello {}", "world");
    eprint!("hello {}", "error");
    eprint!("hello");

    let mut w = Vec::new();
    write!(&mut w, "{}", "ABC");
    writeln!(&mut w, " is 123");
    dbg!(w);

    // 異常終了用のマクロ
    panic!("it will panic");

    // ベクタ初期化マクロ
    let v = vec![1, 2, 3];

    // プログラム外のリソースにアクセスするマクロ
    // file!マクロが呼び出されたファイル名を取得
    println!("defined in file: {}", file!());
    // line!マクロが呼び出された行を出力するマクロ
    println!("defined on line: {}", line!());
    // コンパイラから該当フラグが渡されていればtrue、そうでなければfalse
    println!("is test: {}", cfg!(unix));
    // コンパイル時の環境変数を取得
    println!("CARGO HERE: {}", env!("CARGO_HERE")); 

    // アサーション用のマクロ
    assert!(true);
    assert_eq!(1, 1);
    assert_ne!(1, 0);
    debug_assert!(false);
    debug_assert_eq!(1, 1);
    debug_assert_ne!(1, 0);
}

実装補助用のマクロ

unimplemented!、todo!、unreachable!。いずれも実装が行われていない部分があるソースコードの型検査を通すためのマクロ。

enum Emotion {
    Anger,
    Happy,
}

trait Emotional {
    fn get_happy(&mut self) -> String;
    fn get_anger(%mut self) -> String;
    fn tell_state(&self) -> String;
}

struct HappyPerson {
    name: String,
    state: Emotion,
}

impl Emotional for HappyPerson {
    fn get_anger(&mut self) -> String {
        unimplemented!()
    }

    fn get_happy(&mut self) -> String {
        format!("{} is always happy.", self.name)
    }

    fn tell_state(&self) -> String {
        todo!()
    }
}

fn main () {
    let mut p = HappyPerson {
        name: "Takahashi".to_string(),
        state: Emotion::Happy
    };
    println!("{}", p.get_happy())
}

トレイトの導出

標準ライブラリのいくつかのトレイトは自作の型に対する標準的な実装を自動的に導出することができる。

#[derive(Eq, PartialEq)]
struct A(i32);

#[derive(PartialEq, PartialOrd)]
struct B(f32);

#[derive(Copy, Clone)]
struct C;

#[derive(Clone)]
struct D;

#[derive(Debug)]
struct E;

#[derive(Default)]
struct F;

fn main() {
    println!("{:?}", A(0) == A(1));

    println!("{:?}", B(1.0) > B(0.0));

    let c0 = C;
    let c1 = c0;
    let c2 = c0;

    let d0 = D;
    let _d1 = d0.clone();

    println!("{:?}", E);

    let _f = F::default();
}

長くなってきたので一旦区切る。

基本文法は他の言語に比べてそこまで異質なものはなさそうな印象。

確定申告振り返り(2021年分の所得申告)

2022年の確定申告(2021年分の所得申告)が終わったので振り返り。昨年は紙で申告書作って郵送したけど今年は電子申告でやってみた。

誰の役に立つかもしれないのでやったこと書き残してみる。

申告のため用意したもの

  • Webサービスfreee
  • freeeの電子申告スマホアプリ
  • マイナンバーカード
  • スマホ
  • マイナポータルというスマホアプリ
  • e-Tax推奨ブラウザ。自分の場合はmacなのでsafari
  • 賃貸の場合大家の氏名と住所が必要なので不動産契約書を用意
  • 家賃の履歴。更新があった場合はその履歴も
    • 管理会社じゃなくて大家の情報じゃないとダメらしい
  • 納めた住民税、保険料、年金などがわかるもの
    • 自分の場合は全部自動で銀行口座から引き落とされているので口座履歴見て確認した。年金は年金ネットってサイトで見れたりとか自治体から確定申告の前に葉書で通知が来たりするのでそれと一応照らし合わせたりもした
  • ふるさと納税の履歴。
    • 自分の場合はふるさと納税サイトの納税履歴が残っていたのでそれを参照した
  • 諸事情で1ヶ月だけ売り上げを源泉徴収してもらってたのでその証明
  • 自分の場合事業に関わる支出は電子で履歴が残っていたのでそれを抑えておく

この辺りは仮想通貨や株取引してるか否かだったり家族構成によって変わったりなどなど人によって違うので参考まで。

手順

自分の場合以下の手順で電子申告できた。

  1. Web版のfreeeに1年分の収入、支出を登録して間違いや漏れがないか確認する
  2. freee確定申告タブをクリックして案内通りに納めた税金、保険料、年金の額や按分として計上する家賃、水道光熱費、通信費などを入力していく
  3. e-Taxで「利用者識別番号」を取得する。この際に利用者識別番号のパスワードを抑えておく
  4. freeeの電子申告スマホアプリでマイナンバーカードを読み取ってさっき取得したe-Taxの利用者識別番号とそのパスワード、マイナンバーカードのパスワードを入力する。取得方法はここ
  5. Webのfreeeに戻る。利用者識別番号とそのパスワードを入力して結果を確認できる。うまくいっていればfreee上で「所得税及び復興特別所得税申告が正しく受理され、確定申告が完了しました。」と表示が出てくる
  6. 納税する。自分の場合は口座振替の依頼書を税務署に出して銀行口座から落としてもらうようにした。

freeeで電子申告した後不安になったのでオンラインで申告したくせに物理的に税務署に行って正しく受理されてるか聞きにいってしまった。利用者識別番号を元に確認してくれる。

e-Tax

e-Taxが異常に使いにくい。ログインして申告がうまくいったか確認したりマイナポータルと連動させてみたりと使ってみたけどわかりにくすぎ。

ただメッセージボックス観たりマイナポータルと連動させたりはfreee使ってれば不要っぽい。使いにくすぎて触り続けると気が狂いそうになるので利用者識別番号取得後は2度と触らない方針でいいと思う。

最近読んだ技術書

実践Rustプログラミング入門

API開発、システムプログラミング、Web Assembly、フロントエンドのライブラリ開発といろんな場面で使えて言語仕様も今風だしC、C++に匹敵するくらい速いしで評判良さげな言語なので気になって読んだ。

とりあえず読んでどんな言語なのか、どんな場面で使えるのかざっくり把握できた気はする。まだ手を動かして写経したり自分でアイデア出してRustで何か作った訳ではないので今年中に何かしら作れるレベルになるまで学んでみたいかな。

www.shuwasystem.co.jp

現場ですぐに使える! Unity 2020逆引き大全 303の極意

Unity学んで最近しょうもない自作ゲーム2つ作ってみたんだけどその際に使った。正直詰まった時に調べるのはこの本で探すよりググった方が早かったけどUnityでどんなことができるか全体を把握するにはよかった。

紙の本で買ったけど重いんで電子書籍にすればよかったと後悔している。

www.shuwasystem.co.jp

ドラゴンクエストXを支える技術 ── 大規模オンラインRPGの舞台裏

非エンジニアでも読めますよ。的なことが書いてあるけど筆者もエンジニア出身のプロデューサーだったりして技術者向けの内容だな。と感じた。

gihyo.jp

Kindle Unlimitedで読めるUnityの本

Unityについて調べてるときにKindle Unlimitedで読めるUnityの本が結構あったのでいろいろ読んでみたけどUnityでの開発はGUI操作が多いのもあるのか書籍よりもYoutubeとかUdemyで動画の講座探した方がわかりやすいかな。。という感想

Gitlab CIからのECSデプロイ

GItlabでコード管理してAWS ECSでインフラを構築しているプロジェクトのCI/CDを業務で作った。その中でデプロイについて考えたこと書いてみる。

AWS ECSのデプロイについてググるAWS Codeシリーズを使ったCI/CDの構築方法がよく出てくるが、、コード管理に一番メジャーなGithubではなくCI/CDの便利機能を売りにしているGItlabを使っていることを考えるとなるべくGitlabのCIでなるべく完結させてみたい気もする。

AWS Codeシリーズで構築する場合とGitlab CIに寄せる場合のざっくり2パターン考えた。

それぞれの作業を洗い出すとざっくりこんな感じ

AWS Codeシリーズで構築する場合に必要な作業

  • Codeシリーズの構築
    • CodeCommit、CodePipeline、CodeBuild、CodeDeployと一通りのCodeシリーズを構築する必要あり。
  • CloudWatch Event
    • ECRへのpushをCI/CD開始のトリガーにする場合はCloudWatch Eventの作成が必要(CodeCommitへの変更をトリガーにするなら不要)
  • S3
    • CodepipelineはSource→Build→Deployとステージが進んでいくがそれぞれのステージの実行結果をS3にartifactと呼ばれるzip圧縮されたファイルに書き出していく仕組みなのでS3バケットを作成する必要あり
  • IAM
    • コードシリーズやS3にIAMポリシー設定していく必要あり

今のプロジェクトではTerraformでインフラを作っているのでドラフト的にざっくり一通り書いてみたけど上記全部揃えようとすると結構なコード量になる。手作業でやってもいいけどそれはそれで大変

Gitlab CIに寄せた設計を構築する場合

  • Gitlab CIの実行に必要なサーバの設定
    • 今回はEC2をCI用のサーバとして用意した
      • EC2に必要なソフトウェアを入れていく(DockerとかAWS CLIとか)
    • GItlabとCI用サーバの接続設定
  • 実行する処理の定義
    • CIの設定ファイル(.gitlab-ci.yml)にdocker build〜pushまでのコマンドを記載する
  • 必要なAWSのサービス
    • AWS CodeシリーズはCodeDeployのみ追加する。Gitlab CIから「aws deploy create-deployment」コマンドを呼び出してCodeDeployを開始する
    • CodeDeployの実行にS3とIAMは準備必要

で、結局Gitlab CIで作った。.gitlab-ci.ymlでコマンド実行していく形

# ${CI_BUILD_REF_NAME}でブランチ名を指定して環境ごとにデプロイできるようにする
build-demo-app:
  stage: build
  script:
    - docker build demo-app:latest
    - docker push xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/demo-app-${CI_BUILD_REF_NAME}:latest
 
deploy-demo-app:
  stage: deploy
  script:
    - aws deploy create-deployment --application-name demo-app-${CI_BUILD_REF_NAME} --cli-input-json file://deployment.json --region ap-northeast-1

Codeシリーズは構築するのにやること多くて大変そうだけど現時点でGitlab CIで作るケースより情報量多そうなのでその点はいいかも。

自作ゲーム作ってUnity Playで公開してみた

駄作でもいいから一回自分で仕様考えてゲーム作ってwebで公開してみたい。。と思ったんでUnityChan(フリーで使えるUnityの公式キャラ)の3Dモデルを操作して敵キャラと雪玉をぶつけあう3Dゲームを作ってUnity Playで公開してみた。

公開はUnityで作ったものをWebGLにbuildしてmacでコマンド叩いてzipにしてUnity Playにドラッグアンドドロップでアップしたらそれで終わった。よくできている。。

  • 作ったもの

play.unity.com

github.com

なんかゲームっぽくはなったけど遊んでて楽しくはないかな。難しい

とりあえず素振り的にもう何本か作りつつ3Dモデル、イラスト作成ツール使って簡単でも自分で素材作れるようにしつつちゃんと面白いもの作れるようになりたいな。

2Dシューティングゲームを開発するオンライン講座受講した

ノンフィールドRPG、ゾンビ系FPSの開発講座に続いて2Dシューティングゲームを開発する講座受講してみた。

www.udemy.com

Unity公式のチュートリアルを元に作った講座らしい。使ってる素材は同じだけど成果物は違うっぽい。

github.com

できたもの

vimeo.com