SwiftのDictionaryのsubscriptとupdateValueの違い
SwiftでDictionaryの値を追加、変更するのに2通りのやりかたがあります。
ひとつはsubscript、dict["key"] = "value"
もうひとつはupdateValue、dict.updateValue("value", forKey: "key")
です。
どちらを使ってもDictionaryの値を変更できますが、微妙に違いがあります。
1. 戻り値
updateValueの場合は戻り値で「変更前の値」を返します。 keyが存在しない場合はnilを返します。
例
var dict1 = ["a": "a"] print(dict1.updateValue("A", forKey: "a")) print(dict1.updateValue("b", forKey: "b"))
これを実行すると
"a" nil
となります。
2. nilをセットしたときの挙動の違い
subscriptの場合
updateValueの場合は
例
var dict1 = ["a": "a"] dict1["a"] = nil print(dict1)
これを実行すると
[:]
となり、key-valueが削除されます。
var dict1 = ["a": "a"] dict1.updateValue(nil, forKey: "a") // error: nil is not compatible with expected argument type 'String'
これはコンパイルエラーになります
var dict1: [String: String?] = ["a": "a", "b": "b"] dict1.updateValue(nil, forKey: "b") print(dict1)
これを実行すると
["a": Optional("a"), "b": nil]
Xcode10で地味にInterfaceBuilderで変わっているところ
UIの追加
Xcode9までは右下に出ていたのが、Xcode10だと右上の方にあるボタン(○の中に□のボタン)を押すと出てくるようになりました。
Xcode9
Xcode10
Image Literal
Xcode9だと画像名を入力すると補完されていましたが、Xcode10だとやり方が変わりました
- Xcode10
image
と入力すると補完でimage literalがでてくるので選択
表示されたアイコンをダブルクリック
または、
Shift + ⌘ + Mを押すとMediaの検索が出てくるのでそこからドラック&ドロップすることでも対応できます
git2.19以降のbranchのsort
gitの2.19がリリースされました
Highlights from Git 2.19 | The GitHub Blog
個人的な目玉機能はbranchのsortが楽になったという点です。
git branch --sort=-authordate
とすると更新日順にbranchが表示されます。
更に
git config --global branch.sort -authordate
というふうにconfigに設定しておくと毎回オプションをつけなくてもgit branch
するだけで更新日順に表示されます。
authordate以外には以下のようなsort optionが用意されています。
オプション | 説明 |
---|---|
--sort=numparent | shows merges by how awesome they are |
--sort=refname | sorts branches alphabetically by their name (this is the default, but may be useful to override in your configuration) |
--sort=upstream | sorts branches by the remote from which they originate |
Horizontal StackViewで左寄せにする
横方向のStackViewを普通に使うと余白は詰められて横幅いっぱい使うようになってしまいますが、たまに左寄せにしたい(右に余白を開けたい)場合があります。
そんなときはこのように空のViewを一番うしろに突っ込んでやることで解決できます。
let spacerView = UIView() spacerView.setContentHuggingPriority(.defaultHigh, for: .horizontal) stackView.addArrangedSubview(spacerView())
ただしこのやり方だとspacingを設定している場合は、余分にViewを一つ入れているため一番右に余計に一つspaceが入ってしまいます。
RxSwiftのVariableとBehaviorRelayとBehaviorSubjectの違い
動機
RxSwiftのVariableがdeprecatedになったということで、その代わりとしてBehaviorRelayに置き換えようと思ったときに、BehaviorRelayに単純に置き換えてよいのか?BehaviorSubjectもあるけどこっちはどうなんだっけ?という視点で調べてみました。
結論
単純にVariableをBehaviorRelayに置き換えて良い。
ただVariableのcompletedをトリガーになにかしていたら変更する必要がある。
Variableのvalueをmutateするようなことをしてたらちょっと影響ある。
詳細
Variable, BehaviorRelayのソースを見て違いを見ていきます。 (RxSwift 4.1.2時点)
Variable
VariableはRxSwift/Deprecated.swiftに定義されています。
VariableはBehaviorSubjectのwrapperで、コメントに書いてあるようにBehaviorSubjectとの違いはerrorでterminateしない
ことと、deallocateされたときにcomplete
する点にあります。
/// Unlike `BehaviorSubject` it can't terminate with error, and when variable is deallocated /// it will complete its observable sequence (`asObservable`)
ソースを見てみます。
Variableは自身でvar _value: E
というプロパティで値を保持していて、それをSpinLock()を使って排他制御しています。(え、spinlockなの?と思ってソースをみたらRecursiveLockで安心しましたtypealias SpinLock = RecursiveLock
)
public final class Variable<Element> { public typealias E = Element private let _subject: BehaviorSubject<Element> private var _lock = SpinLock() // state private var _value: E #if DEBUG fileprivate let _synchronizationTracker = SynchronizationTracker() #endif /// Gets or sets current value of variable. /// /// Whenever a new value is set, all the observers are notified of the change. /// /// Even if the newly set value is same as the old value, observers are still notified for change. public var value: E { get { _lock.lock(); defer { _lock.unlock() } return _value } set(newValue) { #if DEBUG _synchronizationTracker.register(synchronizationErrorMessage: .variable) defer { _synchronizationTracker.unregister() } #endif _lock.lock() _value = newValue _lock.unlock() _subject.on(.next(newValue)) } } /// Initializes variable with initial value. /// /// - parameter value: Initial variable value. public init(_ value: Element) { #if DEBUG DeprecationWarner.warnIfNeeded(.variable) #endif _value = value _subject = BehaviorSubject(value: value) } /// - returns: Canonical interface for push style sequence public func asObservable() -> Observable<E> { return _subject } deinit { _subject.on(.completed) } }
BehaviorRelay
VariableはRxCocoa/Traits/BehaviorRelay.swiftに定義されています。
BehaviorRelayはBehaviorSubjectのwrapperで、コメントにも書いてあるようにBehaviorSubjectとの違いはerrorやcompletedでterminateしない
ことです。
Variableとの違いは、
- RxSwiftからRxCocoaに変わった
- BehaviorRelayではcompletedがこない
ということになります。
/// Unlike `BehaviorSubject` it can't terminate with error or completed.
ソースを見てみます。
Variableのようにvalueを保持せず、acceptで値を変更するときはBehaviorSubjectのonNext()、値を取得するときにはcomputed propertyのvalueでBehaviorSubjectのvalue()を返します。
一点気をつける点は、Variableでは値の取得がvar _value: E
だったのがBehaviorRelayだとcomputed propertyになっているので、型が値型の場合、前者はmutable、後者はcopyが返るのでimmutableになります。なので特にArrayとかの場合はmutable前提の使い方をしてると影響があります。
また、BehaviorRelayでは排他はかけてませんが、BehaviorSubject側でRecursiveLockを使って排他しているので大丈夫です。
public final class BehaviorRelay<Element>: ObservableType { public typealias E = Element private let _subject: BehaviorSubject<Element> /// Accepts `event` and emits it to subscribers public func accept(_ event: Element) { _subject.onNext(event) } /// Current value of behavior subject public var value: Element { // this try! is ok because subject can't error out or be disposed return try! _subject.value() } /// Initializes behavior relay with initial value. public init(value: Element) { _subject = BehaviorSubject(value: value) } /// Subscribes observer public func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E { return _subject.subscribe(observer) } /// - returns: Canonical interface for push style sequence public func asObservable() -> Observable<Element> { return _subject.asObservable() } }
まとめ
BehaviorSubject, Variable, BehaviorRelayの違い
terminate
- BehaviorSubject
- error, completedでterminateする
- Variable
- errorでterminateしない。(dealloc時にcompletedでterminateする)
- BehaviorRelay
- terminateしない
import
- BehaviorSubject
- RxSwift
- Variable
- RxSwift
- BehaviorRelay
- RxCocoa
valueの取得
- BehaviorSubject
- function
- Variable
- property
- BehaviorRelay
- computed property
linuxでSwiftのObjCBool.boolValueを呼ぶとerror
不具合
SwiftでObjCBoolのboolValueをこのように呼び出すと
var isDir: ObjCBool = false let isExist = fm.fileExists(atPath: directory, isDirectory: &isDir) XCTAssertTrue(isDir.boolValue)
osxだと大丈夫だけどlinux環境だとerrorになる・・
error: value of type 'ObjCBool' (aka 'Bool') has no member 'boolValue' XCTAssertTrue(isDir.boolValue) ^~~~~ ~~~~~~~~~
原因
no memberなわけないだろと思ったら、ほんとにno memberだった・・・
対策
4.1で治ってるので
#if !os(Linux) || swift(>=4.1) XCTAssertTrue(isDir.boolValue) #else XCTAssertTrue(isDir) #endif
もしくは
たくさんの場所でboolValueを使っている場合はextensionで4.1未満の場合にboolValueを時前で実装してしまう
#if os(Linux) || !swift(>=4.1) extension ObjCBool { var boolValue: Bool { return Bool(self) } } #endif
electron-vueのproductionビルドで気をつけるところ
electron-vueでアプリを作ってみて、npm run devで開発環境で動かしたときは動いたけど、npm run buildで本番向けにビルドしたときは動かないみたいなことがちょいちょいあったので、それの対応方法です。
ファイルが見つからない
productionビルドの場合はwebpackでバンドルされてdist以下に配置されるので、devのときとディレクトリ構造が異なってきます。 なので__dirnameなどを使ってファイルのパスを指定していると駄目です。
下に記載のように__staticを用いる必要があります
path.join(__static, 'image.png')
ファイルの保存ができない
nedbなどで相対パスでファイルに保存するとproductionビルドでは保存に失敗します。
これは、app.getPath()を使ってplatform固有の保存用のパスを取得する必要があります。
const userData = app.getPath('userData'); db = new Datastore({ filename: `${userData}/db/log.db`, });
Macにインストールしようとするとセキュリティエラーがでる
作ったアプリをdmgファイルからインストールしようとすると、登録されていないアプリの警告ダイアログが表示されます。
これを出ないようにするには証明書をインストールしてビルドする必要があります。
Mac App Storeを通さずにアプリケーションを配布する手順
Apple Developer ProgramからDeveloper ID ApplicationとDeveloper ID Installerを作ってビルドするマシンに入れれば、あとはnpm run buildするとOK
Windows用のアプリが生成されない
デフォルトではMac版しかないので自分で追加する必要があります
package.jsonのscriptsにwindows向けのを追加します。
"scripts": { "build:win": "node .electron-vue/build.js && electron-builder -w",
Macだとビルドするにはwineをインストールする必要がありますので、homebrewなどを使ってインストールしておきましょう。
Electronに見えないwebviewを埋め込んで処理させる
すでにWeb版が存在してるけどショボいのでネイティブアプリでいい感じにしたいときがあったりすることもあります。
これを、Electronで作ったネイティブアプリの画面上に見えないwebviewを置くことで実現します。
方法
見えないwebviewの埋め込み
Electronアプリにwebviewを埋め込むことは簡単でただ
次にこれを非表示にします。 非表示というと普通はdispaly: noneを使いますが、display: noneでは残念ながらwebviewの中身が実行されません。 そこでwidthとheightを0にしてやることで非表示にします。
webviewの通信
webviewはrendererプロセスとは別プロセスになるのでwebview内のjsとrendererプロセスのjsでやり取りするにはプロセス間通信をする必要があります。
rendererプロセス→webview
webviewエレメントのsendというメソッドがあるのでこれを叩くことで、webview側にイベントを送ることができます。
webview側でこのイベントを受け取るには、受け取る用のjsファイルを用意してpreloadを使って読み込ませます。 そしてその受け取る用のjsファイルの中でipcRenderer.onを使ってrendererプロセスからのイベントを受け取ります。
webview->rendererプロセス
webviewからrendererプロセスへのイベントの送信はipcRenderer.sendToHostを使います。
rendererプロセス側でこのイベントを受け取るにはipc-messageをaddEventListererで登録すればよいです。
具体例
electron-vueでやる場合の例です。
<template> <div> <webview id="webview" v-bind:preload="preloadJS()" src="https://facebook.com" style="width:0;height:0;" > </webview> </div> </template>
<script> import path from 'path'; import { remote } from 'electron'; let webview = null; export default { data: function() { return { userId: '', password: '', } } mounted: function() { // webviewからのイベントの受信 webview = document.getElementById('webview'); webview.addEventListener('ipc-message', event => { if (event.channel === 'login') { switch (event.args[0]) { case "loading": break; case "loaded": break; case "success": console.log('ログイン成功'); break; case "failure": console.log('ログイン失敗'); break; } } }); }, methods: { preloadJS() { return `file:${path.join(__static, 'fb.js')}`; }, login() { // webviewへのイベントの送信 webview.send('login', this.userId, this.password); } },
// fb.js const { ipcRenderer } = require('electron'); document.addEventListener('DOMContentLoaded', event => { // rendererプロセスからのイベントの受信 ipcRenderer.on('login', (event, userId, password) => { // DOMをゴニョゴニョしてログインする }); }); document.addEventListener('readystatechange', event => { if ( window.location.href === 'https://facebook.com/sign' ) { // rendererプロセスへのイベントの送信 ipcRenderer.sendToHost('login', 'loaded'); } });
MacでIEとEdgeのテスト環境をつくる
MSからVirtualBoxのイメージが配布されているのでそれを使います。
1. VMのダウンロード
ここからVirtualBox用のVMをダウンロード
2. VMの起動
1でダウンロードしたzipを回答すると
MSEdge-Win10というフォルダ内にMSEdge-WIn10-disk001.vmdkとMSEdge-Win10.ovfというファイルが生成される
VirtualBoxを開いてファイル→仮想アプライアンスのインポートをクリック
インポートしたい仮想アプライアンスでMSEdge-Win10.ovfを選択
インポートをクリック
で完了です。
3. Macのローカルホストを参照する
EdgeやIEで10.0.2.2:80 とかを開くとlocalhostのページが見れるはずです。
4. hostsの設定
10.0.2.2とかをいれるのは面倒なのでhostsを使います。
まずpowershellを開いて
以下のコマンドを実行して、powershellを管理者権限で開きます
Start-Process powershell -Verb runAs
開いたpowershellで以下のコマンドを実行してhostsファイルを開きます
start notepad "C:\Windows\System32\drivers\etc\hosts"
hostsに設定を書いて終了です。
10.0.2.2 hoge.com
Prettierで.vueをフォーマット
Prettierのv1.10でVueのsingle file componentをformatできるようになりました。
それまではeslintででたエラーを人手でポチポチ直してたのでこの辺りが自動化できるのは最高です。
やることはすでにprettier導入済みであれば*.vueを対象のファイルにいれるだけです。
// package.json "format": "./node_modules/.bin/prettier --write \"js/**/*.{js,vue}\"",
lint-stagedを使ってprecommit hookしてる場合も同様です
"lint-staged": { "gitDir": "../", "*.{js,vue}": [ "yarn format", "git add" ],
ReactでESCキーを押したときにモーダルを閉じる
このようにdivにonKeyDownをつけた場合、キーを押してもonKeyDownは呼ばれません。
<div onKeyDown={this.onKeyDown} >
tabIndex
をつけて要素にフォーカスを持つことができるようにすれば解決します。
<div onKeyDown={this.onKeyDown} tabIndex="0" >
SwiftTweets 2018 Winterの資料のまとめ
SwiftTweets 2018 Winterの資料のまとめです。
https://swift-tweets.github.io/2018-winter
発表
どうやってSwiftのOSSをメンテナンスしていくか
https://twitter.com/ikesyo/status/954687472369201155
Swift Foundationにコントリビュートする
https://twitter.com/takasek/status/954696314171740160 https://qiita.com/takasek/items/01f7746bf444bd5c85c0
LT
iOSじゃないところでSwiftを使う
https://twitter.com/hiragram/status/954699064855076864
世界をプログラミングで満たしたい
2017振り返り
2017年の振り返りです。
仕事
これまで長らくiOSをやっていましたが、4月からフロントエンド担当になりYADOKARIというメディアを開発してます。
React.jsとかVue.jsを使ったりしてますが、ES6時代だとそんなにJavaScriptについての学習コストがかからないので iOSアプリのエンジニアからのコンバートは割と楽でした。
cssのほうがむしろハードルが高かった気がしてます。(精神的にも)
アウトプット
Swiftの排他制御についてあまり詳しい情報なかったので書きました。 明日から使えない!Swiftの排他制御 | Supership Tech Blog
プライベート
子供が一歳になりました。 今年は勉強会とかもいっさい参加せず、子育てに全てを費やしました。
勉強とかはいつでもできますけど、子育ては今しかできないので最優先です。
まとめ
今年はフロントエンドと子育ての一年だったなぁといった感じでした。
正直iOSはここ最近そんなに新しいことが少なくなってきてて学びはあんまりないなぁと思っていたのでちょうどよかったです。フロントエンドから学ぶことも多々ありますし。
来年も子供の圧倒的な成長を見習いつつがんばりたいです。