- 投稿日:2020-06-29T19:52:15+09:00
キャプチャリストにおいて複数の変数に対して weak, unowned キーワードを付ける際の注意点
今回はトレイリングクロージャにおけるキャプチャリストについての気づきをシェアします!
小ネタですが、意識しないと気づかずメモリリークを起こす原因になるような注意点です!本題: 弱参照のつもりが、強参照に!?
ネットにあるコードを読んでいると、たまに、メモリリーク解消を目的とした弱参照を宣言するために、以下のような実装を見かけます。
alertController.addAction(UIAlertAction(title: "OK", style: .default) { [weak self, alertController] _ in
[weak 変数1, 変数2]
のような実装になっていますね。
コードを読むと、どうやら変数1
変数2
ともにweak
属性にして弱参照にしたいようです。
が、この実装では変数2
が強参照になります。もう一度申し上げます。
変数2
は強参照です。対策
じゃあ、どうするか??
[weak 変数1, weak 変数2]
とそれぞれの変数の前にweak
を宣言することで実現可能です。
先程のUIAlertAction
のトレイリングクロージャの例だと、alertController.addAction(UIAlertAction(title: "OK", style: .default) { [weak self, weak alertController] _ inとなります。
おわり
メモリリークの原因になるので気をつけたいですね?
- 投稿日:2020-06-29T16:00:20+09:00
手を動かして理解するCocoa MVCパターン
はじめに
本記事ではiOSアプリ実装では最もよく用いられているであろうCocoa MVCパターンについて解説します。
巷ではクリーンアーキテクチャーはじめ様々なソフトウェアアーキテクチャーが出てきていますが、Swiftでアプリケーションを構築する上で余程大規模にならない限りは、Apple自体が推していることもあり、このCocoa MVCパターンを使うのが自分の経験上最もいいのではないかなと思います。ソフトウェアアーキテクチャーは本などで読むとふむふむなるほどと思うのですが、いざ実装しようとしてみると難しいことが多いです。
そこで今回は実際に非常に単純なアプリケーションを実装しながら手を動かして理解するような構成にしています。
では早速始めていきましょう!Cococa MVCパターンについてのイメージを掴む
何も知らない状態で手を動かしてもあまり学習効率が良くないので、先にCocoa MVCのイメージを掴んでおきましょう。
実際手を動かした後もう一度復習するので、この時点ではこんなもんだなーくらいの理解で構いません。MVCパターンとはModel-View-Controller Patternの略で、その名の通りアプリケーション内のコードをModel、View、Controllerの3つの役割に分割します。
Model
Modelはデータの保持及び処理を担当します。
具体的には通信処理や計算処理、ローカルストレージに対する保存処理など、後述のView、Controller以外全てがModelに含まれます。RailsなどのWebアプリのフレームワークにおけるSQLデータベース上のテーブルを表現するModelとは意味が異なるので注意が必要です。
またModelは保持しているデータが変更されたら、そのModelを購読しているControllerに変更を通知します。View
Viewは画面の描画処理を行います。
再利用性を高めるためにも極力ロジックは含めずに、入力を受けてそれをそのまま描画するような設計にすることが重要です。Controller
ControllerはModelとViewの参照を保持し、Modelについては変更を監視します。
Controllerはユーザーの入力を受けつけ、Modelに処理を依頼し、Modelが変更されたのを検知してViewの描画を更新します。これらをまとめると以下のようになります。
名前 役割 Model データ保持、処理を行う View 画面描画を行う Controller ユーザーの入力を受け付ける。ModelとControllerの橋渡し役となりアプリ全体をコントロールする。
ここで非常に重要なポイントはControllerがViewとModelの橋渡し役となることで、ViewとModelが何かに依存せずに完全に独立しているところです。
こうすることで、ViewとModelは様々なところで再利用することができるようになります。
逆にControllerは特定のViewとModelに強く依存しており、Cocoa MVCは
Controllerの再利用性を犠牲にViewとModelの再利用性を極限まで高めた設計と言えます。そして実際にユーザーの入力〜画面の描画までの全体の処理の流れは以下のようになります。
- [Controller] ユーザーの入力を受け付ける。
- [Controller] Modelに処理を依頼する。
- [Model] データを処理する。
- [Model] データの処理結果を購読しているControllerに通知する
- [Controller] Modelの変更を検知する。
- [Controller] Viewに描画を指示する。
- [View] 画面に描画を行う。
ではここままででざっくりイメージがつかめたと思うので、次項から実際に手を動かして実装していきましょう!
手を動かして実装する
今回作成するアプリは+ボタン、-ボタンで数字を増減させることができるシンプルなカウンターアプリです。
こちらをCocoa MVCパターンを使って実装していきましょう。
ゼロからプロジェクト作成をしていただいても構いませんし、初期の状態と完成形をこちらに用意しておいたので、こちらをCloneしてJP/Starterから始めても構いません。
https://github.com/kazuooooo/CocoaMVCFromScratchCounterModelを実装する
まずはMVCのM、ModelにあたるCounterModelを実装していきましょう。
前述の通りデータの保持、処理を行うのがModelの役割なので、
- 今数値がいくらなのか保持する(データの保持)
- 数字を増やす/減らす(データの処理)
- Modelを監視しているコントローラーに変更を通知する
が必要です。
CounterModel.swiftimport Foundation class CounterModel { static let notificationName = "CounterModelChanged" let notificationCenter = NotificationCenter() // 今数値がいくらなのか保持する(データの保持) internal var count: Int = 0 { didSet { // Modelを監視しているコントローラーに変更を通知する notificationCenter.post( name: .init(rawValue: CounterModel.notificationName), object: count ) } } // 今数値がいくらなのか保持する(データの保持) func countUp(){ count += 1 } func countDown(){ count -= 1 } }CounterViewを実装する
続いてMVCのVにあたるCounterViewを実装します。
Viewの役割は描画処理を行うことです。
CountViewはrenderというメソッドを通してcountLabelに描画処理を行います。CounterView.swiftimport Foundation import UIKit class CounterView: UIView { @IBOutlet weak var countLabel: UILabel! public func render(count: Int){ countLabel.text = String(count) } }続いて実際の画面をStoryBoardで作成してください。(Starterを使っている場合は事前に作成してあります。)
カウントのLabelはIBOutletで接続し、親のViewにCounterViewを設定します。
CounterViewControllerを実装する
最後にMVCのC、CounterViewControllerを実装します。
ControllerではViewとModelの橋渡しをするために
- Modelの変更を監視する
- ユーザーの入力を受け付けて、Modelに処理を依頼する
- Modelの変更を検知して、Viewに描画処理を依頼する
を行う必要があります。
実装は以下のようになります。CounterViewControllerimport UIKit class CounterViewController: UIViewController { // ViewとModelの参照を保持する @IBOutlet var counterView: CounterView! private(set) lazy var counterModel = CounterModel() override func viewDidLoad() { super.viewDidLoad() // Modelの変更を監視する counterModel.notificationCenter.addObserver( self, selector: #selector(self.handleCountChange(_:)), name: .init(NSNotification.Name(rawValue: CounterModel.notificationName)), object: nil ) } // 変更を検知する @objc func handleCountChange(_ notification: Notification) { if let count = notification.object as? Int { // Viewに描画処理を依頼する counterView.render(count: count) } } // 入力を受け付ける @IBAction func OnPlusButtonTapped(_ sender: Any) { // Modelに処理を依頼する counterModel.countUp() } @IBAction func OnMinusButtonTapped(_ sender: Any) { counterModel.countDown() } }こちらもボタンのIBActionへの紐付け、ViewControllerクラスの設定を忘れないようにしましょう。
さて以上で実装は完了です。
一度Runをして+/-ボタンが正しく動くか確認してみてください!再度パターンに当てはめて考えてみる
実装は完了しましたが、写経しただけ感があってまだどこかしっくりきていませんよね?
最後にもう一度全体像をコードを見ながら確認していきましょう。
そうすることで理解がグッと深まるはずです。依存関係を見てみる
まずは依存関係について先ほど見たこちらの図を見ながらコードをもう一度確認してみましょう。
Controllerのコードを見てみると、ViewとModelの参照及び、変更の監視がされていることがわかります。
CounterViewControllerclass CounterViewController: UIViewController { // ViewとModelの参照を保持する @IBOutlet var counterView: CounterView! private(set) lazy var counterModel = CounterModel() override func viewDidLoad() { super.viewDidLoad() // Modelの変更を監視する counterModel.notificationCenter.addObserver( self, selector: #selector(self.handleCountChange(_:)), name: .init(NSNotification.Name(rawValue: CounterModel.notificationName)), object: nil ) } ... }またViewとModelはControllerは処理を受け付けるだけで完全に独立しており、再利用可能なこともコードを見てチェックしておいてください。
処理の流れをチェックする
最後に+ボタンを押した時を例に全体の処理の流れも図とコードを見ながらチェックしていきます。
1.入力を受け付ける
CounterViewController// 入力を受け付ける @IBAction func OnPlusButtonTapped(_ sender: Any) { ... }2. Modelに処理を依頼する
入力を受けたらControllerはModelに処理を依頼します。CounterViewController@IBAction func OnPlusButtonTapped(_ sender: Any) { // 2. Modelに処理を依頼する counterModel.countUp() }3. データを処理する
Modelはデータを処理します。
今回の場合はcountUpが呼び出されているので自身のcountをインクリメントします。CounterModelclass CounterModel { ... // データを処理する func countUp(){ count += 1 } }4. 変更を通知する
ModelはNotificationCenterを通じて購読者に変更を通知します。
CounterModelclass CounterModel { ... internal var count: Int = 0 { didSet { // Modelを監視しているコントローラーに変更を通知する notificationCenter.post( name: .init(rawValue: CounterModel.notificationName), object: count ) } } ... }5. Modelの変更を検知する
ControllerはModelのNotificationCenterに登録しておいたObserverからModelの変更を検知します。
class CounterViewController: UIViewController { override func viewDidLoad() { ... // Modelの変更を監視する counterModel.notificationCenter.addObserver( self, selector: #selector(self.handleCountChange(_:)), name: .init(NSNotification.Name(rawValue: CounterModel.notificationName)), object: nil ) } ... // 変更を検知する @objc func handleCountChange(_ notification: Notification) { ... } }6.描画指示
@objc func handleCountChange(_ notification: Notification) { if let _ = notification.object as? Int { // Viewに描画処理を依頼する counterView.render(count: counterModel.count) } }7.描画処理を行う
Controllerからの描画指示を受けてViewは描画処理を行います。
CounterViewclass CounterView: UIView { // 描画処理を行う public func render(count: Int){ countLabel.text = String(count) } }終わりに
いかがだったでしょうか?
ソフトウェアアーキテクチャーパターンは初めはとっつきにくいですが、一度理解してしまえばエンジニアにとって非常に強力な武器になってくれます。
今回のCocoaMVCのようにきっちり理解できていなくて雰囲気で使っているなーというところは一度腰を据えて自分で手を動かしてみることで理解が一気に深まるのでオススメです!
- 投稿日:2020-06-29T14:12:45+09:00
App Clipsをざっくり理解する
WWDC20で発表された、App Clipsをざっくり理解するために要点をまとめています。
仕様・特徴
- ユーザーはアプリをインストールする必要なく、ネイティブアプリの一部を使用できる
- App Clipによって処理されるURLを使用する(ユニバーサルリンクに似ている)
- NFCタグ、QRコード、Smart App Banner、iMessageのリンク、Mapsから開くことができる
- App Clip URLはApp Store Connectを使用して登録する
- アプリとApp Clipの両方を含むビルドをApp Store Connectに配信すると、App Clip Configurationセクションが表示される
- 異なるエクスペリエンスには個別のURLを使用する
- URLはprefixの一致に基づいているため、すべての可能なApp Clip URLを登録する必要はない(クエリストリングやパスパラメータで柔軟に出来る)
- アプリがインストールされている場合アプリが優先、アプリがインストールされていない場合は、App Clipがダウンロードされる
- 使用されない場合、iOSはApp Clipとそのデータを削除する
制限・注意点
- App Clipは追加機能であり、App Clip単体では提供できない
- App Clipでのインタラクションは、迅速かつ集中的に行う必要がある
- XcodeでApp Clip用のターゲットを作成する必要がある
- App Clipは10メガバイト未満にする必要がある
- App Clipからの機密情報へのアクセスは制限されている
- タイトル、サブタイトルの長さ、画像サイズ、アスペクト比、およびフォーマットの要件がある
- ユーザーが一貫した体験を得られるように、App Clipにはアプリと同じ名前とアイコンを使用する
その他出来ること
- App Clipからアプリへデータ移行するには、App Groupを利用する
- SKOverlayを使用してアプリをインストールするよう促すことができる
- XcodeでApp ClipのURL処理コードをテストするには、テストURLを指定できる
- App Clipsは、カメラ、マイク、およびBluetoothの許可を要求することができる
- App Clipの通知では、毎回の起動後、最大8時間まで許可を得ることができる
関連リンク
https://developer.apple.com/videos/play/wwdc2020/10174
https://developer.apple.com/videos/play/wwdc2020/10146
https://developer.apple.com/videos/play/wwdc2020/10120
https://developer.apple.com/videos/play/wwdc2020/10172
https://developer.apple.com/app-clips/
https://developer.apple.com/design/human-interface-guidelines/app-clips/overview/
https://developer.apple.com/documentation/app_clips/developing_a_great_app_clip
https://developer.apple.com/documentation/app_clips/creating_an_app_clip
https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui
- 投稿日:2020-06-29T12:51:10+09:00
CocoPodsで外部のライブラリの導入方法
多くのプロジュエクトが外部のライブラリを使っています。
Githubからプロジェクトをダウンロードして、ライブラリの導入したり、インストールしたりする必要です。必要のライブラリの導入方法
1.ターミナルでpodバージョンを確認
pod --version
バージョンが1.7.5より古い、またはpodがインストールしていない場合は下記のコマンドで更新またインストールします。gem sources --remove https://rubygems.org/ gem sources --add https://gems.ruby-china.com/ //pod install sudo gem install cocoapods -n /usr/local/bin sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer //update pod local library pod setup2.プロジェクト関連のライブラリを追加します。
プロジュエクトが必要なラブラリの定義はPodfileで記載します。//cd 実際のxiaozhiboのフォルダのパス 例えば: cd ~/iOs/XiaoZhiBo // Podfileから必要なラブラリをインストール pod install参考資料
- 初心者向け CocoaPods の使い方 [導入編] http://developers.goalist.co.jp/entry/2017/04/20/180931
- 投稿日:2020-06-29T12:51:10+09:00
CocoaPodsで外部のライブラリの導入方法
多くのプロジュエクトが外部のライブラリを使っています。
Githubからプロジェクトをダウンロードして、ライブラリの導入したり、インストールしたりする必要です。必要のライブラリの導入方法
1.ターミナルでpodバージョンを確認
pod --version
バージョンが1.7.5より古い、またはpodがインストールしていない場合は下記のコマンドで更新またインストールします。gem sources --remove https://rubygems.org/ gem sources --add https://gems.ruby-china.com/ //pod install sudo gem install cocoapods -n /usr/local/bin sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer //update pod local library pod setup2.プロジェクト関連のライブラリを追加します。
プロジュエクトが必要なラブラリの定義はPodfileで記載します。//cd 実際のxiaozhiboのフォルダのパス 例えば: cd ~/iOs/XiaoZhiBo // Podfileから必要なラブラリをインストール pod install参考資料
- 初心者向け CocoaPods の使い方 [導入編] http://developers.goalist.co.jp/entry/2017/04/20/180931
- 投稿日:2020-06-29T06:35:34+09:00
Flutterで画像、カスタムフォントを使用する方法
今回はFlutterアプリで画像とカスタムフォントを使用する方法を記述します。
プロジェクトに画像とフォントを追加する
- プロジェクト直下に
assets
フォルダを作成し、その直下にimages
とfonts
フォルダを作成する。- 使用したい画像とフォントを各フォルダに格納する。
pubspec.yaml
にパスを記述するFlutterで画像やフォントを使用するには、
pubspec.yaml
にパスを記述する必要があります。pubspec.yamlflutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: assets: - assets/images/ - assets/fonts/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: fonts: - family: OpenSansCondensed fonts: - asset: assets/fonts/OpenSansCondensed-Light.ttf weight: 300 - asset: assets/fonts/OpenSansCondensed-Bold.ttf weight: 700フォントファイルは太さによって複数に分かれていることが多いので、それぞれにweightを定義します。
pubspec.yaml
に記述する際、インデントが揃っていないと、エラーになったり、上手く適用されないなどがあるので、注意が必要です。画像を表示する
実際にアプリ上にプロジェクトに追加した画像を表示してみます。
aseetsの画像を表示する
main.dartimport 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text('Flutter Demo'), ), body: Center( child: Image.asset('assets/images/sample.png'), ), ), ); } }ネットワーク上の画像を表示させる
Image.network('イメージのURL')画像のURLを記述することで、URL先の画像を表示させることもできます。
カスタムフォントを使用する
カスタムフォントを適用する方法は主に2種類あります。
アプリ全体のデフォルトに設定する
MaterialApp()
のtheme
に指定してあげることで、デフォルトのフォントに設定されます。main.dartclass MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData(fontFamily: 'OpenSansCondensed'), home: SamplePage(), ); } }個別に指定する
特定のTextにのみ適用させたい場合は、以下のように
TextStyle
のfontFamily
に指定します。
fontWeight
にpubspec.yaml
に記述したweightを指定することで、それぞれのフォントファイルを使用することができます。main.dartText( 'custom font', style: TextStyle( fontFamily: 'OpenSansCondensed', fontWeight: FontWeight.w300, fontSize: 40, ), ),おまけ
フォントファイルをわざわざ用意しなくとも、google_fontsを使用することができるFlutterのパッケージがありますので、そちらを利用してみるのもいいかもしれません。
https://pub.dev/packages/google_fonts