will and way

ただの自分用メモを人に伝える形式で書くことでわかりやすくまとめてるはずのブログ

2019年買ってよかったもの/やめたもの

2019年に買ってよかったもの・やめたものをつらつらと。
Amazonはアフィリンク

ガジェット/趣味

ノートPCスタンド

会社の福利厚生で1台買った後に、自宅でも欲しくなって2台目を購入した。

  • 目線が上がるため、複数ディスプレイを使うとき、目線の移動がスムーズに感じる
  • ヒートシンク代りになる。iOSアプリをビルドしたりdocker containerを複数立ち上げてると結構発熱するのでありがたい

バーナー

料理の仕上げや、キャンプの時の火付けに役立ってくれた。

オフィスチェア(エルゴヒューマンプロ)

www.iamworkaholic.jp

アーロンチェアを買いに行ったが、試しに座って見て一番しっくりきて、アーロンチェアの半分の価格だったので購入。10万以下なので消耗品として計上できるのも○。

WORKAHOLICではコンシェルジュと呼ばれる椅子選びやエキスパートに座り方や椅子選びのコツ、それぞれのイスの特徴を教えてもらいながら同時に複数脚を試せた。

地面と自分の間に挟まるものは試して一番いいものを選ぶのがヨシ!

AirPods Pro

www.apple.com

求めてたのは音質じゃなくて聴き方だったと思える一品。

  • バイス間の切り替え
  • 電池の持ち
  • そこそこのノイズキャンセリング
  • 外部音取り込みモード
  • ポータビリティ
  • 操作のシンプルさ
  • 音楽視聴だけでなく、テレカンにも使える

使い勝手の良さから、耳の可処分時間を有効活用できるようになった。特にPodcastをよく聞くようになった。

Pixel3a

store.google.com

 

  • Nexus5がそろそろ使い物にならなくなってきたため、買い替え
  • 夜景モードやソフトウェアぼかしはPixelの方が優秀に感じる

大きめのハンガー

  • ハンガーが小さいと、肩と襟の中間にハンガーのあとがのこる問題を解決したくて買い換え
  • 男性肩幅サイズのハンガーにすると、服の生地の縫い目にちょうどハンガーのへりがくるので、干した時の仕上がりフォルムがきれいになる

50色ソックス(ユニクロ)

www.uniqlo.com

  • マスタード、エンジ、白、茶色あたりをいくつか購入。
  • 靴下をちょっと色味をくわえるだけで、靴や服の組み合わせが締まる感じがよかった。コスパ良し。

革靴

www.sanyoyamacho.com

  • カジュアルにはける革靴として
  • 勘三郎のブラックがなかったが、2018年の秋口に追加されてたので思わず購入。
  • Uチップ外羽根ということで、普段着に合わせてもドレッシーさとカジュアルさが両立できるのでとてもいい。

スパイス(シード/パウダー)

  • ↑の著者に料理教室でスパイスカレーの作り方を教わって、自宅でも作りたくなったため上野に。
  • 上野の大津屋商店は単価も低い、小分けで売られているので2000円もあればかなりこだったスパイス選びができる。

  • 一軒家を買った
  • 子供が生まれる&掛け捨ての家賃を資産にしようということで
  • 広さに対しての価格が激安
  • ポータルサイトで探してある程度物件を見て知識や土地勘を得た後、ポータルサイトを排除してググると買主向けの仲介手数料が無料の不動産屋に出会えるのでおすすめ。

一軒家 -site:suumo.jp -site:athome.co.jp -site:homes.co.jp

こんな感じで、-site:ドメインを追加していくと、排除できる。

不動産業界は不動産屋/売主(建主)/行政書士全員がズブズブなので、ふっかけを回避するには不動産関連の免許を取って建主になるしかないんやなと思った。

滑り止めシート

www.nitori-net.jp

  1. 新居に移るにあたって、ニトリの滑り止めシートをいろんなところに敷いた
  2. コスパよし
  3. デスクの脚やPCの下敷きにもヨシ

洗濯機台

www.heianshindo.co.jp

  • 新居に移るにあたって、台をつけた
  • キャスター付きなので洗濯機を移動できて掃除やレイアウト変更が楽
  • 底上げされて排水ホースに勾配がうまれるため、水がしっかり流れてくれる
  • 移動せずともクイックルワイパーなどで洗濯機下の掃除ができる

電動アシスト付き自転車

www.yamaha-motor.co.jp

  • 妻の足として
  • 乗る頻度が低いこともあって1回の充電で1ヶ月くらいもつ
  • 立ち漕ぎしなくても坂道がサクサク登れる

乾燥機機能付き洗濯機

kadenfan.hitachi.co.jp

  • 縦型でつけ置き洗いができるかどうかも譲れない&ちゃんとシワを伸ばして乾かしたい派なので、縦型に。エセじゃない乾燥機能がついているので、乾燥も可能
  • 実際には乾燥機能を使える衣類は限られるので、毛布やタオルくらい
  • 容量がでかいと一気に洗えるし、水量に余裕があると洗浄力も低下しないのでよかった

曇り止め

hands.net

  • 年末の掃除にお風呂の鏡をこれで洗った
  • 親水式なので入浴中の曇りが全くなくなった
  • レビューでは2週間くらいで効果が切れるとのことなので様子見

サブスク

Netflix

  • 特に言うことなし
  • ヨシ

1Password Family

1password.com

  • 個人で使ってた
  • 家族の個人情報やインフラの登録情報をすべて1Passwordに登録した
  • 家族の共通言語として、連絡帳などの共有にも使えている

NURO光

  • 有線で900Mbps位出る
  • 工事の遅さと開通後の快適さは噂通り
  • 工事は戸建てにかぎり、5000円課金で宅内外工事を同日にやってくれるオプションがあることを、工事が完了してから知った

宅内工事の後、宅外工事のスケジュール調整電話は待つよりも連絡した方がいい。2週間くらい待ってもこなかったので、問い合わせたらその日の1週間後にできるとのことで即座に申し込んだ。

NURO Mobile

  • キャリア特にこだわりがなかった
  • 回線は都内なら良好(自宅だと20Mbps)
  • 3000円前後で電話とデータ通信を求めてたので

LinksMate

  • 5GB から 1GBに変更
  • Abema視聴やゲームのDLはカウントフリーで利用可能なのでヨシ

Amazon Kindle Unlimited

日本経済新聞

  • 情報が早め
  • マクロ的に時流を読んだ記事や連載がある
  • 事実ベースの記述が多い。情報収集のバイアスはあっても事実
  • 記事に本題にまつわる周辺知識が適切に散りばめられている

クックパッド

  • 自炊を趣味にした
  • 王道のレシピはもちろん、余った具材の大量消費系レシピや手間を徹底排除した料理などの工夫をこらした資料がたくさんある
  • 能動的にレシピを検索する人にとってはDELISH KITCHENやクラシルよりもクックパッド
  • クックパッドのエンジニアリスペクトもかねて

Evernote

  • なんでもEvernoteに突っ込んで記録するようになった

マネーフォワードクラウド

  • 確定申告と請求書作成をメインにつかうため

GSuite

  • 個人事業メールやドキュメント管理で使っている

Apple Developer

  • 納税

Nintendo Online

  • 納税

お名前どっとこむ

やめたサブスク

Amazon Prime

  • いろいろ充実してきて、物を買うことがなくなった。2017年→2019年にかけて物の購入件数が10件くらいずつ減少してきたのが大きい。
  • 偽物・模倣品の多さ
  • 引っ越して、帰り道にスーパーや量販店があるので買って帰るようになった
  • 最悪、消耗品はヨドバシで良くなった。ヨドバシの方が安くてポイントもつく

Docomo

  • MVNOと同じ質で、高い料金は払わなくて良い
  • 5Gがきたら考える

Docomo

  • NURO契約と同時に解約した

 プロが教える! ホームマッサージで元気になる本 (中経の文庫) 

妻がマッサージ好きなので、勉強して見た。ツボやマッサージする時の体の使い方をしると、効率的

LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する impress top gearシリーズ

開発プロセスや組織構造がデリバリーに与える影響について網羅的に書かれている

SRE サイトリライアビリティエンジニアリング

www.oreilly.co.jp

サービスの"質"についての言語化。信頼性のポートフォリオをどうするか。WhatとHowが書かれていて実践的

サイロ・エフェクト 

縦割りの組織がうむ弊害について。逆張りするならば、上流ではコラボレーションによってレバレッジが聞く手立てを考えることが大事

ファクトフルネス

ファクトかどうか、ファクトだとしてもバイアス(情報の切り取り)がかかってないかどうか、そして、マーケティングで使われる思考を取り入れて情報を捉えるのが大事。著者のTEDもよい。

iOSシミュレータのクリップボードを同期しやすくするTips

f:id:matsuokah:20190627204535p:plain

TL; DR

MacのカスタムショートカットにiPhone SimulatorのPasteboard管理をバインディングすると便利だったのでメモりました。

そもそも、クリップボードと言う名称かと思っていましたが、ペーストボードが正しいようですね。pbcopyコマンドもありますし。

ログとかユーザー情報をコピーする時、いろいろ捗る気がする〜

環境

Xcode10.2.1

手順

1. SimulatorのAutomatically Sync Pasteboardのチェックを外します。チェックを外すことで、2つのメニューが選択可能になります。

Edit > Get Pasteboard ... シミュレーターのペーストボードをホスト機のペーストボードにコピーします

Edit > Send Pasteboard ... ホスト機のペーストボードをシミュレーターのペーストボードにコピーします

f:id:matsuokah:20190627205815p:plain

 

2. システム環境設定 > キーボード > ショートカットタブでSend Pasteboard / Get Pasteboardにショートカットを設定します。

f:id:matsuokah:20190627212253p:plain

カスタムショートカットはメニューと同じ名前に対して、コマンドを紐付けることができる機能です。

メニュータイトルは一字一句しないと有効にならないので気をつけてください。

私は下記のように紐付けました

 

Send Pasteboard ▷ cmd + shift + v

Get Pasteboard ▷ cmd + shift + c

 

なお、アプリを選択する時、Simulatorはディレクトリを直接指定して選択するといいです。Simulatorのディレクトリは/Applicationsにないため、ディレクトリまでたどり着くのが面倒なtめ。

 

① その他を選択

f:id:matsuokah:20190627211300p:plain

 

② ディレクトリを直接指定するウィンドウを立ち上げる

f:id:matsuokah:20190627211534p:plain

 

シミュレーターのディレクトリは

/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app

です

 

 このアプリごとに設定できるキーボードショートカット機能を使い始めるとめちゃくちゃ便利なので使い倒しましょう〜

iOSのSecurity Frameworkのステータスコード逆引き

Appleのセキュリティ周りのステータスコードでnumberとエラーが紐づかないのでメモ

In addition to the codes listed here, certain Security framework services provide additional status codes that are specific to that service. In particular, see Authorization Services Result Codes, Sessions API Result Codes, Secure Transport Result Codes, Secure Download Result Codes, and Code Signing Services Result Codes.

他のステータスコードは追加していないです。

Success

errSecSuccess: 0

Error(降順)

errSecUnimplemented: -4
errSecDskFull: -34
errSecIO: -36
errSecOpWr: -49
errSecParam: -50
errSecWrPerm: -61
errSecAllocate: -108
errSecUserCanceled: -128
errSecBadReq: -909
errSecInternalComponent: -2070
errSecCoreFoundationUnknown: -4960
errSecACLNotSimple: -25240
errSecPolicyNotFound: -25241
errSecInvalidTrustSetting: -25242
errSecNoAccessForItem: -25243
errSecInvalidOwnerEdit: -25244
errSecTrustNotAvailable: -25245
errSecUnsupportedFormat: -25256
errSecUnknownFormat: -25257
errSecKeyIsSensitive: -25258
errSecMultiplePrivKeys: -25259
errSecPassphraseRequired: -25260
errSecInvalidPasswordRef: -25261
errSecInvalidTrustSettings: -25262
errSecNoTrustSettings: -25263
errSecPkcs12VerifyFailure: -25264
errSecNotAvailable: -25291
errSecReadOnly: -25292
errSecAuthFailed: -25293
errSecNoSuchKeychain: -25294
errSecInvalidKeychain: -25295
errSecDuplicateKeychain: -25296
errSecDuplicateCallback: -25297
errSecInvalidCallback: -25298
errSecDuplicateItem: -25299
errSecItemNotFound: -25300
errSecBufferTooSmall: -25301
errSecDataTooLarge: -25302
errSecNoSuchAttr: -25303
errSecInvalidItemRef: -25304
errSecInvalidSearchRef: -25305
errSecNoSuchClass: -25306
errSecNoDefaultKeychain: -25307
errSecInteractionNotAllowed: -25308
errSecReadOnlyAttr: -25309
errSecWrongSecVersion: -25310
errSecKeySizeNotAllowed: -25311
errSecNoStorageModule: -25312
errSecNoCertificateModule: -25313
errSecNoPolicyModule: -25314
errSecInteractionRequired: -25315
errSecDataNotAvailable: -25316
errSecDataNotModifiable: -25317
errSecCreateChainFailed: -25318
errSecInvalidPrefsDomain: -25319
errSecInDarkWake: -25320
errSecNotSigner: -26267
errSecDecode: -26275
errSecMissingEntitlement: -34018
errSecServiceNotAvailable: -67585
errSecInsufficientClientID: -67586
errSecDeviceReset: -67587
errSecDeviceFailed: -67588
errSecAppleAddAppACLSubject: -67589
errSecApplePublicKeyIncomplete: -67590
errSecAppleSignatureMismatch: -67591
errSecAppleInvalidKeyStartDate: -67592
errSecAppleInvalidKeyEndDate: -67593
errSecConversionError: -67594
errSecAppleSSLv2Rollback: -67595
errSecQuotaExceeded: -67596
errSecFileTooBig: -67597
errSecInvalidDatabaseBlob: -67598
errSecInvalidKeyBlob: -67599
errSecIncompatibleDatabaseBlob: -67600
errSecIncompatibleKeyBlob: -67601
errSecHostNameMismatch: -67602
errSecUnknownCriticalExtensionFlag: -67603
errSecNoBasicConstraints: -67604
errSecNoBasicConstraintsCA: -67605
errSecInvalidAuthorityKeyID: -67606
errSecInvalidSubjectKeyID: -67607
errSecInvalidKeyUsageForPolicy: -67608
errSecInvalidExtendedKeyUsage: -67609
errSecInvalidIDLinkage: -67610
errSecPathLengthConstraintExceeded: -67611
errSecInvalidRoot: -67612
errSecCRLExpired: -67613
errSecCRLNotValidYet: -67614
errSecCRLNotFound: -67615
errSecCRLServerDown: -67616
errSecCRLBadURI: -67617
errSecUnknownCertExtension: -67618
errSecUnknownCRLExtension: -67619
errSecCRLNotTrusted: -67620
errSecCRLPolicyFailed: -67621
errSecIDPFailure: -67622
errSecSMIMEEmailAddressesNotFound: -67623
errSecSMIMEBadExtendedKeyUsage: -67624
errSecSMIMEBadKeyUsage: -67625
errSecSMIMEKeyUsageNotCritical: -67626
errSecSMIMENoEmailAddress: -67627
errSecSMIMESubjAltNameNotCritical: -67628
errSecSSLBadExtendedKeyUsage: -67629
errSecOCSPBadResponse: -67630
errSecOCSPBadRequest: -67631
errSecOCSPUnavailable: -67632
errSecOCSPStatusUnrecognized: -67633
errSecEndOfData: -67634
errSecIncompleteCertRevocationCheck: -67635
errSecNetworkFailure: -67636
errSecOCSPNotTrustedToAnchor: -67637
errSecRecordModified: -67638
errSecOCSPSignatureError: -67639
errSecOCSPNoSigner: -67640
errSecOCSPResponderMalformedReq: -67641
errSecOCSPResponderInternalError: -67642
errSecOCSPResponderTryLater: -67643
errSecOCSPResponderSignatureRequired: -67644
errSecOCSPResponderUnauthorized: -67645
errSecOCSPResponseNonceMismatch: -67646
errSecCodeSigningBadCertChainLength: -67647
errSecCodeSigningNoBasicConstraints: -67648
errSecCodeSigningBadPathLengthConstraint: -67649
errSecCodeSigningNoExtendedKeyUsage: -67650
errSecCodeSigningDevelopment: -67651
errSecResourceSignBadCertChainLength: -67652
errSecResourceSignBadExtKeyUsage: -67653
errSecTrustSettingDeny: -67654
errSecInvalidSubjectName: -67655
errSecUnknownQualifiedCertStatement: -67656
errSecMobileMeRequestQueued: -67657
errSecMobileMeRequestRedirected: -67658
errSecMobileMeServerError: -67659
errSecMobileMeServerNotAvailable: -67660
errSecMobileMeServerAlreadyExists: -67661
errSecMobileMeServerServiceErr: -67662
errSecMobileMeRequestAlreadyPending: -67663
errSecMobileMeNoRequestPending: -67664
errSecMobileMeCSRVerifyFailure: -67665
errSecMobileMeFailedConsistencyCheck: -67666
errSecNotInitialized: -67667
errSecInvalidHandleUsage: -67668
errSecPVCReferentNotFound: -67669
errSecFunctionIntegrityFail: -67670
errSecInternalError: -67671
errSecMemoryError: -67672
errSecInvalidData: -67673
errSecMDSError: -67674
errSecInvalidPointer: -67675
errSecSelfCheckFailed: -67676
errSecFunctionFailed: -67677
errSecModuleManifestVerifyFailed: -67678
errSecInvalidGUID: -67679
errSecInvalidHandle: -67680
errSecInvalidDBList: -67681
errSecInvalidPassthroughID: -67682
errSecInvalidNetworkAddress: -67683
errSecCRLAlreadySigned: -67684
errSecInvalidNumberOfFields: -67685
errSecVerificationFailure: -67686
errSecUnknownTag: -67687
errSecInvalidSignature: -67688
errSecInvalidName: -67689
errSecInvalidCertificateRef: -67690
errSecInvalidCertificateGroup: -67691
errSecTagNotFound: -67692
errSecInvalidQuery: -67693
errSecInvalidValue: -67694
errSecCallbackFailed: -67695
errSecACLDeleteFailed: -67696
errSecACLReplaceFailed: -67697
errSecACLAddFailed: -67698
errSecACLChangeFailed: -67699
errSecInvalidAccessCredentials: -67700
errSecInvalidRecord: -67701
errSecInvalidACL: -67702
errSecInvalidSampleValue: -67703
errSecIncompatibleVersion: -67704
errSecPrivilegeNotGranted: -67705
errSecInvalidScope: -67706
errSecPVCAlreadyConfigured: -67707
errSecInvalidPVC: -67708
errSecEMMLoadFailed: -67709
errSecEMMUnloadFailed: -67710
errSecAddinLoadFailed: -67711
errSecInvalidKeyRef: -67712
errSecInvalidKeyHierarchy: -67713
errSecAddinUnloadFailed: -67714
errSecLibraryReferenceNotFound: -67715
errSecInvalidAddinFunctionTable: -67716
errSecInvalidServiceMask: -67717
errSecModuleNotLoaded: -67718
errSecInvalidSubServiceID: -67719
errSecAttributeNotInContext: -67720
errSecModuleManagerInitializeFailed: -67721
errSecModuleManagerNotFound: -67722
errSecEventNotificationCallbackNotFound: -67723
errSecInputLengthError: -67724
errSecOutputLengthError: -67725
errSecPrivilegeNotSupported: -67726
errSecDeviceError: -67727
errSecAttachHandleBusy: -67728
errSecNotLoggedIn: -67729
errSecAlgorithmMismatch: -67730
errSecKeyUsageIncorrect: -67731
errSecKeyBlobTypeIncorrect: -67732
errSecKeyHeaderInconsistent: -67733
errSecUnsupportedKeyFormat: -67734
errSecUnsupportedKeySize: -67735
errSecInvalidKeyUsageMask: -67736
errSecUnsupportedKeyUsageMask: -67737
errSecInvalidKeyAttributeMask: -67738
errSecUnsupportedKeyAttributeMask: -67739
errSecInvalidKeyLabel: -67740
errSecUnsupportedKeyLabel: -67741
errSecInvalidKeyFormat: -67742
errSecUnsupportedVectorOfBuffers: -67743
errSecInvalidInputVector: -67744
errSecInvalidOutputVector: -67745
errSecInvalidContext: -67746
errSecInvalidAlgorithm: -67747
errSecInvalidAttributeKey: -67748
errSecMissingAttributeKey: -67749
errSecInvalidAttributeInitVector: -67750
errSecMissingAttributeInitVector: -67751
errSecInvalidAttributeSalt: -67752
errSecMissingAttributeSalt: -67753
errSecInvalidAttributePadding: -67754
errSecMissingAttributePadding: -67755
errSecInvalidAttributeRandom: -67756
errSecMissingAttributeRandom: -67757
errSecInvalidAttributeSeed: -67758
errSecMissingAttributeSeed: -67759
errSecInvalidAttributePassphrase: -67760
errSecMissingAttributePassphrase: -67761
errSecInvalidAttributeKeyLength: -67762
errSecMissingAttributeKeyLength: -67763
errSecInvalidAttributeBlockSize: -67764
errSecMissingAttributeBlockSize: -67765
errSecInvalidAttributeOutputSize: -67766
errSecMissingAttributeOutputSize: -67767
errSecInvalidAttributeRounds: -67768
errSecMissingAttributeRounds: -67769
errSecInvalidAlgorithmParms: -67770
errSecMissingAlgorithmParms: -67771
errSecInvalidAttributeLabel: -67772
errSecMissingAttributeLabel: -67773
errSecInvalidAttributeKeyType: -67774
errSecMissingAttributeKeyType: -67775
errSecInvalidAttributeMode: -67776
errSecMissingAttributeMode: -67777
errSecInvalidAttributeEffectiveBits: -67778
errSecMissingAttributeEffectiveBits: -67779
errSecInvalidAttributeStartDate: -67780
errSecMissingAttributeStartDate: -67781
errSecInvalidAttributeEndDate: -67782
errSecMissingAttributeEndDate: -67783
errSecInvalidAttributeVersion: -67784
errSecMissingAttributeVersion: -67785
errSecInvalidAttributePrime: -67786
errSecMissingAttributePrime: -67787
errSecInvalidAttributeBase: -67788
errSecMissingAttributeBase: -67789
errSecInvalidAttributeSubprime: -67790
errSecMissingAttributeSubprime: -67791
errSecInvalidAttributeIterationCount: -67792
errSecMissingAttributeIterationCount: -67793
errSecInvalidAttributeDLDBHandle: -67794
errSecMissingAttributeDLDBHandle: -67795
errSecInvalidAttributeAccessCredentials: -67796
errSecMissingAttributeAccessCredentials: -67797
errSecInvalidAttributePublicKeyFormat: -67798
errSecMissingAttributePublicKeyFormat: -67799
errSecInvalidAttributePrivateKeyFormat: -67800
errSecMissingAttributePrivateKeyFormat: -67801
errSecInvalidAttributeSymmetricKeyFormat: -67802
errSecMissingAttributeSymmetricKeyFormat: -67803
errSecInvalidAttributeWrappedKeyFormat: -67804
errSecMissingAttributeWrappedKeyFormat: -67805
errSecStagedOperationInProgress: -67806
errSecStagedOperationNotStarted: -67807
errSecVerifyFailed: -67808
errSecQuerySizeUnknown: -67809
errSecBlockSizeMismatch: -67810
errSecPublicKeyInconsistent: -67811
errSecDeviceVerifyFailed: -67812
errSecInvalidLoginName: -67813
errSecAlreadyLoggedIn: -67814
errSecInvalidDigestAlgorithm: -67815
errSecInvalidCRLGroup: -67816
errSecCertificateCannotOperate: -67817
errSecCertificateExpired: -67818
errSecCertificateNotValidYet: -67819
errSecCertificateRevoked: -67820
errSecCertificateSuspended: -67821
errSecInsufficientCredentials: -67822
errSecInvalidAction: -67823
errSecInvalidAuthority: -67824
errSecVerifyActionFailed: -67825
errSecInvalidCertAuthority: -67826
errSecInvaldCRLAuthority: -67827
errSecInvalidCRLEncoding: -67828
errSecInvalidCRLType: -67829
errSecInvalidCRL: -67830
errSecInvalidFormType: -67831
errSecInvalidID: -67832
errSecInvalidIdentifier: -67833
errSecInvalidIndex: -67834
errSecInvalidPolicyIdentifiers: -67835
errSecInvalidTimeString: -67836
errSecInvalidReason: -67837
errSecInvalidRequestInputs: -67838
errSecInvalidResponseVector: -67839
errSecInvalidStopOnPolicy: -67840
errSecInvalidTuple: -67841
errSecMultipleValuesUnsupported: -67842
errSecNotTrusted: -67843
errSecNoDefaultAuthority: -67844
errSecRejectedForm: -67845
errSecRequestLost: -67846
errSecRequestRejected: -67847
errSecUnsupportedAddressType: -67848
errSecUnsupportedService: -67849
errSecInvalidTupleGroup: -67850
errSecInvalidBaseACLs: -67851
errSecInvalidTupleCredendtials: -67852
errSecInvalidEncoding: -67853
errSecInvalidValidityPeriod: -67854
errSecInvalidRequestor: -67855
errSecRequestDescriptor: -67856
errSecInvalidBundleInfo: -67857
errSecInvalidCRLIndex: -67858
errSecNoFieldValues: -67859
errSecUnsupportedFieldFormat: -67860
errSecUnsupportedIndexInfo: -67861
errSecUnsupportedLocality: -67862
errSecUnsupportedNumAttributes: -67863
errSecUnsupportedNumIndexes: -67864
OSStatus): -67865: errSecUnsupportedNumRecordTypes: OSStatus: -67866
errSecIncompatibleFieldFormat: -67867
errSecInvalidParsingModule: -67868
errSecDatabaseLocked: -67869
errSecDatastoreIsOpen: -67870
errSecMissingValue: -67871
errSecUnsupportedQueryLimits: -67872
errSecUnsupportedNumSelectionPreds: -67873
errSecUnsupportedOperator: -67874
errSecInvalidDBLocation: -67875
errSecInvalidAccessRequest: -67876
errSecInvalidIndexInfo: -67877
errSecInvalidNewOwner: -67878
errSecInvalidModifyMode: -67879
errSecMissingRequiredExtension: -67880
errSecExtendedKeyUsageNotCritical: -67881
errSecTimestampMissing: -67882
errSecTimestampInvalid: -67883
errSecTimestampNotTrusted: -67884
errSecTimestampServiceNotAvailable: -67885
errSecTimestampBadAlg: -67886
errSecTimestampBadRequest: -67887
errSecTimestampBadDataFormat: -67888
errSecTimestampTimeNotAvailable: -67889
errSecTimestampUnacceptedPolicy: -67890
errSecTimestampUnacceptedExtension: -67891
errSecTimestampAddInfoNotAvailable: -67892
errSecTimestampSystemFailure: -67893
errSecSigningTimeMissing: -67894
errSecTimestampRejection: -67895
errSecTimestampWaiting: -67896
errSecTimestampRevocationWarning: -67897
errSecTimestampRevocationNotification: -67898

FACTFULNESS - ファクトフルネス

学び

  • ファクトとは一体なんなのかを知る。ファクトとはデータとそれを比較・加工して、場面に応じて柔軟に相対的/絶対的に捉えること。1次情報とまではいかないが、加工されていないデータと切り口は宝。切り口の多さ=スコープの多さ
  • 人は事実や事象に対して捉え方の癖(バイアス)がある。捉え方の癖を知ることで、その観点を疑える。疑うことで因果/相関にたどり着ける

 

 

感想

情報の捉え方や視野が狭くなっていることへの問題提起と、分類することで解決策や気をつけるべきことをそれぞれにまとめている。筆者は統計学/医学/公衆衛生学を学び、経済/農業/貧困を研究および実践してきた。実践する中で事象と原因は1対1ではなく、複数の要因があること、そして、バイアスやフィルタが通った情報や分断された情報、そして、自分1人の知識量だけで判断した結論は思ったよりもスコープが狭いことを指摘していた。

それぞれを〜本能として、それぞれがなぜ危険なのか?を説いている。個人的には「トンカチを持った子供はなんでも釘に見える」という表現がささった。僕はエンジニアであり、習得済みの技術を使って問題を解決したがる。果たして、その習得済みの技術はその課題に対して最適なのだろうか。これはまさにトンカチを手にした子供なのだと思った。

 

TEDでも人気スピーカーのよう。

www.ted.com

【Ver. 2】 AppStore Connectでアプリがレビューに入ったとか、リジェクトされたとかをSlackに通知する

adventar.org

この記事はCyberAgent Developers Advent Calendar 2018の12/14の記事です。

 

今回はタイトルの通り、AppStore Connectのステータスを通知する仕組みを実装していきます。

TR;DR

Amason AWSを使いました。 AppStore Connect -> SES -> SNS -> Lambda -> Slack

  1. Appleのメールの通知先にSESで受け取れるメールアドレスを追加する
  2. SESでメールを受ける
  3. SNSでメールの通知を受け取る
  4. Lambdaでメールをパースし、Slackに投げる

Lambdaではこちらのスクリプトを動かしました。

AWSLambdaAppStoreConnectStatus.py · GitHub

発端

このスクリプトをTypeScriptにバージョンアップしよう画策していました。そこで、会社の後輩が「AWSのLambdaでやってみては?」とアドバイスをくれたのでやってみました。

AWSはほぼ触れたことがなかったのでちょうどよかったです。各サービスの設定を説明していきたいと思います。普段はiOSエンジニアでAWSではEC2のmicroインスタンスを作ったことしかないマンなので予防線をひかせてください(笑)

blog.matsuokah.jp

元のスクリプトでやっていたこと

簡単に説明すると、こんなことをやっていました

  1. Google App ScriptでAppStore Connectからの未読のステータスメールを検索
  2. 件名含まれている文字列からステータスを判定
  3. Slackに通知する
  4. メールを既読にする

これを5分くらいのタイムスケジューラで動かしていました。

Pros
  • 無料
  • ほぼセットアップなしにできる
  • スプレッドシートに保存したりできるので、使い方は無限大
Cons
  • 既読にすると通知されない。
  • たまにスクリプトがこける。
  • 個人のアカウントでスケジューラを動かすため、引き継ぎが面倒
  • 若干のタイムラグがある

ということで、AWSを使っている組織であればメールの設定とAWSの設定をした方が、会社の資産としても使いやすいのは言うまでもないですね。それでは実際の作業ログを書いていきます

 

1. SESでメールを受け取ってLambdaを起動するところまで作る

1. SESのEmail受信設定を入れる

docs.aws.amazon.com

まずはマニュアル通り設定することで、メールを受け取ることができるようになります。今回はS3にメールを保存する必要はないのでスキップします。

また、ドメイン販売業者のDNSなどを利用している場合は、そちらのコンソールで作業が必要になります。自分は必要でMXレコードとTXTを外部のDNS設定で行いました。

2. Amazon SNSのトピックを作成する

適当に名前をつけて作ればOKです 

3. SESのActionでSNSのトピックにパブリッシュする

SESのコンソールで、先ほど作ったトピックにメールをパプリッシュする設定をいれます。Rule Setsから新たにルールを設定し、Actionを追加します。Actionの先でSNSを選択すると候補にトピックが出てくるので選択するだけです。エンコーディングはUTF-8を選択しました

4. AWS Lambdaのエンドポイントを作成する

「一から作成する」で作っていけば終わります。今は作るところまででOKです

5. SNSのサブスクリプションを作成する

SESと紐づけたトピックをサブスクライブし、Lamdaを起動するサブスクリプションを作成します。ここでもLambdが候補に出てきてくれるので、ぽちぽちしていけば終わります。

 

これで、メールを受け取って、Lambdaの関数を起動することができるようになりました。

 

SNSはトピックを入り口としてサブスクリプションがトピックと出口をつなぐようなものと捉えたらOKでしょうか。Apach FlumeのSource / Sinkのようなものをだなーと感じました

2. メールの内容を取得し、ステータスを検知する

これが地味にめんどくさかったです。

Lambdaでは第1引数のeventにトリガーとなった情報が入ってきます。

Lambdaのテスト設定にSESのメールオブジェクトがあったので、その通りにパースしてみたのですがうまくいきませんでした

一旦、EventのオブジェクトをS3にダンプして、JSONの中身をjqでみてみることにしました。

 

Python(boto3)でS3にデータをファイル保存せず直接アップロードする方法 | DevelopersIO

 

S3の権限設定にハマる

書き込もうとするとAccess Denidedと怒られました...

そこでLambdaを作成する時に一緒に作成されていた「ロール」に対しS3のWrite権限を設定することで、解決しました。

 

S3に吐き出されたJSONファイルを見てみると

events[0]['Sns']['Message']のなかにメールの件名が含まれている時JSONがあることがわかりました。

最終的に件名は 

json.loads(event['Records'][0]["Sns"]["Message"])["mail"]["commonHeaders"]["subject"]

で取得することができました。

 

あとは、件名からステータスを判定し、Attachmentを作ってWebHook URLを叩くだけです。

3. Slackに通知する

Pythonのrequestsを使ってURLを叩くのですが、pipでインストールしたライブラリをLambda上で使うにはベンダリング(依存関係を含め全てパッケージに含めること)をする必要があります。

 

こちらを参考にrequestsを含めたあとzipしてアップロードすることで解決しました

qiita.com

 

あとは元のスクリプトで組み込んだロジックを組み込めば終わりです!

 

ということでメールを送ってみます。

f:id:matsuokah:20181214151001p:plain

 

f:id:matsuokah:20181214151017p:plain

 

やたーーーー!

 

ということで、AppStore Connect -> SES -> SNS -> Lambda -> Slackでメールが来るようになりました。今日の午前2時くらいからやりはじめた初心者でもサクッとこういうことができるAWSはすごいですね(小並感)

料金は、Appleからのメールは多くても月に100件も行かないので無料枠の範囲内におさまりそうです。

 

本来ならterraformとかで自動的に構築するのがモダンなのでしょうか?

優秀な後輩たちに聞いてみようと思います。

 

ということで、こんな提案をしてくれる後輩たちがいる僕たちのチームで一緒に働きませんか!という広告を掲載して締めたいと思います。

www.wantedly.com

 

www.wantedly.com

 

まとめ

  • GASは便利だが属人化しがちという短所を持ち合わせているので、資産とするならAWSへの移行を視野に入れよう!
  • バックエンド/SREエンジニア応募よろしくです 🙏

iOSDC 2018 「二癖くらいある画面収録からの生放送」というタイトルで発表してきました!

「二癖くらいある画面収録からの生放送」というタイトルで発表してきました!

内容としてはiOS11から可能になった画面収録をBroadcast Upload ExtensionというReplayKitを用いたキャプチャ機構から生放送する機能を実装した時の話です。

iOSDCとは

iosdc.jp

iOSアプリデベロッパーによるiOSデベロッパーのためのiOS関連技術の共有を目的とした国内最大級のカンファレンスです。
アプリ開発という文脈を各エンジニアがテスト、設計、Swiftという言語仕様やコンパイラといったような観点で掘り下げた内容を聞けるめちゃくちゃいい機会です。
2016年から開催されており、今年で3回目になります。3回目にして4日間開催ってすごいですね。毎年1日増えてる気がします。
自分は去年はサイトスタッフ、今年はスピーカーとして参加しました。iOSという共通言語を持った者同志が集まって技術について会話できるのは本当にありがたいですね。

提出したCfP

二癖くらいある画面収録からの生放送 by matsuokah | プロポーザル | iOSDC Japan 2018 - fortee.jp

なぜこの題材で発表したのか

シンプルに3つです

  • ReplayKitを使った生放送というマイナーなトピックを発表することで同志に会えるといいなと思った
  • Appleのドキュメントに書かれていない内容やバグを踏んだので共有することで、誰かの力になれればいいなと思った
  • 登壇はいつか乗り越えたい壁だった

マイナーなトピックでも人が集まるところで話せば、少なくとも興味持ってくれる人はきてくれますし、相談してくれる/壁打ちできる相手ができる気と思っています。 あと、乗り越えたい壁としてハードルなのが、マサカリが来るのでは?この知識は当たり前なのでは?と思うわけですが

「自分の当たり前の知識は誰かが欲しているもの」そして、「概ね、その知識を知らない人が聞きに来る」ので、割とカジュアルな気持ちでCfPを出しました。

伝えたかったこと

ReplayKitはBufferを受け取ったあと、メディアの編集が自由にできるというところや、モバイル端末ならではのイベント(通話などによるインタラプト)のハンドリングをすることが配信者のUX向上に繋がることは伝えられたかなと思います。

Sli.doを使ってみた

f:id:matsuokah:20180902112400j:plain f:id:matsuokah:20180902112356j:plain

Sli.doを使って、アンケートを取りながら発表してみました。

ということで、ReplayKitを使っている同志には会えなかったのは残念でしたが、発表を聞きにきてくださっていた方々ではHLSの認知度がかなり高かったので発表のしがいがありました。

最後に

もし、聴きにきてくださっていた方で、ベストトーク賞に投票がまだの方はぜひよろしくお願いします!
もしくは、気になったことなどあれば Twitterでご質問いただければと思います!

TypeScriptでGoogle App Scriptを実装する環境を作る

TypeScriptは型をもっていてJSに変換できる言語みたいな感じでしょ?くらいの経験値しかないですが、Google App Scriptをスクリプトエディタでずっとやっていてつらみを感じたので、
ローカル開発環境を作りつつ、TypeScriptに手を出してみようと思いました。

nodeを実践的に使ったこともTypeScriptも書いたことなかったので、備忘録としてまとめてみる。

TL; DR

browserifyやらbabelでGoogle App Scriptランタイムに変換してあげればOK

GitHub - matsuokah/gas-launchpad

Google App Scriptをローカルで開発する環境を作るメリット

  • gitで履歴を追跡できる
  • モジュール化
  • テストがしやすい
  • JSではなくTypeScriptで開発できる
  • IDEの恩恵が受けられる

個人的にはモジュール化や設計もできるならやりたかったので、やりやすさが増すというのはありがたい

Google App Scriptのランタイムを理解する

  1. もろもろのimport周りが使えない
  2. ES5(?)
  3. DOM操作はできない。consoleなどで使えるwindowオブジェクト等もない。(それらはブラウザのグローバル変数)という認識出会っているだろうか...

つまり、TypeScriptで開発したいなら、ES5にして1ファイルにしてあげるのが一番よさそう。

TypeScriptをGASにまとめる手順

  1. TypeScriptをJS(ES5)に変換する
  2. プロジェクト内の依存関係をGAS使える形にする

使ったもの

  • gulp -> JSのビルドフローを定義する
  • browserify -> 1ファイルにまとめる
  • tsify -> browserifyで解決した依存関係をJavaScriptに変換する.ここでtsconfig.jsonが読み込まれる
  • baberify -> require, import構文をランタイムに依存しない形式に変換する(?)
  • gasify -> browserifyGASが使える形式に変換する

基本的にはbrowserifyの恩恵を受けて、プラグインで変換していく感じ。

これらを使うことで、プレーンなTSをGASにアップロード可能なJSに変換ができるようになりました。

gulpfile.js

TS > JSの変換部分

const outputFile = 'main.js';
const distributionDirName = 'dist';

gulp.task('build:ts', () => {
    browserify({
        entries: './src/app.ts'
    })
        .plugin('tsify')
        .transform('babelify')
        .plugin('gasify')
        .bundle()
        .pipe(source(outputFile))
        .pipe(gulp.dest(distributionDirName));
});

あとは、tsを書くだけ。

src/core/hello.ts

export function world(): void {
    Logger.log("hello");
}

src/core/world.ts

export function world(): void {
    Logger.log("world");
}

src/app.ts

import {hello} from "./core/hello";
import {world} from "./core/world";

declare namespace glFunctions {
    interface global {
        main(): void;
    }
}

declare var global: glFunctions.global;

global.main = () => {
    hello();
    world();
};

ここが重要

declare namespace glFunctions {
    interface global {
        main(): void;
    }
}


declare var global: glFunctions.global;

global.main = () => { }

app.tsに記述したglFunctionsのglobalに対してmainのような関数を定義し、実装をつけてあげることで、GAS化した時の関数候補にすることができます。

この場合だと、mainという関数がコンボボックスの関数候補に現れるようになります。

f:id:matsuokah:20180707151646p:plain

Google App Scriptで使えるGsuit系のオブジェクトの補完を有効にする

package.jsonのdevDependencyに下記を入れてあげる。そうするとtsで補完が効くようになる。頭いい。

    "@types/google-apps-script": "0.0.24",

リポジトリ

基本的に下記の’リポジトリをテンプレートとして使えば、幸せになれるはず!

github.com

参考

【GoogleAppsScript】ES6を使ったGoogle Apps Scriptの開発

anyenvのndenvを使ってnode.jsのバージョンを管理する

gulp + browserify + tsifyを利用してTypeScriptコンパイル環境を作る - $shibayu36->blog;

GitHub - naoki-sawada/gas-typescript-webpack: This is an example of writing Google Apps Script in TypeScript and building with webpack

CSS Spriteっぽいことを手軽にiOSでやる

やりたいこと

アトラス化(1つの画像に複数がまとまられていること)されている画像をいい感じにスライダー上に表示させたい。

具体的にはYouTubeのシークバーとサムネイルみたいなことをしたい。

f:id:matsuokah:20180630184049g:plain

できたもの

f:id:matsuokah:20180630184241g:plain

やったこと

アトラス化されている画像は下記のようなものを想定してみます。

f:id:matsuokah:20180630184355p:plain

ルールは簡単で、1枚の画像にN行*M列枚がパッキングされているのみです。 ここから特定のindexを指定し、それをトリミングして表示させました

UIScrollViewのContentSizeとContentOffsetを使う

今回は手軽にやりたいので、UIScrollViewをViewPortの役割のために使って見ます。

やることは下記の通り

  1. UIScrollViewに元の画像をUIImageViewでaddSubviewする
  2. UIScrollViewのサイズを決める
  3. 元画像のサイズと元画像を基準にしたトリミングサイズを割り出す
  4. トリミングサイズとUIScrollViewのサイズから拡縮率を算出する
  5. UIImageViewのサイズに拡縮率をかける
  6. 表示したいindexから列の行と行を割り出し、そこにcontentOffsetを合わせる

なぜUIScrollViewをつかったか?

ほかには、元画像からトリミングした画像を生成し、UIImageViewにセットするという方法が挙げられると思います。

  • 画像のキャッシュが1枚で済む(複雑なキャッシュ戦略を実装しなくていい)
  • 画像の生成が走らない

この2つの利点が大きいのでは?ということで、UIScrollViewでよしなにトリミングする方法を選びました。

リポジトリ

github.com