Webエンジニアの備忘録

およそ自己の作業メモとして残しております。

自社プロジェクトに xcprojectlint をかけてみた

知見が少なかったり、検証者がいなかったり、小さな開発現場は積極的にLinterを入れるべきだと思っています。

iOS開発をはじめてまだ1ヶ月ちょっとですが、まず最初にLinterを探しました。

プロジェクトのLinterを発見

引き継ぎプロジェクトが Objective-C だったのですが、言語のLinterは良さそうなものが見つからず、代わりにプロジェクトのLinterを見つけました。

github.com

アメックスさんがオープンソースを公開しているとは知りませんでした。 こちらはSwiftで書かれておりMacでの導入も非常に簡潔で、さくっと仕掛けられました。

導入方法について

何人かの方がすでに書かれていたので、記事を拝見させていただきました。 こちらで事足りると思いますので割愛します。

qiita.com techblog.lclco.com

実行

今回は業務上引き継いだおよそ4年もののプロジェクトにて検証をかけます。 ただし会社のコードはさらせないので、エラー、キャプチャ等のサンプルは比較的リアルな再現と思ってください(笑)

./xcprojectlint --report error --validations all --project awesome.xcodeproj

結果

初回実行で 219項目 もの指摘をされました…これは心が折れます\(^o^)/

検査対象である xcodeproj の中身はそもそもテキストエディタではあまり扱わない場所なため、解決には骨が折れました…(^_^;)

問題を分類するため、以下のような手順に切り替えました。

対応

チェックの際、 オプションを刻む実行 に切り替えました。

f:id:tak_taniguchi:20181117123520p:plain

xcprojectlint は5つ内容をチェックしてくれます。 初回実行では --validations all で実行してしまったのですべての検証が行われました。

以下の順序に切り替えます。

1. empty-groups

空のグループを検出してくれます。こちらも不要なものは削除していきます。 一番わかり易いのでこちらから対応しました。

error: Xcode folder “awesome/samples” has no children.

2. items-in-alpha-order

ファイルの並び順がフォルダ・ファイルのアルファベット順であることをチェックします。

error: Xcode folder “awesome/controllers” has out-of-order children.
Expected: ["comment", "friend", "game", "menu", "shared"]
Actual:   ["shared", "comment", "menu", "game", "friend"]

Expected があるべき並び方となっていますが、Xcodeのソートを利用するとフォルダ・ファイルを区別せずに名前順になります。ここは若干手作業が必要になりました。

f:id:tak_taniguchi:20181117112535p:plain

3. files-exist-on-disk

実ファイルが存在しないものを教えてくれます。ナビゲータ上でレファレンスが赤字になっている箇所ですね。こちらはリファレンスを消していくことで解消します。

New type found: ["versionGroupType": wrapper.xcdatamodel, "path": awesome.xcdatamodeld, "isa": XCVersionGroup, "sourceTree": <group>, "children": <__NSArrayM 0x1234567890A>(
1234567890ASDFGHJKLQWER
)
, "currentVersion": 1234567890ASDFGHJKLQWER]

イニシャルコミットで作られたものの、利用されずにファイルだけ消されたような形跡がいくつかありました。

4. disk-layout-matches-project

実ファイルの所在とナビゲータの配置がマッチしているか確認してくれます。 先頭にどのファイル名、フォルダ名が表示されますが、パスは表示されないのでこちらも少し頑張って検索しながら調整していく感じです(^_^;)

error: File “TopViewController.m” (1234567890ASDFGHJKLQWER) is misplaced on disk, or wrong kind of reference.
error: Folder “items” (1234567890ASDFGHJKLQWER) is misplaced on disk, or wrong kind of reference.

また、Xcodeのフォルダとグループは似て非なるもので、内包する実体との関係が少し変わってくるようです。グループが正しく設定されていない箇所を指摘されます。

グループについてはLocationにて相対グループに統一し、フォルダアイコンから実ファイルの相対パスを設定することで、ナビゲータ上のファイル移動に実ファイルも対応されるようになりました。

f:id:tak_taniguchi:20181117114501p:plain

5. build-settings-externalized

こちらが最後で最難関でした。 ビルド設定は project.pbxproj ではなく、個別ファイル(xcconfig)で管理されるのが望ましいようですが、こちらはまだ徹底できていません。

/Users/ttaniguchi/work/awesome/awesome.xcodeproj/project.pbxproj:2134: error: Pods-awesome.development.xcconfig (development) has settings defined in the project file.

なんとか5ステップを通して、8〜9割がたはLinterを通すことができました。 作業を通してナビゲータの整頓方法や、グループ、ファイルの差異、プロジェクト設定関連のファイルレイアウトを理解することができました。

Linter はこのあたりを読み解くきっかけとしても実行してみてよかったです。

iOS開発の事始め

今まで近くて遠い存在だったiOS開発ですが、ひょんな事から先月より自社アプリの主担当に就任し、突貫で技術習得しました。 「ピンチはチャンス」 、この1ヶ月は会社の理解もあり引き継ぎ名目で集中してインプットができました。

ここで1ヶ月を振り返り、覚えたことをメモがてら記録しておこうと思います。

やったこと

Xcodeの使い方を学ぶ 

ウィンドウ名称の把握

まずは各ウィンドウの名称を把握し、ググれる準備からです(^_^;)

【アプリ開発】Xcodeのウィンドウ名称 | Fussan Blog

こちらのサイトなど、参考にさせていただきました。 また、書籍でいくつかのXcodeバージョンのキャプチャなども確認しました。

ファイルタイプの把握

利用されているファイルの種類についても少しまとめました。

  • .xcodeproject: Xcodeプロジェクト。実体はディレクトリで存在する。
  • .xcworkspace: XcodeワークスペースPods利用など、プロジェクトが複数になるときに利用。
  • .plist: プロパティリスト。内部的にはxmlで記載される、おもに設定ファイル。
  • .h: Objective-Cヘッダーファイル。
  • .m: Objective-Cメインファイル。今回の主戦場。
  • .swift: Swiftファイル。残念ながら当該のプロジェクトではほぼ取扱なし…
  • .xib: XML Interface Builderファイル。単ページのテンプレートフォーマット。
  • .storyboad: Storyboadファイル。画面遷移を可視化できる新テンプレートフォーマット。
    • xibからstoryboadへの完全移行も共存も可能。
  • .xcassets: アセットカタログ。ディレクトリ構造のファイル群を contents.json を用いてカタログ管理している。

エディタの把握

Xcodeを使った最初の違和感は、ツールを触った際に出てくる git差分 でした。どの操作がどのコードに影響を与えているのかが、序盤の肝となりました。

ナビゲータ/プロジェクトエディター

ナビゲータの ファイルツリーは実ファイルの配置と異なります 。こちらは [プロジェクト名].xcodeproj 下の project.pbxproj で管理されており、ファイルの並び順が変わった際にもコードが書き換えられます。

ナビゲータツリーのルートをクリックすると、プロジェクトやターゲットの設定(下図)が確認できますが、こちらも [プロジェクト名].xcodeproj 下のxml等に対する書き出しを行っています。

f:id:tak_taniguchi:20181117003951p:plain

なので、意図する書き換え以外で [プロジェクト名].xcodeproj 下に差分が出た際はマウス操作等による可能性もあり、ちょくちょくコミットから外しています。

アプリ起点について

コードリーディングに際して真っ先に気にする起点についてもこちらに設定がありました。Main Interface を用いて起点となる Storyboard が選べるようです。

f:id:tak_taniguchi:20181117020342p:plain

とはいえ、 AppDelegate.m も起動時に実行されるので、こちらでインスタンス生成しても良さそうです。

インターフェイスビルダー/ストーリーボード

xib/storyboard を開いた際はこれらのエディタが起動しています。こちらもXcodeのバージョンアップでオプションの追加などが発生し、ときおり意図せずコードが勝手に書き換わるケースがあります。

アセットカタログ

xcassets は実ファイルが json で存在するのですが、画像をサイズで括って管理するのに便利です。旧仕様だとおそらくファイル名に hoge@2x.png などと節尾句を設けるのがセオリーだったようですが、こちらのほうが一覧性もあるので、追々完全移行する予定です。 唯一、多言語対応の考慮がされていないのが難ありです。

f:id:tak_taniguchi:20181117011307p:plain

Objective-Cを学ぶ

ネット上の記事やブログはSwiftに席巻されていて、Objective-Cについてはあまり残っていませんでした。こちらは書籍を購入し体系的に学習中です。

詳解 Objective-C 2.0 第3版

詳解 Objective-C 2.0 第3版

NSLog(@"test");

最初に学ぶべきはどの言語でもダンプでしょう。わたしはそのほうが捗ります。

  • コード実行箇所のあたりを付けて、ブレイクポイントで確認。
  • ダンプで細かな値なども確認。

これで実動を踏まえたコードリーディングが可能です。 ひとまずObjective-Cはこれを利用して読み解いていく予定です。

ライブラリ管理を学ぶ

CocoaPodsの把握

iOS開発ではCocoaPodsというパッケージ管理ツールを用いるようです。

CocoaPods.org

こちらを扱い始めると、実プロジェクトと別に Pods プロジェクトが併設されます。それによって、Xcode起動直後に開くファイルが xcodeproject から xcworkspace に変わります。

xcodeproject を開いてビルドしようとすると、 Libraryが見つからない という内容のエラーに苦しみます…(苦しんだ)

f:id:tak_taniguchi:20181117015217p:plain


インプットが多く纏めきれませんでしたが、俯瞰的にこんな感じで情報収集をしていました。次はもう少し絞り込んで、メモを残そうと思います。

getUserMedia() でカメラ・マイクのブロックを個々に検知

最近詰まったので、メモっておくことにします。

ブロックによるエラーの特徴

ブラウザから getUserMedia() を用いてカメラやマイクにアクセスすると、それらのデバイスへのアクセス許可が必要になります。これがブロック状態であった時に NotAllowedError が飛んで来ます。 ただし、カメラ・マイクともにアクセスを求めた際、どちらかが利用できる状況であればブロックエラーが取得できません。

navigator.mediaDevices.getUserMedia({ audio: true, video: true })
  .then((stream) => {
    this.setState({ stream });
  .catch(err => console.log(err)); // カメラ・マイクともにブロックの場合のみ NotAllowedError を受け取る

上記のブロックを受けた場合は音声のみの動画、または音声のない動画が取得出来ます。これはこれで、プライバシーを尊重したブロックの結果なのですが、開発都合で個々のブロックをキャッチしたいケースが多々あると思います。

対応策

以下のコードが対応策を施したものです。 stream を取得したい getUserMedia を実行した後に、 マイクのみ、カメラのみの getUserMedia を実行することで、こちらが NotAllowedError エラーを取得してくれます。

navigator.mediaDevices.getUserMedia({ audio: true, video: true })
  .then((stream) => {
    this.setState({ stream });

    // マイク単体検証(ブロック有無)
    navigator.mediaDevices.getUserMedia({ audio: true })
      .then(streamTmp => streamTmp.stop())
      .catch(err => console.log(err));

    // カメラ単体検証(ブロック有無)
    navigator.mediaDevices.getUserMedia({ video: true })
      .then(streamTmp => streamTmp.stop())
      .catch(err => console.log(err));
  })
  .catch(err => console.log(err));

getUserMediaを連投して大丈夫かな、という懸念はありましたが、Android数端末、Windows等で確認したところ問題は見られませんでした。 ただ個別チェックに利用した stream は取得後すぐに止めています。( streamTmp.stop()

所感

急場しのぎではありましたが、比較的シンプルに対処ができてよかったです。 それでも、これより良い解決方法などありましたらご享受いただければ幸いです。コメントなどください。

WEB+DB PRESS Vol.97

WEB+DB PRESS Vol.97

2017年を振り返る

自身の来年やるべきことを模索するためにメモを残しています。

所感

昨年にクライアントサイドに転向し、主にブラウザ対応に重きをおく開発をおこなっていました。サーバーサイドでもRailsなど新しい経験もしましたが、Javascriptの世界は作法も大きく異なり、エンジニアリングの世界が格段に広がりました。

やったこと

Reactjs

クライアントサイドの学習基盤としてReactjs+Reduxに触れられたのは非常に良かったです。APIを通してデータを取得するフローや、特に非同期の考え方はクライアントの動きを深く知ることができました。 コンポーネントのライフサイクルはネイティブにも通じる考え方で、今後はiOSAndroidの開発にも触れてみたいと思っています。

Ruby / Rails

API開発とRSpecコーディングに触れる機会は多かったです。bundleもトラブルが多かったので、そこそこ多く調べましたね…特にVagrantではnfsストレージ上でbundleがうまく動かないトラブルもありました。CコンパイルMacローカルのコアを認識しつつVagrantCentOSで動くみたいな(^_^;) kaminariがCで書かれているのも知って驚きました。

CSS

Animationやレイアウトについて、実作業ベースで苦しみました(笑) 特にレイアウトについては縦方向でのセンタリングの難しさや、ブラウザによって出る描画差異なども実践なくしてはわからないものが多かったです。 flexやjustify-contentなどの魔法の単語を知って作業がかなり楽になりました。translateとtop、bottomを組み合わせて縦位置をど真ん中に合わせるなども今は重宝しています。(これはいずれブログにまとめたい…)

WebRTC

ブラウザができることが大きく広がっていることを知りました。 以下のコード、htmlにて保存してChromeで開いてみてください。カメラが起動します(笑)

<html>
<body>
  <video id="video" width="640" />
</body>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script>
  navigator.getUserMedia({ audio: true, video: { width: 1280, height: 720 } },
    (stream) => {
      const video = document.querySelector('#video');
      video.src = URL.createObjectURL(stream);
      video.onloadedmetadata = (e => video.play());
    },
    err => console.log("The following error occurred: " + err.name),
  );
</script>
</html>

ローカルHTMLでもカメラがいとも簡単に起動する、それを保存したりもできる、ネイティブアプリとブラウザとの違いはどんどん少なくなっているようです。

逆に言えば、Webエンジニアに求められることもどんどん増えているわけで、今後も精進が必要そうです(^_^;)

Xcode

業務都合で自社アプリのビルド程度まではおこなうようになりました。開発はまだ触れていませんが、エラーコンソールとBuild Settingくらいは読むようにはなっていました。ビルドを行うようになって意識が変わったことのひとつに、MacOSXcodeのバージョンは比較的早く最新のものを追うようになりました。

モヒカンSlack

kotakanbeさんが運用されているSlackチームですが、こちらの利用で情報収集の効率が格段に良くなりました。 qiita.com

冬眠気味だったTwitterアカウントも改めて情報収集に活かせるようフォローを増やしたり手をかけましたが、情報収集方法も年々進化していますね。

TILをやってみたが…

syossan.hateblo.jp

Today I learndでTILですね。1週間も持ちませんでしたが(^_^;)

ブログよりも細かい粒度で身につけたことを残したいと思いはじめましたが、情報が少なすぎると一生懸命ネタを増やそうとしてしまうのがブログやってるときの癖で… 調べたことを一個単位で残していくのは非常に効率的だと思うので、今後はもう少しマメに書いていきたいです!

GitHub - ttaniguchi/til: Today I learned

まとめ

今年はわたしにとってはクライアント元年であり、非常に楽しい試みを多く経験できた年だったと思います。ただ、環境的には同じ技術軸で作業をする人間が身近におらず、ガラパゴス懸念を感じながらLintだ勉強会だと他者の技術や標準化にやや枯渇した時期でした。 来年は身近なメンバーに技術普及をすることで切磋琢磨できる環境をつくることにも尽力したいですね。

それと、やはりネイティブには触れていきたいです。Reactjsかvuejsか、みたいな時期もありました。それでもReactjsを選んだのは「アプリも作れるみたいだし…」というとろこもあったわけで。 そこに回帰して、来年はプライベートでReact Nativeをはじめてみようかと思っています。(1年くらい前にHwllo Worldくらいまではやった気がします)

今年も残すところ僅かですが、良いお年を! Happy Hacking

Github二段階認証後はID + tokenを使う

f:id:tak_taniguchi:20171207165716p:plain

https://github.com/settings/security

困ったこと

会社のルールでGithubで二段階認証(SMS)をおこなっていたのですが、コンソールでID+パスワードを求められた際に認証不足で処理が通らなくなります。

こんな場合です。
$ git clone https://github.com/ttaniguchi/private-repository.git
Cloning into 'private-repository'...
Username for 'https://github.com': ttaniguchi
Password for 'https://ttaniguchi@github.com':

対処方法

パスワードの代わりにPersonal access tokensを作れば大丈夫でした!

https://github.com/settings/tokens

  • 先ほどのパスワードの代わりに発行したトークンを利用すればOKです。
  • リポジトリの取り込み操作だけであれば、このあたりのチェックだけでおおよそOKです。

f:id:tak_taniguchi:20171207170258p:plain

これを知らないと二段階認証への移行に結構足踏みしそう… メモがてら共有します。

React+Redux環境でreact-router@v4への移行をおこなう

React Routerの4系がリリースされて久しいですが、相変わらず対応ができていませんでした。 ようやく重い腰を上げてアップデートしたのですが、噂に違わず障壁が多い移行だったため、メモがてら記録を残しておきます。

v3 → v4で何が変わったか

簡潔に言うと、とても良くなりました。

  • ルーティングコンポーネントのネストについての制約(縛り)が解消された。
  • contextを利用せずにパラメータを送れるようになった。

現在の状況

  • React+Reduxで構築されたサービスです。
  • react-router3系とreact-router-reduxを併せて利用していました。
  • 画面遷移はhistroyに対するrouter.push()がメインです。
<button onClick={context.router.push()} />

移行作業

パッケージの更新

今回更新したパッケージは以下の通りです。

react-router@3.0.0 → react-router-dom@4.2.2
  • DOMのみパッケージ分割されていましたが、こちらで事足りました。
react-router-redux@4.0.8 → @next
  • 4系への対応は@nextバージョンを利用するように、とのことでした。
  • それ以前のバージョンは2/3系用となっています。
query-string
  • クエリパラメータ取得に使います。

ルーティング部分

新たなルーティングがこちらです。

import React from 'react';
import { render } from 'react-dom';
import { HashRouter, Route, Switch } from 'react-router-dom';
import { createHashHistory } from 'history';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';

  :

const history = createHashHistory();
const store = configureStore(history);

render(
  (
    <Provider store={store}>
      <HashRouter>
        <ConnectedRouter history={history}>
          <NaviComponent>
            <Switch>
              <Route exact path="/" component={Top} />
              <Route path="/contents/:id" component={Contents} />
              <Route path="/contents" component={Contents} />
            </Switch>
          </NaviComponent>
        </ConnectedRouter>
      </HashRouter>
    </Provider>
  ),
  document.getElementById('app'),
);
  • ConnectedRouterがreduxと連結したhistoryを注入してくれます。
  • Hashによるルーティングを利用しているのでHashRouterで括っています。
  • NaviComponentのように共通のコンポーネントはそのまま組み込めます。
  • Switchで括っておくと、マッチするRouteが複数あっても最初にマッチしたコンポーネントしか表示しません。
  • pathは"/contents(/:id)"と書けなくなってしまったため、2行に分けて記載しています。
    • もっと良い方法があればコメントなどください。
  • URLパラメータの:id引き渡しについても以下の通り変更がありました。
- props.params.id
+ props.match.params.id

アクションの置き換え

  • push / replace / go / goBack等のファンクションはcontext.router内から取得していましたが、withRouter()メソッドの登場によって、Route直下にない孫コンポーネントにもprops経由でこれを送ることができるようになりました。
import { withRouter } from 'react-router-dom';

  :

export default withRouter(Component);
  • また、相対パスが利用できるようになったことも注意が必要でした。
- this.context.router.push('contents');
+ this.props.history.push('/contents');

contextを廃止

Context - React

公式ドキュメントにもあるように、contextは非推奨仕様でした。そもそもReactRouterの実装のために用いていたので、これを機にすべて撤廃することにしました。

クエリパラメータの対処

  • クエリが元のsearchパラメータからしか取得できなくなっていました。
  • query-stringを利用することで、key/valueに分割して値を返してくれます。
const search = props.location.search;
// ='?key1=hoge&key2=fuga'

const params = perse(search);
// = {
//   key1: 'hoge',
//   key2: 'fuga',
// }
  • また、Switchからも見て取れるように、パスでのみコンポーネントが切り替わるためクエリの変化はpropsに流れてこなくなりました。
    • 上の対応として、props内のhistory.listen()メソッドを利用します。こちらはクエリを含め、historyの更新に対してコールバックをおこなってくれます。

所感

主に詰まったところしか記載していないのですが、大概の情報はこちらのページで仕入れる事ができました。 reacttraining.com

以下のサイトにもお世話になりました。 qiita.com

今回のredux連携のようなケースは単体でのパッケージ導入と異なり、公式ドキュメントだけだとなかなか追いづらいところもありましたが、開発者側も周辺パッケージを意識しているようで、今回のアップデートはかなりreduxに寄せたものだったとも思いました。

今後もナレッジについてはメモがてら残していこうと思います。

補足

1018/05/18訂正

サンプルのコードで、HashRouter と ConnectedRouter のネストが逆になっていました。今のコードは訂正後のコードになっています。

HashRouter から振ってきたものを history 反映しないとダメですよね… 手元で起きた不具合としては、「history が一巡遅れて」いました。

Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門

Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門

Reactで絶対パスによるimportを使いたい

コンポーネントが深くなるとパスが読みづらい

ecmascriptでは外部ファイルを取り込む際に相対パスを指定します。

ただ、コンポーネントの階層が深くなるに連れて相対パスがわかりづらくなるので、絶対パスが使えればと思うケースも出てきます。

 -import Error from './../containers/ErrorContainer';
 -import Loader from './../containers/LoaderContainer';
 -import locale from './../libs/Locale';
 -import DevTools from './../DevTools';

  ↓ ↓ ↓

 +import Error from 'jsx/containers/ErrorContainer';
 +import Loader from 'jsx/containers/LoaderContainer';
 +import locale from 'jsx/libs/Locale';
 +import DevTools from 'jsx/DevTools';

babel-plugin-module-resolverを使う

Babelifyの際にパスを解決できれば絶対パスも利用可能です。 それを実現するライブラリがこちら。

www.npmjs.com

利用方法

npmでインストール

npm install -S babel-plugin-module-resolver

/.babelrcファイルに設定

{
  "plugins": [
    "transform-inline-environment-variables",
    ["module-resolver", {
      "alias": {
        "jsx": "./jsx", // componentを置くディレクトリを絶対パス化
      }
    }]
  ]
}

aliasとしてディレクトリ名を登録できるので、相対パスもそのまま利用できます。 npmパッケージと被らない名称であれば複数aliasを増やせます。

eslintも通したい

Babelで解決させた後はeslintでもパスを許容するよう修正します。 eslint-import-resolver-babel-moduleを利用するのが便利です。

www.npmjs.com

利用方法

npmでインストール

npm install -S eslint-import-resolver-babel-module

/. eslintrcファイルに設定

{
    "settings": {
        "import/resolver": {
            "babel-module": {}
        }
    }
}

これで絶対パスもlintが通るようになります。

ただ、VSCodeなどでタグジャンプなどを利用している場合、うまく動かなくなる可能性があります。こちらについては別途プラグインなどを調査中です。(見つかったら加筆します)