- 投稿日:2020-11-25T23:05:06+09:00
CoreMLモデルのサイズを小さくする量子化でモデルの精度はどう変わるのか
モバイルに載せるなら、モデルのサイズは小さいほうがいいと思う。
量子化とは、重みパラメータを、例えばFloat_32からFloat_16にして早めの桁で切り上げるようにすること。実際のモデルを量子化して、どれくらいサイズが小さくなるのか、精度はどう変わるのか、をやってみた。
deeplabv3のxeptionバックボーンバージョンでセマンティックセグメンテーションを比較(背景ぼかし)。
xeptionバックボーンのdeeplabv3のmlmodelのサイズは165.1MB。
MobileNetバックボーンだとModelが8.6MBなので、xeptionの方が精度はずっといいとはいえ、さすがにもうちょっと小さくしたいところ。CoreMLModelの量子化の方法
from coremltools.models.neural_network import quantization_utils # 可能な値はnbits = 16, 8, 7, 6, ...., 1 nbits = 16 quantized_model = quantization_utils.quantize_weights(model, nbits) coremltools.utils.save_spec(quantized_model, 'quantizedDeepLab.mlmodel') # Tips: 量子化したmlmodelは、なぜか.save(Path)メソッドが使えないので、coremltools.utils.save_specで保存します。結果
モデル サイズ(MB) 速度(s) 使用メモリ(MB) Float32 165.1 315 477 Float16 82.6 2.72 477 Float8 41.8 2.77 477 モデルのサイズは見事に半分半分になった。
しかし、実行速度と使用メモリは変わらなかった。さて、セグメンテーションの精度はというと・・・
左から【オリジナル】 【Float32】 【Float16】 【Float8】
165MB 82MB 41MB
おわかりいただけたであろうか?
32と16ではほぼ全くセグメント精度は変わらない。
「8だとさすがに雑になるでしょ」と思いきや、あにはからんや、これもほとんど変わらない。
よく見ると右下の花をぼかしが少し侵食しているが、これくらいなら十分32と同じように使える。バンドルするモデルのサイズを小さくしたければ、量子化は有効な選択肢になると思う。
他のタスクでどう違ってくるのかは試してないけど。
*今回はLinearというデフォルトの量子化方法を使った。Coremltoolsの引数で別の種類も選択できるらしい。?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-11-25T22:37:25+09:00
2020-11-25時点でMongoDB RealmをiOSで使う場合の注意点
経緯
いま、iOS/Android(おそらくWebも)対応予定のアプリをprivateで書いています。
クロスプラットフォームでデータを同期するようなアプリとなる予定なので、記事タイトルにあるMongoDB Realmを利用することにしました。というような経緯で、MongoDB RealmをiOSアプリで導入しようとしたら、2020-11-25時点でいくつか注意する点があったので、メモがてら残しておきます。
Be sure to select MongoDB version 4.4
見出しの通りなのですが、MongoDB Realmでは、MongoDB version 4.4(2020-11-25時点)である必要があります。
MongoDBが2019年にRealmを買収したため、2020年頃にRealmとMongoDBを統合したサービスに刷新され、以下の構成でworkするようになっています。
https://www.mongodb.com/realm より稲妻アイコンがRealmで葉っぱアイコンがMongoDBです。
MongoDB Realm利用者は、AWS / Google Cloud / Azure上にMongoDB用のクラスタを構築する必要があります。
このあたりは、MongoDB RealmがWeb UI(Realm UIと呼ばれています)を提供してくれているので、それ経由で簡単に構築できます。なのでRealm UIに従いサクサク進めていくと、見落としがちなのですが(私がそうでした )、MongoDBのバージョンは4.4である必要があります。
default versionが4.2になっていたり、いくつかのregionは4.2までしか利用できない、という状況なので、利用する際には気をつけてくださいChoose your preferred provider and region, tier, and additional settings. As you build your cluster, Atlas displays the associated costs at the bottom of the page.
Be sure to select MongoDB version 4.4 for your cluster if you wish to use sync!https://docs.mongodb.com/realm/get-started/create-realm-app/#b-create-an-atlas-cluster
(ドキュメントにはちゃんと書いてあります。sync機能を使う場合は
と記載されているのですが、MongoDB Realmを採用するにも関わらず、sync機能を使わないケースはあまりない気がするので、実質必須と言ってしまって良い気がします。)SwiftPM経由でRealmを導入できない?
こちらのドキュメントを読みつつiOSアプリにRealmの導入を進めていると、
こちらに記載されている通り、RealmはSwiftPM対応されているとのことなので、SwiftPM経由でRealmを依存に追加しました。追加後もドキュメントに従い、import文を追加、
import RealmSwiftさらに下記を追加したのですが、
App
は見つからない、というようなエラーが出てbuildできませんでしたlet app = App(id: YOUR_REALM_APP_ID) // Replace YOUR_REALM_APP_ID with your Realm app ID「import文が足りない?SwiftPMにおける依存の追加に誤りがある?」 などと自身の見落としを疑いドキュメントを改めて読んでみたのですが、特に問題はなさそうでした。
そもそもApp.swift
が存在していないのでは、ということで、realm-cocoaのPackage.swiftを見てみると、App.swift
がexclude
されており、こちらが原因のように見えました
試しにCocoaPods経由で追加すると無事buildが通るようになりました。ドキュメント上では、SwiftPM経由で追加した場合には
App.swift
は無い(利用できない)、というようなことは何も記載されていないにも関わらずexclude
されている原因が気になったのでPRを追ってみました。Package.swiftのexclude対象として
App.swift
が追加されたのは、こちらのPRですが、このPRではRealmApp -> App
というrenameをしただけのようで、rename前のRealmApp.swift
がexclude対象になったのは、こちらのPRのようでした。
PRコメントとしてはDescription TK.
と書かれているのみだったので、
CHANGELOG.mdを読んでみると、このPRでMongoDB統合をサポート開始したようでした。Add support for next generation sync. Support for syncing to MongoDB instead of Realm Object Server. Applications must be created at realm.mongodb.com
ただ、知りたかった
RealmApp.swift
がPackage.swiftにてexcludeされるようになった理由については特に記載はなさそうでした。commit logもこれだけで、理由はクリアにならなかったので、issueで聞いてみるとことにしました。issueを立てようとすると、下記のISSUE_TEMPLATEが存在し、
!!! MANDATORY TO FILL OUT !!! <!--- **Questions**: If you have questions about HOW TO USE Realm, ask on [StackOverflow](http://stackoverflow.com/questions/ask?tags=realm), or in our [Swift Forum](https://forums.realm.io/c/swift) or [Obj-C Forum](https://forums.realm.io/c/objc). **Feature Request**: Just fill in the first two sections below. **Bugs**: To help you as fast as possible with an issue please describe your issue and the steps you have taken to reproduce it in as many details as possible. --> ## Goals <!--- What do you want to achieve? --> ## Expected Results <!--- What did you expect to happen? --> ## Actual Results <!--- What happened instead? e.g. the stack trace of a crash --> ## Steps for others to Reproduce <!--- What are steps OTHERS can follow to reproduce this issue? --> ## Code Sample <!--- Provide a code sample or test case that highlights the issue. If relevant, include your model definitions. For larger code samples, links to external gists/repositories are preferred. Alternatively share confidentially via mail to help@realm.io. Full Xcode projects that we can compile ourselves are ideal! --> ## Version of Realm and Tooling <!--- [In the CONTRIBUTING guidelines](https://git.io/vgxJO), you will find a script, which will help determining some of these versions. --> Realm framework version: ? Realm Object Server version: ? Xcode version: ? iOS/OSX version: ? Dependency manager + version: ?今回の件だと、テンプレート中に記載があるSwift Forumのほうが適していそう、かつ、ぱっと見、同じような質問はなさそうだったので、そちらで質問してみることにしました。
で、いま
Why is App.swift exclude from Package.swift?
というタイトルの投稿をしてみたのですが、どうやら承認が必要なようで、まだ表示されていません。We've received your new post but it needs to be approved by a moderator before it will appear. Please be patient.
You have 1 post pending.
なにか返信など来たら追記したいと思います。
2020-11-25 22:43追記
当記事を公開していたら、早速返信してもらうことができました。(めちゃくちゃ早い)
https://developer.mongodb.com/community/forums/t/why-is-app-swift-exclude-from-package-swift/12160ということで、Realmのsync機能はSPMをサポートしていないようです
素直にCocoapods
orCarthage
経由で利用しましょう。
(ドキュメントに書いておいてくれても良さそうな気もしますが、sync機能はβだったりするので、整備中なのかもしれません)さいごに
なんというか、まとまりのない文章になってしまいましたが、言いたかったのは下記2点です。
- Be sure to select MongoDB version 4.4
- SwiftPM経由でRealmを導入できない?
以上です!
- 投稿日:2020-11-25T21:32:04+09:00
ProvisioningProfileの有効期限をチェックする
InHouse配布のiOSアプリは、ProvisioningProfileの有効期限が切れるとアプリが起動しなくなる。
アプリが起動しない状況を未然に防ぐため、
ProvisioningProfileの有効期限をチェックする機構を検討した。前提条件
- ProvisioningProfileはFastlane Matchで管理されていること
仕組み
主にFastlaneを利用する。
1. Fastlane MatchにてProvisioningProfileを取得する
2. OpenSSLにてProvisioningProfileをデコードし、Plistファイルを得る
3. Plistファイルから有効期限を取得するサンプル
Fastlaneにて有効期限をチェックするサンプル
lane :check_provisioningprofile do # Fastlane MatchにてProvisioningProfileを取得する match( readonly: true, git_url: #{matchリポジトリのURL}, app_identifier: #{Bundle ID}, output_path: "/tmp/check_provisioningprofile" ) Dir.glob("/tmp/check_provisioningprofile/*.mobileprovision") do |mobileprovision| # OpenSSLにてProvisioningProfileをデコードし、Plistファイルを得る sh("openssl smime -inform der -verify -noverify -in #{mobileprovision} > /tmp/decoded.plist") # PlistファイルからProvisioningProfileの名称を取得する profile_name = get_info_plist_value(path: "/tmp/decoded.plist", key: "Name") # Plistファイルから有効期限を取得する expiration_date_value = get_info_plist_value(path: "/tmp/decoded.plist", key: "ExpirationDate") expiration_date = DateTime.parse(expiration_date_value.to_s) # 有効期限が30日後に迫っていたらエラーとする warning_threshold = DateTime.now + 30 if warning_threshold > expiration_date UI.user_error!("It will expire soon.\n---> #{profile_name}") end end end
- 投稿日:2020-11-25T20:10:08+09:00
[Swift5]containsを使って特定の文字列を検索する方法
containsメソッドとは
文字列の中にある特定の文字列を検索するメソッドです。
contains("検索したい文字列")実用例
var firstText = "2020年日本一はソフトバンク" var secondText = "2021年は阪神タイガースが優勝" var thirdText = "baseball" if firstText.contains("ソフトバンク") { //true print("含まれているので呼ばれる") } if secondText.contains("巨人") { //false print("含まれていないので呼ばれない") } //以下、lowercased()で大文字小文字を無視して検索 if thirdText.contains("BaseBall".lowercased()) { //true print("含まれているので呼ばれる") } if thirdText.contains("Soccer".lowercased()) { //false print("含まれていないので呼ばれない") }
- 投稿日:2020-11-25T19:16:57+09:00
iOSアプリのサイズを軽量化したときの話
先週、iOSアプリのプロジェクトを見直し、
35.8 MB
から17.3 MB
まで 軽量化しました。
その方法とは「参照しなくなったファイルは、すぐプロジェクトから消しましょう」というだけのお話です。iOSアプリのサイズを最適化する
Appleは、iOS11の公開にあわせて、モバイルデータ通信時にApp Storeから
アプリをダウンロードするときの容量制限を 100 MB から 150 MB に拡大しました。https://developer.apple.com/news/?id=09192017b
さらに、iPhone端末自体の大容量化や、LTEや5G回線により
高速通信が可能になったことで、大きなサイズのアプリも不自由なく利用できるようになりました。
とは言え、アプリのサイズはもちろん今でも小さいほうがいいです!私自身も、当初からアプリのサイズは小さくするように開発していました。
写真はその都度ダウンロードするため、アプリの中には5枚くらいしか入ってない...はずでした。私がはじめに着目したのは、データベースファイルです。
SQLiteファイルを最適化する
私の使用しているSQLiteファイルには、テキストと写真のURLなどが保存されています。
調べてみると、SQLiteはテーブルからデータを削除したらすぐSQLiteファイルのサイズが小さくなる訳では無いようです。
ファイルの中で利用されていた領域はすぐに削除されるのではなく次にデータが追加された時に再利用されます。https://www.dbonline.jp/sqlite/manage/index1.html
私のアプリではデータの削除はしていませんので、これは関係ありませんでした。
アプリサイズの内訳をチェックする
今回、ビンゴだった解決策はこちらです。
アプリサイズの内訳を確認してみたところ、不要ファイルがたくさん含まれていました。
(まさか、プロジェクトに含まれてたら使ってなくても一緒にコンパイルされてしまうなんて、、)その方法は、まず以下の手順でOrganizerを開きます。
関連記事)https://qiita.com/kenkubomi/items/5e8b4aec981dfc9812c2
その後、確認したいアーカイブを選択後、
右クリックして「Show In Finder」を選択。ファインダーで開きます。次に、開いたディレクトリにある
.xcarchive
ファイルを右クリックして、
「パッケージの内容を表示」を選択します。さらに、
Products > Applications
の中にある、
.app
ファイルを右クリックして、先ほどと同様に「パッケージの内容を表示」を選択します。この中のファイルをサイズの大きい順に並べたら一目瞭然ですね!
あまりにサイズの大きい画像などは、縮小や圧縮をしてみても良いかもしれません。見事、ダイエットに成功!
アプリサイズが大きかった原因が分かってスッキリです!
- 投稿日:2020-11-25T14:39:01+09:00
Carview に導入したい iOS の最新技術
はじめに
Carview Advent Calender が始まりました! 1 日目の記事は、 iOS エンジニアの @zwtin が担当します。普段は iOS や Flutter1 の記事を書いています。今回はアプリエンジニア以外の方が見てくださる機会だと思うので嬉しいです!これを機会に、初日ですしそこまで踏み込んだコードの話は後日の方々におまかせし(決して手抜きではありません)、私は「 Carview に導入したい iOS の最新技術」と題して、 iOS では今こういう技術が流行ってます!というのをお伝えできればと思います。
導入したい技術
以下、第 1 位 〜 第 3 位まで発表します。
第 1 位: Core ML & Create ML
流行りの「機械学習・AI」の iOS 版です。 iOS 11 の発表とともに追加され、 iOS 用の機械学習として広まりました。 Carview では、写真を撮ると自動車のナンバープレート部分が自動で隠れるカメラをリリースしています(気になる方は、「みんカラ」で検索!)。ここをCore ML & Create ML に置き換えたいと思っています。というのも、Core ML は iOS 用の機械学習として謳っているだけあり、他の機械学習では CPU や GPU を使って処理するところをニューラルエンジン2 というものを使い、より高速に処理することができます。
下記に、私が個人的に実装してみたサンプルアプリの動画を貼ります。 PC で自動車の画像を表示しそれを iPhone のカメラで撮影している最中の動画ですが、常時ナンバープレートを隠せていることがわかります。
個人的には、この検知したナンバープレートエリアをさらに Core ML にかけることで、愛車登録時にナンバープレート情報を自動入力されるようにしたいと考えています。実際に、ナンバープレートの番号だけであれば Apple が文字を判定する機能3 を提供しているのであまり苦労せず実装することができそうです。ただこの機能は日本語に対応していないため、陸運支局の部分やひらがなの部分は独自で作る必要があり、さらにこの陸運支局は時間が経つごとに増えていく部分であるので、実装難易度がかなり高いです。
第 2 位: Swift UI
1 位がなかなか華々しい技術だったので期待された方には申し訳ありませんが、2 位は目玉らしい機能が実装できるというものではありません。 Swift UI とは iOS 13 とともに発表された、新しい画面の実装方法です。 iOS は今まで画面を作るときに、
- 画面上に部品を追加し、
- その部品をコード上に紐付けて、
- コード上で機能を実装する
という実装を行う必要があります。これが Swift UI では 1 と 2 を行う必要がなく、3 のようなコードのみで完結する書き方です。ですので、実装時間を短縮することができコードレビューもしやすい、という特徴があります( 1 で作ってる画面上のコードレビューは本当に大変。。。)。
また個人的な話ですが、将来的には Apple は Swift UI しかサポートしなくなるのでは?という不安があります4。というのも、 iOS 14 から追加されている機能である Widget ですが、これは Swift UI でしか作れません。他にも Swift UI の発表と共に Objective-C からは使えないフレームワークが発表されたり5、いつ Apple が「古い作りのアプリは承認しないよ」といってもおかしくありません(といってもかなり先の話だと思いますが)。いつそう言われてもいいように、移行準備をしっかりしておきたいところですね。
第 3 位: ARKit
最新技術か?という声が聞こえなくもないですが、 iPhone 12 Pro が発売され LiDAR6 により精度が上がった ARKit を第 3 位としました。 iPhone をお持ちの方は、 Safari で「コウテイペンギン」や「ジャイアントパンダ」と検索すると、検索結果に「 3D 表示 」というボタンが出てきていることがわかると思います。
それを押すとカメラが起動し、カメラが平面を検知すると「コウテイペンギン」や「ジャイアントパンダ」が出現するようになります。このように、現実世界をカメラで撮影しながら、その世界に何か物体を置く技術を AR7 と呼びます。これを iOS で実現するための機能が、 ARKit と呼ばれるものになります。自分が気になる車種がカメラを向けたら出現し、それと一緒に撮影ができたら感動しそうですよね!
おわりに
というわけで、個人的に考えている「 Carview に導入したい iOS の最新技術」3 選をご紹介しました。iOS や iPhone について、今後の技術の進化を予感させられたり、わくわくできるような記事になっていましたら幸いです。
明日は @nbapps_dev さんの投稿で、全自動アプリ審査提出についてだそうですね!アプリエンジニアであれば審査提出作業の煩わしさを感じる機会は多いと思うので、どんな記事なのかとても楽しみです!
以上で、 1 日目の記事は終了しようと思います。ありがとうございました!!
iOS と Android のアプリを一つのコードで実装できる、クロスプラットフォームフレームワーク。 ↩
Apple が開発した、機械学習関連の処理に特化した SoC の一部。 iPhone X 以降に搭載されている。 ↩
正確には機械学習モデルを提供しています。 MNIST というモデルを使えば可能だと考えています。 ↩
過激派による暴論の一つだと考えてください。サポートしなくなる、という根拠はないです。 ↩
Combine という非同期処理を扱うためのフレームワークが発表されましたが、これは Objective-C からは使用できません。 ↩
Light Detection and Ranging の略。iPhone では赤外線を発し反射して返ってくる時間を計測することで、部屋の奥行きなどを高精度で計測することができる。iPhone 12 Pro と iPhone 12 Pro Max に搭載されている。 ↩
Augmented Reality(拡張現実)の略。他にも Virtual Reality(仮想現実)や Mixed Reality(複合現実)などがある。 ↩
- 投稿日:2020-11-25T13:13:14+09:00
FlutterのImagePickerでapp storeにリジェクトされた件。
ストアから以下のリジェクトをくらいました。
Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage
We noticed that your app requests the user’s consent to access the camera and photos, but doesn’t sufficiently explain the use of the camera and photos in the purpose string.
To help users make informed decisions about how their data is used, all permission request alerts need to specify how your app will use the requested information.
Next Steps
Please revise the relevant purpose string in your app’s Info.plist file to specify why your app needs access to the user's camera and photos. Make sure the purpose string includes an example of how the user's data will be used.
You can modify your app's Info.plist file using the property list editor in Xcode.
原因と対策
下記ページを参考にImagePickerを導入してたのですが、
https://makulogsleep.com/flutter-image-store下の部分をそのままInfo.plistに貼り付けてたら、「簡単すぎてわからんわ!」とお叱りを受けました。
タグの中身を、具体的に「画像データをどう使うのか」と書き直したらOKでした。
ちなみに、どこで使われるのか。
- 投稿日:2020-11-25T06:03:18+09:00
公式ドキュメントのAuto Layout記事にあるStackViewの例を作成してみた!
はじめに
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH11-SW12
この公式ドキュメントの例を元に、画像のようなレイアウト作成しました。
Stack View
、Content Hugging Priority
、Content Compression Resistance Priority
の使い方や機能についてまとめてみようと思います。
Stack Viewの使い方
いくつかやり方があるが、公式ドキュメントに載っていたやり方が分かりやすかったのでそのやり方を説明します。
Label
とtextField
をStack View
でまとめるには、
二つを選択して
[Editor]>[Embed in]>[Stack View]を選択!
水平方向のStack View
ができました!
垂直に並べて同じ作業すれば垂直方向のStack View
ができます。
この作業を繰り返してレイアウトを作成していきます。完成したもの
Content Hugging PriorityとContent Compression Resistance Priority
この完成したレイアウトの
Content Hugging Priority
とContent Compression Resistance Priority
は以下の通りです。
Content Hugging Priority
Stack View内でパーツを配置して、Stack Viewの方が水平または垂直方向に対して大きく、余白が発生する場合の余白を埋めるためのものです。このHuggingの値が小さい方が空白を埋めるために広がるイメージです。
以下の画像はLabelとtextFieldの間が空いていてどちらかに余白を埋めさせるとします。
textFieldのContent Hugging Priority
をLabelより低くして、Stack View
の左右の幅を決めると、画像のようにtextFieldが余白を埋めてくれます。
LabelのHorizontalのContent Hugging Priority
の値は251。(画面一番右の下の方)
textFieldのContent Hugging Priority
の値は250
Content Compression Resistance Priority
こちらはパーツがStack Viewからはみ出るときに、優先して表示する方の値を大きくします。
labelのContent Compression Resistance Priority
を751、textFieldを750にすると、labelがまず優先的に表示されてからtextFieldが表示されます。
逆に、labelを750、textFieldを751にするとtextFieldが優先的に表示され、今回のようにtextFieldの内容が長ければlabelを隠してしまいます。
公式ドキュメントで作成したレイアウトで、labelのResistanceが750でtextFieldが749になっている理由もこれと同じでしょう。
最後に
この辺りの知識は使いこなすことができれば、レイアウトの作成がやりやすくなり思うように動かせると思うので、しっかり使いこなしたいですね。
参考サイト
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH11-SW12
https://blog.officekoma.co.jp/2019/02/stackview-content-hugging.html
- 投稿日:2020-11-25T04:08:02+09:00
Swift Classのまとめ
class
swiftのクラスに関してまとめたのこちらに記事にいたします。
Class 参照型のデータ構造
- 参照型、継承できる
- 構造体と列挙体と違うは プロパティやメソッドの文法、初期化のフローなどが違う
- 上記は継承を考慮するため
- CocoaフレームワークのほとんどはClassで定義されている
class SomeClass { let id: Int let name: String init(id: Int, name: String) { self.id = id self.name = name } func printName() { print(name) } } let instane = SomeClass(id: 1, name: "name") instane.printName()継承
- 型の構成要素の引き継ぎ
- 継承とは、新たなクラスを定義する時、他のクラスのプロパティ、メソッド、イニシャライザなどの型を再利用する仕組み
- 継承先のクラスでは、継承元のクラスと共通する動作を改めて定義する必要がなく、継承元のクラスとの差分のみを定義知れば済む
- 継承元のクラスのサブクラス、継承元のクラスはクラスのスーパークラスといいます
- Swiftでは複数のクラスから継承する多重継承は禁止されている
class ClassName: 継承 { クラスの定義 } class User { let id: Int var massage: String { return "Hello" } init(id: Int) { self.id = id } func printProfile() { print("id: \(id)") print("massage: \(massage)") } } // Userを継承したクラス class RegisteredUser: User { let name: String init(id: Int, name: String) { self.name = name super.init(id: id) } } let registeredUser = RegisteredUser(id: 1, name: "tanaka takuya") let id = registeredUser.id let massage = registeredUser.massage // Userを継承しているのでmassageが呼べる registeredUser.printProfile()オーバーライド
- 型の構成要素の再定義
- スーパークラスで定義されているプロパティやメソッドなどの定義は、サブクラスで再定義することもできる
- オーバーライド可能なプロパティはインスタンスプロパティと後述するクラスプロパティのみで、すタティックプロパティはオーバーライドできない
- オーバーライドを行うには、overrideキーワードを使用してスーパークラスで定義されている要素を再定義します。
class ClassMame: superClass { override func methodName(引数) -> 戻り値 { メソッド呼び出し時に実行される文 } } override var プロパティ名:型名 { get { return文によって値を返す処理 superキーワードでスーパークラスの実装に利用できる } set { 値を更新する処理 superキーワードでスーパークラスの実装を利用できる } }
- オーバーライドしたプロパティやメソッドの中でsuperキーワードを使用することで、スーパークラスの実装を呼び出すこともできる
- User1で定義されているmassageとprintProfileをRegisteredUser1でオーバーライドしている
- printProfileではsuper.printProfileによって、スパークラスの実装を読んでいる
class User1 { let id: Int var massage: String { return "Hello" } init(id: Int) { self.id = id } func printProfile() { print("id: \(id)") print("massage: \(massage)") } } class RegisteredUser1: User1 { let name: String override var massage: String { return "Hello, my name is \(name)" } init(id: Int, name: String) { self.name = name super.init(id: id) } override func printProfile() { super.printProfile() print("name: \(name)") } } let user1 = User1(id: 1) user1.printProfile() print("--") let registeredUser1 = RegisteredUser1(id: 2, name: "tanaka takuya") registeredUser1.printProfile()
- Userクラスのprofileではidとmessageのみを出力していましたが、ResisteredUser1ではこれに加えてnameの値も出力している
final 継承とオーバーライドの禁止
- オーバーライド可能な要素の前にfinalをつけることによって、その要素がオーバーライドされることを禁止できる
class SuperClass { func overrideMethod() {} final func finalMethod() {} } class SubClass: SuperClass { override func overrideMethod() {} // コンパイルerror // Instance method overrides a 'final' instance method // override func finalMethod() {} } // classにfinalをつけることによって、そのクラスを定義することを禁止する class AAA {} class BBB: AAA {} final class CCC {} // コンパイルerror // nheritance from a final class 'CCC' // class DDD: CCC {}クラスに紐づく要素
- クラスのインスタンスではなく、クラス自身に紐ずく要素として、クラスプロパティとクラスメソッドがある
- クラスプロパティとすタティックプロパティと、クラスメソッドはスタティックメソッドと、それぞれ似た性質を持っている
両者の違い
- スタティックプロパティとスタティックメソッドはオーバーライドできない
- クラスプロパティとクラスメソッドはオーバーライドできる
クラスプロパティ
- クラス自身に紐ずくプロパティ
- クラスプロパティはクラスのインスタンスではなく、クラス自身に紐ずくプロパティで、インスタンスに依存しない値を扱う場合に利用する
- クラスプロパティを定義するには、プロパティ宣言の先頭にclassをつける
- アクセス方法は型名に . とクラスプロパティ名をつけて、型名.クラスプロパティのように書く
class A { class var className: String { return "A" } } class B: A { override class var className: String { return "B" } } A.className // "A" B.className // "B"クラスメソッド
- クラスに紐ずくメソッド
- クラスメソッドはクラスのインスタンスではなく、クラス自身に紐ずくメソッドで、インスタンスに依存しない処理を実装する際に利用します
- 使い方は上と同じ
class A1 { class func inheritanceHierarchy() -> String { return "A" } } class B1: A1 { override class func inheritanceHierarchy() -> String { return super.inheritanceHierarchy() + "<-B" } } class C1: B1 { override class func inheritanceHierarchy() -> String { return super.inheritanceHierarchy() + "<-C" } } A1.inheritanceHierarchy() // A B1.inheritanceHierarchy() // A<-B C1.inheritanceHierarchy() // A<-B<-Cイニシャライザの種類と初期化のプロセス
- イニシャライザの役割は型のインスタンス化の完了までに全てのプロパティを初期化し、型の生合成を保つことである
- クラスには継承関係があるため、様々な階層で定義されたプロパティが初期化されることを保証する必要がある
- 上記を保証するためにクラスには二段階初期化の仕組みが導入されている
- 二段階初期化を実現するためにクラスのイニシャライザは指定イニシャライザとコンビニエンスイニシャライザの二種類がある
指定イニシャライザ 主となるイニシャライザ
- クラスの主となるイニシャライザ
- このイニシャライザの中で全てのストアドプロパティが初期化される必要がある
class Mail { let form: String let to: String let title: String // 指定イニシャライザ init(form: String, to: String, title: String) { self.form = form self.to = to self.title = title } }コンビニエンスイニシャライザ
- 指定イニシャライザをラップするイニシャライザ
- 指定イニシャライザを中継するイニシャライザで、内部で引数を組み立てて指定イニシャライザを呼び出す必要がある
class Mail1 { let from: String let to: String let title: String init(from: String, to: String, title: String) { self.from = from self.to = to self.title = title } convenience init(from: String, to: String) { self.init(from: from, to: to, title: "Hello, \(from)") } } let mail = Mail(form: "aaa", to: "bbb", title: "ccc") let mail1 = Mail1(from: "aaa", to: "bbb") print(mail.form, mail.to, mail.title) print(mail1.from, mail1.to, mail1.title)二段階初期化
- 型の生合成を保った初期化を実現するため、クラスのイニシャライザには3つのルールがある
- 指定イニシャライザは、スーパークラスの指定イニシャライザを呼ぶ
- コンビニエンスイニシャライザは同一クラスのイニシャライザを呼ぶ
- コンビニエンスイニシャライザは、最終的に指定イニシャライザを呼ぶ
上記のルールを満たしている場合
- 継承関係にある全てのクラスの指定イニシャライザがかなさず実行され、各クラスで定義されたプロパティが全て初期化されることが保証される
- 一つでも満たせないルールがある場合、型の生合成を保てない可能性があるのでコンパイルエラーになる
- またスーパークラスとサブクラスのプロパティの初期化順序を守ため、指定イニシャライザによるクラスの初期化は二段回に分けて行われる
- クラス内で新たに定義された全てのストアドプロパティを初期化し、スーパークラスの指定イニシャライザを実行する
- スーパークラスでも同様の初期化を行い、大元のクラスまで遡る
- ストアドプロパティ以外の初期化を行う
class User2 { let id: Int init(id: Int) { self.id = id } func printProfile() { print("id: \(id)") } } class RegisteredUser2: User2 { let name: String init(id: Int, name: String) { // 第一段階 self.name = name super.init(id: id) // 第二段階 super.printProfile() } }デフォルトイニシャライザ
- プロパティの初期化が不要な場合に定義されるイニシャライザ
- プロパティが存在しない場合や、全てのプロパティが初期値を持っている場合、指定イニシャライザ内で初期化する必要があるプロパティはない
- 暗黙的にデフォルトの指定イニシャライザが定義される
class User3 { let id = 0 let name = "kenta" // 以下と同等のイニシャライザが自動的に定義されている // init() {} } let user3 = User3() // 一つでも指定イニシャライザ内で初期化が必要なプロパティが存在する場合、デフォルトイニシャライザがなくなり、指定イニシャライザを定義する必要があるます! class User4 { let id: Int let name: String // これがないとコンパイルエラー init(id: Int, name: String) { self.id = id self.name = name } } let user4 = User4(id: 0, name: "Taro")デイニシャライザ インスタンスの終了処理
- ARCによってインスタンスが破棄されるタイムングでは、クラスのデイニシャライザが実行されます、デイ二シャライザの逆で、クリーンアップなどの終了処理を行うもの - デイニシャライザは、deinitキーワードを使用して、クラス内に次のように定義する
class ClassName { // deinit { // クリーンアップなどの処理 // } //} // デイニシャライザは継承関係の下位クラスから自動的に実行されるため、 // スーパークラスのデイ二シャライザを呼び出す必要はありません // 値の比較と参照の比較 // 参照型の比較は、参照先の値同士の比較と、参照先の比較の2つに分けられる // 参照先の値同士の比較はこれまでに登場してきたものと同様に==演算子で行い、参照自体の比較は===演算子で行う class SomeClassA: Equatable { static func ==(lhs: SomeClassA, rhs: SomeClassA) -> Bool { return true } } let a = SomeClassA() let b = SomeClassA() let c = a // 同じ a == b // true // 参照は異なる a === b // false // 参照先は同じ a === c // trueまとめ
個人的にまとめたので、記事にしてみました。
完全に個人メモをそのまま書いたので、見辛いかも知れませんがご了承ください。
- 投稿日:2020-11-25T03:52:35+09:00
swift 構造体まとめ
構造体
Swiftの構造体に関してまとめたので、記事として残しておきます。
構造体とは?
- 値型
- ストアドプロパティ の組み合わせによって、一つの値を表す
- 標準ライブラリで提供されている多くは構造体である
- Bool String Int Array Dictionary
- オプショナルは列挙型 タプルは型の組み合わせ Anyはプロトコル
定義
struct 構造体名 { 構造体の定義 } struct Article { let id: Int let title: String let body: String init(id: Int, title: String, body: String) { self.id = id self.title = title self.body = body } func printBody() { print(body) } } let article = Article(id: 1, title: "title", body: "body") article.printBody() // body
- ストアドプロパティ の変更による値の変更
- 構造体はストアドプロパティの組み合わせで一つの値を表す値型
- 構造体のストアドプロパティを変更することは、構造体を別の値に変更することであり、構造体が入っている変数や、定数への再代入を必要とします
値型の変更に関する仕様は構造体のストアドプロパティ の変更にも適用される
定数のストアドプロパティは変更できない
構造体のストアドプロパティの変更は再代入を必要とするため、定数に代入された構造体のストアドプロパティは変更できない
struct SomeStruct { var id: Int init(id: Int) { self.id = id } } var variable = SomeStruct(id: 1) variable.id = 2 let constant = SomeStruct(id: 1) constant.id = 2 // error
- メソッド内のストアドプロパティの変更にはmutatingが必要
- 構造体のストアドプロパティの変更は再代入が必要なので、ストアドプロパティを含むメソッドにはmutatingが必要
struct SomeStruct2 { var id: Int init(id: Int) { self.id = id } // mutating をつけないとerror // Cannot assign to property: 'self' is immutable // 自動でつけてくれるようになる mutating func someMethod() { id = 4 } } var some2 = SomeStruct2(id: 1) some2.someMethod() some2.id // 4 変更されている
- メンバーワイズイニシャライザ デフォルトで用意されているイニシャライザ
- 型のインスタンスは初期化後に全てのプロパティ が初期化される必要がある
- 独自にイニシャライザを定義して初期化の処理を行うこともできます、
- 構造体では自動的に定義される メンバーワイズイニシャライザというイニシャライザがある
- 型が持っている各ストアドプロパティと同名の引数をとるイニシャライザである
struct Article2 { var id: Int var title: String var boby: String // 以下と同等のイニシャライザが自動的に定義される // init(id: Int, title: String, body: String) { // self.id = id // self.title = title // self.boby = body // } } let article2 = Article2(id: 1, title: "Hello", boby: "this is body") article2.id // 1 article2.title // Hello article2.boby // this is body
- メンバーワイズイニシャライザのデフォルト引数
- ストアドプロパティ が初期化式とともに定義されている場合
- そのプロパティに対するメンバーワイズイニシャライザの引数はデフォで引数を持ち、呼び出し時の引数の指定の省略ができる
struct Mail { var subject: String = "(No Subject)" var body: String // 以下と同等のイニシャライザが自動的に定義される // init(subject: String = "(No Subject)", body: String ) { // self.subject = subject // self.body = body // } } let noSubject = Mail(body: "Hello") noSubject.subject // "(No Subject)" noSubject.body // Hello let greeting = Mail(subject: "Greeting", body: "Hello!") greeting.subject // Greeting greeting.body // Hello!まとめ
簡単にまとめたのでこちらに記載しました。
何かあればご指摘ください
- 投稿日:2020-11-25T03:52:35+09:00
Swift 構造体まとめ
構造体
Swiftの構造体に関してまとめたので、記事として残しておきます。
構造体とは?
- 値型
- ストアドプロパティ の組み合わせによって、一つの値を表す
- 標準ライブラリで提供されている多くは構造体である
- Bool String Int Array Dictionary
- オプショナルは列挙型 タプルは型の組み合わせ Anyはプロトコル
定義
struct 構造体名 { 構造体の定義 } struct Article { let id: Int let title: String let body: String init(id: Int, title: String, body: String) { self.id = id self.title = title self.body = body } func printBody() { print(body) } } let article = Article(id: 1, title: "title", body: "body") article.printBody() // bodyストアドプロパティ の変更による値の変更
- 構造体はストアドプロパティの組み合わせで一つの値を表す値型
- 構造体のストアドプロパティを変更することは、構造体を別の値に変更することであり、構造体が入っている変数や、定数への再代入を必要とします
値型の変更に関する仕様は構造体のストアドプロパティ の変更にも適用される
定数のストアドプロパティは変更できない
構造体のストアドプロパティの変更は再代入を必要とするため、定数に代入された構造体のストアドプロパティは変更できない
struct SomeStruct { var id: Int init(id: Int) { self.id = id } } var variable = SomeStruct(id: 1) variable.id = 2 let constant = SomeStruct(id: 1) constant.id = 2 // error
- メソッド内のストアドプロパティの変更にはmutatingが必要
- 構造体のストアドプロパティの変更は再代入が必要なので、ストアドプロパティを含むメソッドにはmutatingが必要
struct SomeStruct2 { var id: Int init(id: Int) { self.id = id } // mutating をつけないとerror // Cannot assign to property: 'self' is immutable // 自動でつけてくれるようになる mutating func someMethod() { id = 4 } } var some2 = SomeStruct2(id: 1) some2.someMethod() some2.id // 4 変更されているメンバーワイズイニシャライザ
- デフォルトで用意されているイニシャライザ
- 型のインスタンスは初期化後に全てのプロパティ が初期化される必要がある
- 独自にイニシャライザを定義して初期化の処理を行うこともできます、
- 構造体では自動的に定義される メンバーワイズイニシャライザというイニシャライザがある
- 型が持っている各ストアドプロパティと同名の引数をとるイニシャライザである
struct Article2 { var id: Int var title: String var boby: String // 以下と同等のイニシャライザが自動的に定義される // init(id: Int, title: String, body: String) { // self.id = id // self.title = title // self.boby = body // } } let article2 = Article2(id: 1, title: "Hello", boby: "this is body") article2.id // 1 article2.title // Hello article2.boby // this is body
- メンバーワイズイニシャライザのデフォルト引数
- ストアドプロパティ が初期化式とともに定義されている場合
- そのプロパティに対するメンバーワイズイニシャライザの引数はデフォで引数を持ち、呼び出し時の引数の指定の省略ができる
struct Mail { var subject: String = "(No Subject)" var body: String // 以下と同等のイニシャライザが自動的に定義される // init(subject: String = "(No Subject)", body: String ) { // self.subject = subject // self.body = body // } } let noSubject = Mail(body: "Hello") noSubject.subject // "(No Subject)" noSubject.body // Hello let greeting = Mail(subject: "Greeting", body: "Hello!") greeting.subject // Greeting greeting.body // Hello!まとめ
簡単にまとめたのでこちらに記載しました。
何かあればご指摘ください
- 投稿日:2020-11-25T02:07:25+09:00
【iOS14】CollectionViewのscrollToItemが機能しない場合の対処法
iOS14において、CollectionViewの
scrollToItem
が機能しないバグが報告されています(執筆時、2020年11月25日現在)。AppleのDeveloper Forumsでも話題になっており、iOS14.2でも修正されていないようです。私もOS依存のバグとは気づかず、仕事で10時間くらい時間を溶かしました(ずっとiOS14のシミュレータでなんでスクロールできないんだろうと四苦八苦してた)https://developer.apple.com/forums/thread/663156
scrollToItemの説明(読み飛ばし可)
scrollToItem
は指定したindexPath
までcellをスクロールさせることができるものです。アプリ初回起動時のウォークスルー、チュートリアル画面などで、スワイプではなくボタンタップでcellをスクロールさせるような場合に使ったりすると思います。例えば上記のような画面で「次へ」を押した時にページをスクロールするような場合は、以下のように書けます。
collectionView.scrollToItem(at: IndexPath(item: newPage, section: 0), at: .init(), animated: true)しかしiOS14では、「次へ」を押してもなぜかスクロールされない状態となっています。
解決方法
フォーラムでは下記のように
scrollToItem
前後でisPagingEnable
のBool値を切り替えるような手法が提案されています。self.collectionView.isPagingEnabled = false self.collectionView.scrollToItem(at: indexPath, at: .left, animated: false) self.collectionView.isPagingEnabled = true実際試すとこれでもうまくいくのですがこれよりも、冒頭のようなチュートリアル画面を作りたい場合は、同じくcollectionViewの
setContentOffset
で代用する方が良いのではないかなと思いました。let cellWidth = collectionView.frame.width * CGFloat(newPage) let cellHeight = CollectionView.frame.minY collectionView.setContentOffset(CGPoint(x: cellWidth, y: cellHeight), animated: true)チュートリアル画面ではなくとも今まで
scrollToItem
を使っていた実装は、同じように座標を取得する方法が有効かと思います。おそらくすぐにAppleが対応してくれる問題だとは思うのですが、もしお困りの方がいらっしゃったら試してみてください〜
参考
- 投稿日:2020-11-25T01:02:58+09:00
【SwiftUI】Likeボタンタップ時のLike数増減とFirebaseの更新機能の実装
はじめに
SwiftUIでカード内のLikeボタンを押した場合にLike数を+1し、再度押した場合に-1する機能を実装することと、その場合にFirebaseのデータベースを更新できるようにすることを目的とする。
今回はカード内のLikeボタンの実装を行います。
前回までの記事は以下を参考ください。
参考記事
【SwiftUI】Likeボタンとリスト内セルにボタンを実装する場合の注意点開発環境
OSX 10.15.7 (Catalina)
Xcode 12.2.0
CocoaPods 1.10.0実装したコード
主な変更点
・Likeボタン内にif文を設定
→ボタンを押した場合はLike数を+1、再度押した場合は-1になるようにしました。また、押した場合はFirebaseのデータベースが更新されるようにしています。struct ContentRowView: View { var id = "" var user = "" ...略... static let formatter = RelativeDateTimeFormatter() // 以下を追記 @State var isLiked = false var body: some View { ...略... // Likes Number HStack { Image(systemName: isLiked ? "heart.fill": "heart").font(.headline).foregroundColor(Color("pinkColor")) Text(likes).font(.headline).foregroundColor(Color("pinkColor")) Spacer() }.padding(.top, 5) // Info HStack { ...略... // Like Button push Image to add like's Number. Image(systemName: isLiked ? "heart.fill": "heart") .font(.title) .foregroundColor(Color("pinkColor")) .onTapGesture { self.isLiked.toggle() if isLiked == true { // push button print("true. likes+1") // update likes... let db = Firestore.firestore() let like = Int.init(self.likes)! db.collection("ikku").document(id).updateData(["likes": "\(like + 1)"]) { (err) in if err != nil{ print((err) as Any) return } print("updated....") } } else { // And push Button print("false. likes-1") // update likes... let db = Firestore.firestore() let like = Int.init(self.likes)! db.collection("ikku").document(id).updateData(["likes": "\(like - 1)"]) { (err) in if err != nil{ print((err) as Any) return } print("updated....") } } } .frame(width: 30, height: 30) }.padding(5) }.padding(.top, 8).frame(height: 391) } }・getDataクラス内に
.modified
を実装
→Likeボタンを押すと、Like数も変更するようにしました。実装にあたって、ボタンを押すと、Like数左にあるハートマークも変更できるようにしています。class getData: ObservableObject { @Published var datas = [dataType]() init() { let db = Firestore.firestore() db.collection("ikku").order(by: "now", descending: true).addSnapshotListener{ (snap, err) in if err != nil{ print((err?.localizedDescription)!) return } for i in snap!.documentChanges{ if i.type == .added{ let id = i.document.documentID let user = i.document.get("userName") as! String let haiku = i.document.get("haiku") as! String let place = i.document.get("place") as! String let likes = i.document.get("likes") as! String let mapImage = i.document.get("mapImage") as! String let userImage = i.document.get("userImage") as! String let now = i.document.get("now") as! Timestamp let stamp = i.document.get("createdDate") as! Timestamp let formatterDate = DateFormatter() formatterDate.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" let createdDate = formatterDate.string(from: stamp.dateValue()) let isLiked = false DispatchQueue.main.async { self.datas.append(dataType(id: id, user: user, haiku: haiku, place: place, likes: likes, mapImage: mapImage, userImage: userImage, stamp: stamp.dateValue(), createdDate: createdDate, now: now.dateValue(), isLiked: isLiked )) } } // 以下を追加 if i.type == .removed{ let id = i.document.documentID for j in 0 ..< self.datas.count { if self.datas[j].id == id{ self.datas.remove(at: j) return } } } if i.type == .modified{ let id = i.document.documentID let likes = i.document.get("likes") as! String for j in 0 ..< self.datas.count { if self.datas[j].id == id{ self.datas[j].likes = likes return } } } } } } }実装後のイメージ
再度、タップするとFirebaseのLike数は-1になり、Likeの見た目が元に戻ります。
ハートをアニメーションで動かす
Likeボタンをタップした場合に、ハートにアニメーションがかかるようにしました。
実装方法
import SwiftUI struct ContentView: View { @State private var isLiked = false var body: some View { VStack { Text(isLiked ? "Liked!" : "unLiked!") HeartButton(isLiked: $isLiked) } } } struct HeartButton: View { @Binding var isLiked : Bool private let animationDuration : Double = 0.1 private var animationScale : CGFloat { isLiked ? 0.7 : 1.3 } @State private var animate = false var body: some View { Button(action: { self.animate = true DispatchQueue.main.asyncAfter(deadline: .now() + self.animationDuration, execute: { self.animate = false self.isLiked.toggle() }) if isLiked == true { // push button print("true") } else { // And push Button print("false") } }, label: { Image(systemName: isLiked ? "heart.fill": "heart") .font(.largeTitle) .foregroundColor(Color.red) .frame(width: 100, height: 100) }) .scaleEffect(animate ? animationScale : 1) .animation(.easeIn(duration: animationDuration)) } }実際に実装した後のイメージ
以上です。