- 投稿日:2020-12-07T23:39:45+09:00
VTCompressionSessionEncodeFrame deadlock現象
現象
動画のsample bufferを独自作成されたqueueでエンコードしてますが、たまにencoderリセットしたあとエンコード走るとVTCompressionSessionEncodeFrame
でdeadlockになって、statusも返ってこないまあまあ死んでしまうcode
-(void)append:(CMSampleBufferRef)sb { CFRetain(sb); dispatch_async(queue, ^{ ..... // ここでdead lock,statusも返ってこない auto s = VTCompressionSessionEncodeFrame(mImpl.mSession, cr, CMSampleBufferGetPresentationTimeStamp(sb), CMSampleBufferGetDuration(sb), nullptr, nullptr, &flags); if (s) { NSLog(@"encode status %d", (int)s); } // samplebuffer解放 CFRelease(sb); }); }原因
encoder session解放処理をエンコード処理と同じqueueで処理してなかったから。
修正
-(void)invalidateSession { dispatch_async(queue, ^{ if (mSession) { VTCompressionSessionInvalidate(mSession); } }); }
- 投稿日:2020-12-07T23:15:34+09:00
SwiftLintのAnalyzeを使って高度な解析をする方法
はじめに
本記事は Swift/Kotlin愛好会 Advent Calendar 2020 の8日目の記事です。
SwiftLintのAnalyze機能を紹介します。環境
- OS:macOS Big Sur 11.0.1
- Swift:5.3.1
- Xcode:12.2 (12B45b)
- SwiftLint:0.41.0
本記事で説明しないこと
- SwiftLintの概要やセットアップ方法
私が以前書いた記事を参考にしてください。
Swiftの静的解析ツール「SwiftLint」のセットアップ方法 - Qiita- SwiftのASTについて
そもそも私が理解できていないので説明できません。
詳しく学びたい人は以下の動画が参考になると思います。
try! Swift Tokyo 2018 - AST Meta-programming - YouTubeSwiftLintの「Analyze」とは?
かんたんにいうと「ビルドログのASTを使って解析する」機能です。
Analyzeは実験的な機能であり、いつでも変更される可能性があるとのことです。
Analyzeの使い方
Analyzeの使い方を紹介します。
設定ファイルの作成
Analyze用のルールを設定ファイルの
analyzer_rules
に記述します。.swiftlint.ymlanalyzer_rules: #- explicit_self # 関数は `self.` を付けずに呼び出したいため - unused_declaration - unused_import公式ドキュメントで「Analyzer rule: Yes」となっているルールが対象で、私が見た限りでは以下の3つのみでした。
ルール 説明 参考リンク Explicit Self self.
を明示的に書くべきhttps://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#explicit-self Unused Declaration 宣言した変数やクラスなどは使われるべき https://qiita.com/uhooi/items/8e9767c2e746f4171ded#unused-declaration Unused Import インポートされたモジュールは使われるべき https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#unused-import 私は同一クラス内の関数を
self.
なしで呼び出したいため、下の2つのみ有効にしています。Analyzeの実行
READMEに記載されている通り
xcodebuild
コマンドでログをファイルに出力し、それを--compiler-log-path
オプションで渡します。コマンドが長くなるので、私は
Makefile
に定義してmake analyze
で実行できるようにしました。
make build-debug
はCIでビルドが通るかの確認にも使っているため、swiftlint analyze
に対して必要最小限にはなっていません。MakefilePRODUCT_NAME := UhooiPicBook # 製品名を適宜変更する PROJECT_NAME := ${PRODUCT_NAME}.xcodeproj SCHEME_NAME := ${PRODUCT_NAME} TEST_SDK := iphonesimulator TEST_CONFIGURATION := Debug TEST_PLATFORM := iOS Simulator TEST_DEVICE ?= iPhone 12 Pro Max TEST_OS ?= 14.2 TEST_DESTINATION := 'platform=${TEST_PLATFORM},name=${TEST_DEVICE},OS=${TEST_OS}' XCODEBUILD_BUILD_LOG_NAME := xcodebuild_build.log .PHONY: analyze analyze: # Analyze with SwiftLint $(MAKE) build-debug mint run swiftlint swiftlint analyze --autocorrect --compiler-log-path ./${XCODEBUILD_BUILD_LOG_NAME} .PHONY: build-debug build-debug: # Xcode build for debug set -o pipefail \ && xcodebuild \ -sdk ${TEST_SDK} \ -configuration ${TEST_CONFIGURATION} \ -project ${PROJECT_NAME} \ -scheme ${SCHEME_NAME} \ -destination ${TEST_DESTINATION} \ build \ | tee ./${XCODEBUILD_BUILD_LOG_NAME} \ | bundle exec xcpretty --color
--autocorrect
オプションを付けると自動で修正されるのでオススメです。
make analyze
を実行します。$ make analyze # ... # `make build-debug` のログは省略 # ... mint run swiftlint swiftlint analyze --autocorrect --compiler-log-path ./xcodebuild_build.log Loading configuration from '.swiftlint.yml' Correcting Swift files at paths Collecting 'Debug.swift' (1/23) Collecting 'ActivityRouter.swift' (2/23) # ... Correcting 'MonsterListInteractor.swift' (15/23) /Users/uhooi/Documents/Repos/GitHub/uhooi/UhooiPicBook/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift:10:1 Corrected Unused Import Correcting 'MonsterListViewController.swift' (16/23) # ... Correcting 'MonsterDetailViewController.swift' (22/23) Correcting 'SceneDelegate.swift' (23/23) Done correcting 23 files!
.../SpotlightRepository.swift:10:1 Corrected Unused Import
のログからわかる通り、SpotlightRepository.swift
の10行1列目に未使用のモジュールがインポートされているので自動で削除されました。
git diff
で確認します。$ git diff diff --git a/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift b/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift index e435a9b..ba45ffb 100644 --- a/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift +++ b/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift @@ -7,7 +7,6 @@ import CoreGraphics.CGGeometry import CoreSpotlight -import MobileCoreServices /// @mockable protocol SpotlightRepository: AnyObject { // swiftlint:disable:this file_types_order確かにインポート文が削除されています。
今回は一度実行済みなので1箇所のみ削除されましたが、初回は大量の
import Foundation
とimport UIKit
が削除されて気持ちいいです。おまけ①: 使用しているインポートが削除される場合がある
先ほど削除された
import MobileCoreServices
ですが、実はないとビルドエラーになります。理由としては、iOS 13以前で
kUTTypeData
の呼び出しに使っているためです。SpotlightRepository.swiftprivate func createAttributeSet(title: String, contentDescription: String, thumbnailData: Data?) -> CSSearchableItemAttributeSet { let attributeSet: CSSearchableItemAttributeSet if #available(iOS 14.0, *) { attributeSet = .init(contentType: .data) } else { attributeSet = .init(itemContentType: kUTTypeData as String) // !!!: ここでビルドエラーになる } // ... // 中略 // ... return attributeSet }
xcodebuild
の-destination
オプションでiOS 14.2を指定しているためだと思われます。
仕方ないので手動でインポート文を戻しました。このようなケースはまれですが、自動修正する場合はコミット前にビルドが通るか確認しましょう。
おまけ②: Analyzeの実行タイミング
READMEに記載されている通り、Analyzeには時間がかかります。
そのため、Xcodeのビルドフェイズで毎回実行するのを避けています。現在は手動実行しているのですが、それだと漏れる可能性があるため、いい方法を考え中です。
おわりに
これで未使用のインポート文を大量に削除できます!
以上、 Swift/Kotlin愛好会 Advent Calendar 2020 の8日目の記事でした。
明日はまだ埋まっていません。ぜひ こちら から参加しましょう!
@Sho-heikun さんが参加してくださりました!
ありがとうございます
ウホーイさんを1人にさせるわけにはいかないので明日分入れときました! 1人で1週間埋めたいとかあったらキャンセルしますので教えてください!w
— ShoHeiKun (@ShoHeiKun3) December 8, 2020参考リンク
- 投稿日:2020-12-07T22:36:21+09:00
macOS/iOS Keychain と Security framework のCLIで証明書を管理しよう
はじめに
macOS/iOS アプリを開発していると、定期的に証明書の更新が必要になりますね。
証明書を作った後に、手元の開発マシンに証明書を登録したり、CIとして運用されているビルドマシンに証明書を登録したり、といったように証明書を登録するところはいくつかあり、それぞれに対して手作業のメンテナンスが発生することがあります。
運用形態に合わせてできるところから自動化して管理していこうと、まずは macOS で証明書の操作やキーチェーンの管理などのセキュリティフレームワークをコマンドラインから実行して試してみました。
具体的には
/usr/bin/security
コマンドについて試しています。※
macOS Catalina Version 10.15.7
の環境で試しています。コマンド操作してみよう
まずは
man
コマンドを使ってできることを見てみます。$ man security security(1) BSD General Commands Manual security(1) NAME security -- Command line interface to keychains and Security framework SYNOPSIS security [-hilqv] [-p prompt] [command] [command_options] [command_args] DESCRIPTION A simple command line interface which lets you administer keychains, manipulate keys and certificates, and do just about anything the Security framework is capable of from the command line. By default security will execute the command supplied and report if anything went wrong. If the -i or -p options are provided, security will enter interactive mode and allow the user to enter multiple commands on stdin. When EOF is read from stdin security will exit. Here is a complete list of the options available: -h If no arguments are specified, show a list of all commands. If arguments are provided, show usage for each the specified commands. This option is essentially the same as the help command. -i Run security in interactive mode. A prompt (security> by default) will be displayed and the user will be able to type commands on stdin until an EOF is encountered. -l Before security exits, run /usr/bin/leaks -nocontext on itself to see if the command(s) you executed had any leaks. -p prompt This option implies the -i option but changes the default prompt to the argument specified instead. -q Will make security less verbose. -v Will make security more verbose. SECURITY COMMAND SUMMARY security provides a rich variety of commands (command in the SYNOPSIS), each of which often has a wealth of options, to allow access to the broad functional- ity provided by the Security framework. However, you don't have to master every detail for security to be useful to you. Here are brief descriptions of all the security commands: help Show all commands, or show usage for a command. list-keychains Display or manipulate the keychain search list. default-keychain Display or set the default keychain. login-keychain Display or set the login keychain. create-keychain Create keychains. delete-keychain Delete keychains and remove them from the search list. lock-keychain Lock the specified keychain. unlock-keychain Unlock the specified keychain. set-keychain-settings Set settings for a keychain. set-keychain-password Set password for a keychain. show-keychain-info Show the settings for keychain. dump-keychain Dump the contents of one or more keychains. create-keypair Create an asymmetric key pair. add-generic-password Add a generic password item. add-internet-password Add an internet password item. add-certificates Add certificates to a keychain. find-generic-password Find a generic password item. delete-generic-password Delete a generic password item. set-generic-password-partition-list Set the partition list of a generic password item. find-internet-password Find an internet password item. delete-internet-password Delete an internet password item. set-internet-password-partition-list Set the partition list of a internet password item. find-key Find keys in the keychain set-key-partition-list Set the partition list of a key. find-certificate Find a certificate item. find-identity Find an identity (certificate + private key). delete-certificate Delete a certificate from a keychain. delete-identity Delete a certificate and its private key from a keychain. set-identity-preference Set the preferred identity to use for a service. get-identity-preference Get the preferred identity to use for a service. create-db Create a db using the DL. export Export items from a keychain. import Import items into a keychain. cms Encode or decode CMS messages. install-mds Install (or re-install) the MDS database. add-trusted-cert Add trusted certificate(s). remove-trusted-cert Remove trusted certificate(s). dump-trust-settings Display contents of trust settings. user-trust-settings-enable Display or manipulate user-level trust settings. trust-settings-export Export trust settings. trust-settings-import Import trust settings. verify-cert Verify certificate(s). authorize Perform authorization operations. authorizationdb Make changes to the authorization policy database. execute-with-privileges Execute tool with privileges. leaks Run /usr/bin/leaks on this process. smartcards Enable, disable or list disabled smartcard tokens. list-smartcards Display available smartcards. export-smartcard Export/display items from a smartcard. error Display a descriptive message for the given error code(s).上記は一部ですが、できることがたくさんあります。今回はその中の一部ですが、試したことを順を追って書いてみます。
キーチェーンのサーチリストを表示する
$ security list-keychain "/Users/user/Library/Keychains/login.keychain-db" "/Library/Keychains/System.keychain"デフォルトキーチェーンを表示する
$ security default-keychain "/Users/user/Library/Keychains/login.keychain-db"ログインキーチェーンを表示する
$ security login-keychain "/Users/user/Library/Keychains/login.keychain-db"キーチェーンを作成する
今回は
test.keychain
キーチェーンを作成してみることにします。$ security create-keychain -p password test.keychain上記の作成コマンドを実行しただけだとサーチリストに表示されません。
$ security list-keychain "/Users/user/Library/Keychains/login.keychain-db" "/Library/Keychains/System.keychain"lsコマンドでファイルが作成されていることが見えます。
$ ls -l /Users/user/Library/Keychains/キーチェーンのサーチリストに追加します。
これを実行するとGUIのKeychain access にも表示されるようになりました。$ security list-keychains -d user -s login.keychain-db test.keychain-dbキーチェーンのサーチリストを見ると、上記の追加コマンドで引数に指定した順で表示されていました。
$ security list-keychain "/Users/user/Library/Keychains/login.keychain-db" "/Users/user/Library/Keychains/test.keychain-db" "/Library/Keychains/System.keychain"キーチェーンの中身を表示する
$ security dump-keychain test.keychain-dbキーチェーンに証明書をインポートする
アプリのビルドに使うためにコードサインを指定して証明書をインポートします。
$ security unlock -p password "/Users/user/Library/Keychains/test.keychain-db" $ security import Certificates.p12 -k "/Users/user/Library/Keychains/test.keychain-db" -P "" -T /usr/bin/codesign 1 identity imported. $ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password "/Users/user/Library/Keychains/test.keychain-db"キーチェーンから証明書をエクスポートする
初回の一回だけ許可するポップアップダイアログで許可する必要がありました。キーチェーン単位で1回ポップアップダイアログが表示されるようでした。2回目以降はコマンドだけで実行できました。
$ security unlock-keychain -p password "/Users/user/Library/Keychains/test.keychain-db" $ security export -k "/Users/user/Library/Keychains/test.keychain-db" -f pkcs12 -P "" -o ./mycerts.p12キーチェーンから証明書を削除する
common name を指定して削除する場合は、
$ security unlock -p password "/Users/user/Library/Keychains/test.keychain-db" $ security delete-certificate -c name "/Users/user/Library/Keychains/test.keychain-db"または、SHA-256 または SHA-1 ハッシュ値を指定して削除する場合は、
$ security unlock -p password "/Users/user/Library/Keychains/test.keychain-db" $ security delete-certificate -Z SHA-1 "/Users/user/Library/Keychains/test.keychain-db"このハッシュ値は、コマンド操作で取得する方法は見つけることができず、GUI の Keychain Access から証明書の詳細を見ると Fingerprints に表示されている値で実行できました。
キーチェーンを削除する
$ security delete-keychain "/Users/user/Library/Keychains/test.keychain-db"おわりに
OSのバージョンが上がったりで見直しは定期的に必要になりますが、煩雑な作業をコマンドで処理することで運用形態に合わせた運用フローの自動化が進められると思います。
- 投稿日:2020-12-07T21:26:15+09:00
条件分岐 ? : の使い方
はじめに
この書き方を見た時に、こんな書き方もあるんや!って思ったが、
この書き方を調べてもわからなかった(?:と調べてもわからなかった)ので、自分なりに一旦まとめていこうと思います。
色々わかり次第、情報を追加していこうと思います。使われ方
条件式 ? 式か値① : 式か値②
説明すると、条件式がtrueなら?の後ろの①、falseなら:の後の②を実行すると言うもの。試してみた
switchを用意してisOnがtrueの時とfalseの時を比較して見た。
let switch1 = UISwitch() let switch2 = UISwitch() switch1.isOn = true let number1 = 1 * (switch1.isOn ? 1 : -1) //1 * 1が実行され、number1 = 1 switch2.isOn = false let number2 = 1 * (switch2.isOn ? 1 : -1) //1 * -1が実行され、number2 = -1このように、trueの時に?の後が実行され、falseの時には:の後の処理が行われる。
- 投稿日:2020-12-07T19:40:15+09:00
react nativeでのIDFA対応(AdjustSDK利用)
※まだリリースまでこじつけてないので、問題はあるかも・・・
背景
年明けにAppleさんが広告ID利用の許諾確認必須にするらしいので、その対応を実施したので、そのときAdjustSDKのバージョンアップ対応時のメモ(微妙にgithubの説明がわかりづらかったので
前提
react native: 0.61.4
yarn : 1.21.1
react-native-adjustを既に導入済みでアップグレード対応対応概要
- package.jsonの更新とライブラリのnpmのパッケージの更新
- podsの該当ライブラリの更新
- Xcodeプロジェクトに必要ライブラリの追加
- 許諾ポップアップを表示するコードの追加
- Info.plistに許諾用文言の追加
1.package.jsonの更新とライブラリのnpmのパッケージの更新
プロジェクトのpackage.jsonの該当ライブラリのバージョンをIDFAの対応が入っている4.23.0以上にする
package.json“react-native-adjust”: “^4.23.1",package.json更新後以下コマンドを実行
yarn upgrade react-native-adjust2.podsの該当ライブラリの更新
iOSプロジェクトのディレクトリで以下コマンドを実行
pod update react-native-adjust3.Xcodeプロジェクトに必要ライブラリの追加
- Xcodeで該当プロジェクトを開く
- サイドメニューの
show the Project navigator
を開いて、プロジェクトファイルを選択する- メイン部分で
TARGETS
を選択して、Build Phases
を選択するLink Binary With Libraries
を開いて、+
ボタンを押下して以下のライブラリを追加する。追加後追加したすべてのライブラリのStatusをRequiredからOptionalに変更する
- StoreKit.framework
- AppTrackingTransparency.framework
- CoreTelephony.framework
- iAd.framework
- AdSupport.framework
4.許諾ポップアップを表示するコードの追加
この辺は公式の記述通りにすんなり。以下コードを自分はトップの画面の
componentDidMount
に記述しました。Adjust.requestTrackingAuthorizationWithCompletionHandler(function(status) { switch (status) { case 0: // ATTrackingManagerAuthorizationStatusNotDetermined case break; case 1: // ATTrackingManagerAuthorizationStatusRestricted case break; case 2: // ATTrackingManagerAuthorizationStatusDenied case break; case 3: // ATTrackingManagerAuthorizationStatusAuthorized case break; } });5.Info.plistに許諾用文言の追加
ルート直下でNSUserTrackingUsageDescriptionの項目を追加して、Valueには許諾のポップアップに表示したい、文言を記述する。以上でbuildすればiOS14以降で広告ID取得許諾のポップアップが表示されるかと思います。
参考
- 投稿日:2020-12-07T17:51:31+09:00
SwiftのKeyPathと戯れる
KeyPath
とは?
KeyPath
とは?Swift4で追加された、プロパティに動的にアクセスするための記法
Use key-path expressions to access properties dynamically.1
Swift3.xまでの
#keyPath
class Hoge: NSObject { // ? @objc var foo: String // ? let bar: String init(foo: String, bar: String) { self.foo = foo self.bar = bar } } let foo = hoge.value(forKeyPath: #keyPath(Hoge.foo)) print(type(of: foo)) // Optional<Any> print(#keyPath(hoge.bar)) // error: argument of '#keyPath' refers to non-'@objc' property 'bar'
Obj-C時代からあるKVOでのtypoを防ぐ程度の役割しかない
NSObject
にしか使えない@objc
宣言されたプロパティのみ- 返却値は
Any!
(型情報が消える?)- 解析が遅い、Darwinでしか使えない2といった問題点も
Swift4からの
KeyPath
- Swiftの強力な型システムに基づいた機構
- structにもenumにも使える
- 型情報が維持される
- Swiftのstdlibに含まれているのでプラットフォームに依存しない
NSObject
も@objc
宣言もいらないclass Hoge { var foo: String let bar: String } let hogeFoo: ReferenceWritableKeyPath<Hoge, String> = \.foo let hogeBar = \Hoge.bar // KeyPath<Hoge, String> let hoge = Hoge(foo: "foo", bar: "bar") print(hoge.foo) // foo hoge[keyPath: hogeFoo] = "hoge-foo" print(hoge.foo) // hoge-foo hoge[keyPath: hogeBar] = "bar" // error: cannot assign through subscript: 'hogeBar' is a read-only key path
structにも使える
struct Fuga { var foo: String let bar: String } let fugaFoo: WritableKeyPath<Fuga, String> = \.foo let fugaBar = \Fuga.bar // KeyPath<Fuga, String> var fuga = Fuga(foo: "foo", bar: "bar") print(fuga.foo) // foo fuga[keyPath: fugaFoo] = "fuga-foo" print(fuga.foo) // fuga-foo fuga[keyPath: fugaBar] = "bar" // error: cannot assign through subscript: 'fugaBar' is a read-only key path
KeyPath
関連クラス_AppendKeyPath └ AnyKeyPath └ PartialKeyPath<Root> └ KeyPath<Root, Value> └ WritableKeyPath<Root, Value> // Value is variable └ ReferenceWritableKeyPath<Root, Value> // Root is class
小ネタ
Tupleも
Root
になれるlet hogeFuga = (hoge, fuga) let hogeKeyPath = \(Hoge, Fuga).0 // no label let fugaKeyPath = \(hoge: Hoge, fuga: Fuga).fuga // with labels hogeFuga[keyPath: hogeKeyPath] // hoge hogeFuga[keyPath: fugaKeyPath] // fuga
プロパティのネストも可能
struct FooBar { var hoge: Hoge var fuga: Fuga } let fooBarHogeFoo = (\FooBar.hoge).appending(path: \Hoge.foo) \FooBar.hoge.foo == fooBarHogeFoo // true print(fooBar.hoge.foo) // hoge-foo fooBar[keyPath: fooBarHogeFoo] = "foo-bar-hoge-foo" print(fooBar.hoge.foo) // foo-bar-hoge-foo fooBar[keyPath: \.hoge.foo] = "foo" print(fooBar.hoge.foo) // foo let fooBarFugaFoo = (\FooBar.fuga).appending(path: \Fuga.foo) fooBar[keyPath: fooBarFugaFoo] = "foo-bar-fuga-foo" // error: cannot assign through subscript: 'fooBar' is a 'let' constant
KeyPath
の実用例
プロパティでソートする3
public extension Sequence { func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] { sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] } } func sorted<T: Comparable>(by keyPath: KeyPath<Element, T?>) -> [Element] { sorted { guard let l = $0[keyPath: keyPath], let r = $1[keyPath: keyPath] else { return false } return l < r } } func min<T: Comparable>(by keyPath: KeyPath<Element, T>) -> Element? { self.min { $0[keyPath: keyPath] < $1[keyPath: keyPath] } } } struct Person { var id: Int var name: String var age: Int } extension Person: CustomStringConvertible { var description: String { name } } let people: [Person] = [ .init(id: 3, name: "Bob", age: 28), .init(id: 1, name: "Emma", age: 40), .init(id: 4, name: "Amelia", age: 18), .init(id: 2, name: "George", age: 22), ] // Before people.sorted(by: { $0.id < $1.id }) // ["Emma", "George", "Bob", "Amelia"] people.sorted(by: { $0.name < $1.name }) // ["Amelia", "Bob", "Emma", "George"] // After people.sorted(by: \.id) // ["Emma", "George", "Bob", "Amelia"] people.sorted(by: \.name) // ["Amelia", "Bob", "Emma", "George"]
同じAnchor同士へのConstraintを貼る
public extension UIView { func equal<Axis, Anchor: NSLayoutAnchor<Axis>>(_ anchor: KeyPath<UIView, Anchor>, to target: UIView) -> NSLayoutConstraint { self[keyPath: anchor].constraint(equalTo: target[keyPath: anchor]) } func equal<Axis, Anchor: NSLayoutAnchor<Axis>>(_ anchor: KeyPath<UIView, Anchor>, to target: UIView, constant: CGFloat) -> NSLayoutConstraint { self[keyPath: anchor].constraint(equalTo: target[keyPath: anchor], constant: constant) } } // Before NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: target.topAnchor), view.leadingAnchor.constraint(equalTo: target.leadingAnchor), view.trailingAnchor.constraint(equalTo: target.trailingAnchor), view.bottomAnchor.constraint(equalTo: target.bottomAnchor) ]) // After NSLayoutConstraint.activate([ view.equal(\.topAnchor, to: target), view.equal(\.bottomAnchor, to: target), view.equal(\.leadingAnchor, to: target), view.equal(\.trailingAnchor, to: target) ])
更に関数チックに書いた例
https://www.objc.io/blog/2018/10/30/auto-layout-with-key-paths/
Key Path Expressions as Functions
Key Path Expressions as Functions
- Swift5.2で追加(SE-0249)
KeyPath
表現をクロージャを引数とする関数に渡すことができる
- コレクション操作などが簡潔に書けるように
- Xcode12.xならコード補完が効く
例
struct Department { var name: String var member: [Person] } extension Department { var boss: Person { member.min(by: \.id)! } } let departments: [Department] = [ .init(name: "General Affairs Department", member: [ .init(id: 3, name: "Bob", age: 28), .init(id: 1, name: "Emma", age: 40) ]), .init(name: "Development Department", member: [ .init(id: 4, name: "Amelia", age: 18), .init(id: 2, name: "George", age: 22) ]) ]
Before
departments.flatMap { $0.member } .map { $0.name } // ["Bob", "Emma", "Amelia", "George"] departments.map { $0.boss } .map { $0.name } // ["Emma", "George"] departments.map { $0.boss.name }
After
departments.flatMap(\.member) .map(\.name) // ["Bob", "Emma", "Amelia", "George"] departments.flatMap(\.member.name) // Value of type '[Person]' has no member 'name' departments.map(\.boss) .map(\.name) // ["Emma", "George"] departments.map(\.boss.name) // ["Emma", "George"]
Performance4
extension Person { var canDrink: Bool { age >= 20 } }
注意
変数/定数化したKeyPathを渡すことはできない(2020/12/9現在)
let bossKeyPath = \Department.boss let nameKeyPath = \Person.name let bossNameKeyPath = bossKeyPath.appending(path: nameKeyPath) departments.map(bossKeyPath) // Cannot convert value of type 'KeyPath<Department, Person>' to expected argument type '(Department) throws -> T' .map(nameKeyPath) // Cannot convert value of type 'WritableKeyPath<Person, String>' to expected argument type '(T) throws -> T' departments.map(bossNameKeyPath) // Cannot convert value of type 'KeyPath<Department, String>' to expected argument type '(Department) throws -> T'
最後に
KeyPath
によってSwiftコードがより簡潔に書きやすくなった- RxSwift / Combine のストリーム変換にも使えるのでより宣言的に書ける
- Genericsと併用することで処理の汎用化も大幅に進みそう
- Sample in Xcode Playground
- 投稿日:2020-12-07T14:22:34+09:00
[Swift] Delegateが分からない初心者のための最後の受け皿
今回作るもの
黄色のボタンを押すと画面が遷移され黄色くなる実装をdelegateを使って実装&説明します。
きっかけ
Swiftのdelegateの説明をQiitaでたくさん読みましたがいまいち理解できず、なんとなく実装していました。delegateで詰まることが多かったので、アウトプットの機会にしようと思います。
対象
私のように他の記事を読んでも理解に苦しんだ方々に読んで頂きたいです。
Delegateとは
Delegateは委譲という意味ですが、そのクラスではできないことを他のクラスでやってもらうため委譲が必要になるのです。
詳しくコードと共に見ていきましょう。実際のコード
3ファイル作成しました。
1つめははじめの画面のViewController
2つめは黄色画面遷移先のYellowViewController
3つめはViewControllerのCellのクラス(ButtonTableViewCell)まず1つ目のViewControllerです。
次に黄色画面遷移先のYellowViewControllerです
最後に先ほど作った黄色ボタンのCellのクラスです
ここではcellの中のボタンタップ時の処理を行えます。
「ここに処理を書きたい」の部分にlet yellowViewController = YellowViewController() navigationController?.pushViewController(yellowViewController, animated: true)を入れて黄色画面に飛ばしてやりたいですが、CellではNavigationControllerが使用できないので、他の画面で処理をする必要があるのです。そこでdelegateという考え方が必要になります。
Delegateの使い方
1, protocolを宣言
2, delegateを宣言
3, 発火させたい場所にdelegate設置
4, 受け渡し先のクラスで継承
5, 受け渡し先のViewにdelegateを呼び出す
6, 処理を実装する具体的な実装方法
1, まずprotocolを発火させたい(ここでは@IBActionのあるButtonTableViewCell)クラスで宣言します。protocol [delegate名]: class { func [メソッド名] }2, delegateを発火させたい(ここでは@IBActionのあるButtonTableViewCell)クラスで宣言します
3, 実際に発火させたい箇所(ここでは@IBActionの中)でdelegateを設置
4, ViewControllerに戻って、delegate名を継承する
5, ViewControllerでcellを使える場所で、delegateを呼び出す
6, そのままでは実際にしたい処理が書かれていないので、protocolで宣言したmethodをViewController内で書きますおまけ (元々あるDelegate)
ViewControllerにも元々
buttonTableView.delegate = self buttonTableView.dataSource = selfと書かれていますが、これも同様に
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,UITableViewDelegate, UITableViewDataSourceを継承しているからです。
その証拠に、
UITableViewDelegateを右クリックして見てみましょう。
protocolが宣言されている訳です。
ViewControllerでデフォルトでtableViewが使えていたのも理由があったのですね。func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { }最後に
最後まで読んでいただきありがとうございます。理解していただけたしょうか?
delegateはswiftを学習し始めで必ずと言っていいほど、詰まる箇所だと思います。
とても複雑に見えていましたが、根本を理解してみると簡単なものでした。
- 投稿日:2020-12-07T10:54:42+09:00
iosショートカットの凄技でこんなこともできます
みなさんショートカット使ってますか?
https://support.apple.com/ja-jp/guide/shortcuts/welcome/ios
https://qiita.com/tags/shortcutsもともとは Workflow というサードパーティアプリだったのですが、 Apple に買収され、iOS に組み込まれてさらにパワフルなツールになりました。
最近だと、ホーム画面のアプリ起動アイコンをカスタマイズする方法としてニュースに取り上げられたりしています。
さて、そんなショートカット機能、ぱっと見はブロックプログラミングみたいな、最近流行りのノーコードみたいな感じで、「コードを書ける俺には必要ないぜ」と見くびって深堀りしていない諸氏も多いことでしょう。
さて本日は、プログラミング大好きな皆さんを刺激する技をご紹介します。
「WebページでJavaScriptを実行」アクション
まずはこちら。御覧の通り、JavaScriptのコードを実行できそうな気配満々のアクションです。アプリ不要で、標準でついてます。
Safariで表示中のページの Content Scope に JavaScript を埋め込んで実行できる、というもので、一昔前に流行した bookmarklet のように使うことができます。
戻り値を返すことができ、それを次のアクションへつなげるので、サイト上から要素を拾ってリマインダーに登録、といったようなことも簡単にできます。戻り値はcompletion()
の呼び出しで返すことになっているので、非同期処理の結果を取得することもできます。ただし、スクリプトの実行には「Safariウェブページ」というオブジェクトが必要です。
ショートカット起動からあらかじめ指定したURLにアクセスして云々みたいな使い方は出来ず、iOS の safari ブラウザのシェアシートからショートカットを起動することになります。 (筆者は発見できませんでしたが、何か裏技をご存知の方はぜひコメントください!)便利だけど、期待していたのとちょっと違う、と思った方は次節へどうぞ。
「Scriptable」アプリ
https://scriptable.app/
無料アプリです。インストールすると「ショートカット」アプリから Scriptable アクションを呼び出して任意のJavaScriptを実行でき、戻り値をショートカットアプリに返すこともできます。
上のスクショは、 Google Cloud のインスタンスを起動、停止するために私が書いたものです。こんな感じで Web API を自在に呼び出すことができます。 (gist に置いておきました)なぜわざわざこんなことを?という方に念のため説明しますと、ショートカットに「マシン起動」のような名前をつけて保存しておくと、 Apple Watch の竜頭を押しながら「マシン起動」というだけで GCP インスタンスを立ち上げることができるのです。戦隊モノ気分です。便利です。
独自のAPIを持っており、 iOS の標準カレンダーやリマインダーへのアクセスもできるので、ショートカットアプリでぽちぽちするのが大変なループ処理や if 分岐をコードで書けます。
iCloud ドライブにそのままテキストファイルで保存されているので、 Mac や PC で iCloud 接続すれば、使いなれたエディタとキーボードでコード編集することができます。iOS14 の Widget を出力する機能もあり、任意の WebAPI の実行結果をウィジェットとしてホーム画面に表示しておくこともできてしまいます。
「Pushcut」アプリ
こちらも無料アプリです。自作の Web アプリや Slack bot などから手元の iPhone へ通知を送ることができ、それをトリガーにして任意のショートカットを iOS 上で実行できます。
簡単な使い方としては、 Mac/PC で開いているページのURLを iPhone へ送ってすぐに開く、だとか、画像のURLを送るとそのURLの画像をiPhone のクリップボードに入れる、のようなことが実現できます。まとめ
ショートカットアプリの持つ強力な連携パワーをさらに引き出す技をいくつかご紹介しました。関連する連携アプリやテクをご存じの方、ぜひコメントください。
- 投稿日:2020-12-07T05:17:49+09:00
[Swift] SkyWayを使ったWebRTCの映像をFirebase MLに接続して顔認識させる
前書き
とあるハッカソンで使った技術で、実装するにあたり壁にぶつかりました。
おそらく前例がないので、備忘録として残します。技術に関して
1. SkyWayとは
ほとんど画像に記載がありますが、Webでリアルタイムコミュニケーションを実現する標準技術「WebRTC(Web Real Time Communication)」を簡単にアプリに導入できるSDK&APIです。(公式引用)
2. Firebase MLとは
ML Kit は、Google の機械学習の機能を Android アプリや iOS アプリとして提供するモバイル SDK です。(公式引用)
今回はこの部分の中身についての実装は話しません。
あくまでもSkyWayとFirebaseを繋ぐ部分のみになります。
(中身の参考資料「Firebase MLKitを使って動画の顔認識をさせる」)3. 構成図
アプリ間で動画のリアルタイム通信を行いながら、その映像をFirebase MLで判定させるということ行います。
SkyWayが2つ導入されていますが、
- 上部はSkyWayサーバー - 右下アプリ内はSkyWayのSDKになります。
実装
1.前提
Firebase ML
側でデータを分析するためには、動画をCMSampleBufferという型で受け取る必要があります。幸いなことにiOSでは映像から
CMSampleBuffer
型を受け取るために、
「AVCaptureVideoDataOutputSampleBufferDelegate」
というものが用意されています。Example
final class ExampleVideoManager { private var videoOutput: AVCaptureVideoDataOutput! private let queue = DispatchQueue(label: "videoOutput", attributes: .concurrent) init() { self.videoOutput = AVCaptureVideoDataOutput() self.videoOutput.setSampleBufferDelegate(self, queue: queue) // デリゲードのセット } } extension CaptureVideoManager: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // CMSampleBuffer型の値が受け取れる場所。Firebase MLへ接続。 } }2.問題点
通常、カメラや動画を実装するためには
AVKit
をimportとして色々と実装していく必要があります。
(実装説明は割愛するので参考として「Swiftでカメラアプリを作成する」)前提のように自前で
AVCaptureVideoDataOutput
を生成してFirebase ML
接続できれば良いですが、SkyWay
の場合は指定された実装方法で行う必要があるため、わからないようになっています。具体的には、
SKWVideo
という指定されたUI型に、勝手にレンダリングされるようになっていることが問題になります。Example
@IBOutlet weak var streamView: SKWVideo! // レンダリングする画面 func setuo() { /* ~略~ */ let constraints = SKWMediaConstraints() localStream = SKWNavigator.getUserMedia(constraints) localStream?.addVideoRenderer(self.remoteStreamView, track: 0) // レンダリングの指定 /* ~略~ */ }3.解決法
中で隠蔽化されていますが、必ず
AVCaptureVideoDataOutput
は実装されているので、それをSKWVideo
の中から自力で探し当てました。具体的には、レンダリングされる画面のヒエラルキーをちまちまとデバッグして
AVCaptureVideoDataOutput
を持つレイヤー層を探り当てました。探り当てた部分をコードで取り出す場合は、以下のようになります。
// レンダリング画面 @IBOutlet weak var streamView: SKWVideo! // レンダリングしている画面の階層を深掘りし、中から`layer`層を取り出す var videoLayer: AVCaptureVideoPreviewLayer? { return streamView.subviews.first?.layer as? AVCaptureVideoPreviewLayer } // 上記の`layer`層から変換に必要な`AVCaptureVideoDataOutput`を取り出す var output: AVCaptureVideoDataOutput? { return videoLayer?.session?.outputs.first as? AVCaptureVideoDataOutput }上記から
AVCaptureVideoDataOutput
にアクセスできるので、output
にsetSampleBufferDelegate
セットしてあげることで、CMSampleBuffer
型の値を手に入れることができるようになります。これで
Firebase ML
への接続も可能となりました。あとがき
複数のストリームで受け取って加工する、など色々試した結果、この方法が最適なかつ実現可能な方法でした。
少しマニアックなネタだったかもしれませんが、誰かの参考になれば嬉しい所存です。
文献
SkyWay
の実装周りは解説しなかったので、以下を参考にすると良いでしょう。
- SkyWay - アプリやWebサービスに、ビデオ・音声通話をかんたん導入
- iOSのCallKitフレームワークとSkyWay
- SKYWAY SDKを使ったIOS版通話アプリ開発Firebase周りはこちらをご覧ください、
- Firebase MLKitを使って動画の顔認識をさせる
- 投稿日:2020-12-07T05:17:49+09:00
[Swift] SkyWayを使ったWebRTCの映像をFirebase MLに接続する
前書き
とあるハッカソンで使った技術で、実装するにあたり壁にぶつかりました。
おそらく前例がないので、備忘録として残します。技術に関して
1. SkyWayとは
ほとんど画像に記載がありますが、Webでリアルタイムコミュニケーションを実現する標準技術「WebRTC(Web Real Time Communication)」を簡単にアプリに導入できるSDK&APIです。(公式引用)
2. Firebase MLとは
ML Kit は、Google の機械学習の機能を Android アプリや iOS アプリとして提供するモバイル SDK です。(公式引用)
今回は映像部分の顔判定をになっていますが、この部分の中身についての実装は話しません。
あくまでもSkyWayとFirebaseを繋ぐ部分のみになります。
(中身の参考資料「Firebase MLKitを使って動画の顔認識をさせる」)3. 構成図
アプリ間で動画のリアルタイム通信を行いながら、その映像をFirebase MLで顔認識させるということ行います。
SkyWayが2つ導入されていますが、
- 上部はSkyWayサーバー - 右下アプリ内はSkyWayのSDKになります。
実装
1.前提
Firebase ML
側でデータを分析するためには、動画をCMSampleBufferという型で受け取る必要があります。幸いなことにiOSでは映像から
CMSampleBuffer
型を受け取るために、
「AVCaptureVideoDataOutputSampleBufferDelegate」
というものが用意されています。Example
final class ExampleVideoManager { private var videoOutput: AVCaptureVideoDataOutput! private let queue = DispatchQueue(label: "videoOutput", attributes: .concurrent) init() { self.videoOutput = AVCaptureVideoDataOutput() self.videoOutput.setSampleBufferDelegate(self, queue: queue) // デリゲードのセット } } extension CaptureVideoManager: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // CMSampleBuffer型の値が受け取れる場所。Firebase MLへ接続。 } }2.問題点
通常、カメラや動画を実装するためには
AVKit
をimportとして色々と実装していく必要があります。
(実装説明は割愛するので参考として「Swiftでカメラアプリを作成する」)前提のように自前で
AVCaptureVideoDataOutput
を生成してFirebase ML
接続できれば良いですが、SkyWay
の場合は指定された実装方法で行う必要があるため、わからないようになっています。具体的には、
SKWVideo
という指定されたUI型に、勝手にレンダリングされるようになっていることが問題になります。Example
var localStream: SKWMediaStream? @IBOutlet weak var streamView: SKWVideo! // レンダリングする画面 func setup() { /* ~略~ */ let constraints = SKWMediaConstraints() localStream = SKWNavigator.getUserMedia(constraints) localStream?.addVideoRenderer(self.streamView, track: 0) // レンダリングの指定 /* ~略~ */ }3.解決法
中で隠蔽化されていますが、必ず
AVCaptureVideoDataOutput
は実装されているので、それをSKWVideo
の中から自力で探し当てました。具体的には、レンダリングされる画面のヒエラルキーをちまちまとデバッグして
AVCaptureVideoDataOutput
を持つレイヤー層を探り当てました。探り当てた部分をコードで取り出す場合は、以下のようになります。
// レンダリング画面 @IBOutlet weak var streamView: SKWVideo! // レンダリングしている画面の階層を深掘りし、中から`layer`層を取り出す var videoLayer: AVCaptureVideoPreviewLayer? { return streamView.subviews.first?.layer as? AVCaptureVideoPreviewLayer } // 上記の`layer`層から変換に必要な`AVCaptureVideoDataOutput`を取り出す var output: AVCaptureVideoDataOutput? { return videoLayer?.session?.outputs.first as? AVCaptureVideoDataOutput }上記から
AVCaptureVideoDataOutput
にアクセスできるので、output
にsetSampleBufferDelegate
セットしてあげることで、CMSampleBuffer
型の値を手に入れることができるようになります。output?.setSampleBufferDelegate(self, queue: /* Queue */)これで
Firebase ML
への接続も可能となりました。あとがき
複数のストリームで受け取って加工する、など色々試した結果、この方法が最適なかつ実現可能な方法でした。
少しマニアックなネタだったかもしれませんが、誰かの参考になれば嬉しい所存です。
文献
SkyWay
の実装周りは解説しなかったので、以下を参考にすると良いでしょう。
- SkyWay - アプリやWebサービスに、ビデオ・音声通話をかんたん導入
- iOSのCallKitフレームワークとSkyWay
- SKYWAY SDKを使ったIOS版通話アプリ開発Firebase周りはこちらをご覧ください
- Firebase MLKitを使って動画の顔認識をさせる