【Swift】view controllerのライフサイクル

view controllerのライフサイクルについてまとめてみた(2020-08-12)

畑田です。
今となっては当然のように判断していますが、コードでiOSアプリを作る時に絡んでくるview controllerのライフサイクル(生活環)についてメモしておきます。

loadView()

基本的にoverrideして再定義するべきではないし、view controller classがインスタンス化されて終わっていないタイミングだから、selfが取れないです。
だからここにUIを司るviewとかを書いても嬉しくないというわけです。

viewDidLoad()

これは、view controller classの新しいファイルを生成するとXcodeが自動でoverrideを書き込んでくれるやつです。
ここでUIを設定してあげるのがおすすめだと考えられます。
view controllerのインスタンス化も済んでいるので、selfも取れるし、propertyもインスタンス化できるので、self.viewとかも自由自在というわけです。

viewWillAppear(), viewDidAppear()

その名の通り、件のview controllerが画面上に現れる(現れた)たびに呼ばれます。swipeで戻ったときとかも。
ここでしか実装できないこともありますね。画面が表示されるたびにタイムラインを更新したいとか、表示されていることを認識したい場合とか。
viewDidLoad()だとインスタンス化されたその時一回だけしか呼ばれないですからね。

viewWillLayoutSubviews(), viewDidLayoutSubviews()

これは画面をレイアウトする必要があるとiOSが認識した時に呼ばれます。つまりは初めにloadView()インスタンス化した後、UI partsを並べなきゃいけないときと、画面の向きが変わって表示を縦横変えなきゃいけないときです。
画面の回転を許すようなアプリケーションを作るときはここにレイアウトの処理だけを置いておくと賢いです。
またこの関数の中ではsafe areaを取得できるので、画面の端にuser interactionのあるUI partsをおくときはここにその設定を書くのが良さそうですね。
ただし、ここにUI partsのインスタンス化(button = UIButton(frame: CGRect()))みたいなのをかくと、画面が呼ばれるたびにそのインスタンスが生成されてメモリがどんどん食われる上、viewの上にUI partsを配置するコード(self.view.addSubview(button))を書くとすでに置かれているパーツの上にどんどんUI partsが重ねられてしまいます。 この中にはlayoutに関する設定だけを書くのが吉です!

参考図

swiftにおけるlifecycle
swiftにおけるlifecycle

Swiftにおけるorientation 操作

view controllerごとにorientationを操作した経験より(2020-08-15)

畑田です。 OTOnectの開発においてview controllerごとにorientationを細かく設定する機会があったので記録に残します。
全てコードで書いています。

環境

Swift version 5.2.4
Xcode version 11.6

fix application orientation

アプリ全体で画面の向きを固定する場合は、Info.plistSupported interface orientationsにおいて設定します。
例えばSupported interface orientationsPortrait (bottom home button)のみにすると、縦画面のみがサポートされますし、Portrait (bottom home button)Portrait (top home button)Landscape (left home button)Landscape (right home button)にすると全ての画面の向きがサポートされます。

fix launch screen orientation

launch screenの画面の向きはapplicationがlaunchされる前の設定なので、コードで設定するというよりはInfo.plistSupported interface orientationsにおいて設定します。
ただし、こうしてしまうとアプリ全体がそのorientationに固定されてしまうので、設定しなおしてあげる必要があります。
これはAppDelegate.swiftに記述してあげます。以下を参照してください。

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.allButUpsideDown
}

こうしてあげると、launch後の画面はXcodeのデフォルトの設定(上下反対の縦画面以外をサポートする設定)に戻ります。

fix orientation of each view controller

view controllerごとの画面の向きの設定はUIViewControllershouldAutorotatesupportedInterfaceOrientationをoverrideしてあげることで実現します。

override var shouldAutorotate: Bool {
    return false
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.portrait
}

fix orientation of navigation controller or tab bar controller

navigation controllerなどを使っている場合は、そのorientationをvisibleViewControllerのorientationに従わせるようなextensionを追加することで実現できます。

import UIKit

extension UINavigationController {
    open override var shouldAutorotate: Bool {
        guard let viewController = self.visibleViewController else { return true }
        return viewController.shouldAutorotate
    }

    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        guard let viewController = self.visibleViewController else { return .all }
        return viewController.supportedInterfaceOrientations
    }
}

set orientation when the view appears

それぞれのview controllerが立ち上がったときに画面の向きを指定したい場合は、以下のようなメソッドで実現可能です。
これを画面の向きを強制しタイミングで呼び出してあげるだけです。
もちろん.portraitの部分を変えてあげれば他のorientationにも対応できます。

UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")

ただし、このメソッドは強制的に画面の向きを指定したように回転させるので、呼び出したときにshouldAutorote propertyがtrueになっていないとエラーが起こります。
しかも、このメソッドは呼び出すたびに一度だけ強制的に画面を回転させるだけで、その後の画面の向きの保持には関わりません。
したがって、shouldAutorotateの値をflagで管理し、以下のようにするのが自分なりのpracticeです。

class EachViewController: UIViewController {
    
    // flag
    private var canRotate = true

    override var shouldAutorotate: Bool {
        return canRotate
    }

    override func viewDidLoad() { // viewDidLayoutSubviews()などでも
        super.viewDidLoad()

        // free orientation
        canRotate = true

        // force orientation change
        UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")

        // fix orientation
        canRotate = false

        // skip details
    }

}

URL control

localのファイルへのURLを扱った経験より(2020-08-20)

畑田です。
OTOnectの開発においてlocal fileへのpathを取得し、内容を保存、取得する機会があってわかりづらかったので記録に残します。
全てコードで書いています。

absolute path, relative pathとは

そもそもパスとはあるファイルの所在を表す文字列です。
terminalなどのshellにコマンドを打つときも、$ cd Developer/GitHub/MyApp/みたいに入力すると思いますが、このスラッシュで区切られている文字列がパスです。英語で道です。
さらにパスには絶対パス(absolute path)と相対パス(relative path)の2種類があります。
絶対パスは所在を表したいファイルを最も浅い階層のディレクトリから表したもので、/Users/daikihatada/Developer/GitHub/MyAppのようなものです。
相対パスは所在を表したいファイルを、そのパスが書かれたファイルのディレクトリから表したもので、./Developer/GitHub/MyAppのようなものです。

URLとは

URL (uniform resource locator)は通信方式やファイルのネットワーク内の所在地、それ付随するその他の情報を表す文字列で、ブラウザーがウェブ上のリソースを一意に特定するための仕組みです。
query stringsやroute patameterを持つこともできます。

FoundationのURLにおけるURLの扱い

iOSでは、remoteだけでなく、localのファイルへのpathをURLとして表すことができます。
FoundationフレームワークURLは構造体であり、localファイルへの参照を持っています。
実際にlocalのディレクトリへのpathを取得し、URL typeで値を返す関数が存在します。
また、SwiftのURLクラスはそのURLの絶対パス相対パスを値(String type)として持つpropertyを持っています。
localのファイルを表すURLを用いると、そのファイルを直接操作することなどができます。
以下に、既存のURLを編集して新しいURLを生成するサンプルコードを載せておきます。

let path = "https://daikihatada-url/"
let directoryName = "image"
let imageName = "sample"
let imageExtension = "jpg"

// convert String into URL
let url = URL(fileURLWithPath: path) // https:/daikihatada-url

// append directory to the end of URL (with `isDirectory` `true`, `/` appended to the end of the directory name)
let directoryURL = url.appendingPathComponent(directoryName, isDirectory: true) //https:/daikihatada-url/image/

// append file to the end of URL
let imageNameURL = directoryURL.appendingPathComponent(imageName) // https:/daikihatada-url/image/sample

// append extension to the end of file name with `.`
let completeURL = imageNameURL.appendingPathExtension(imageExtension) // https:/daikihatada-url/image/sample.jpg

// delete extension at the end of URL
let extensionDeletedURL = completeURL.deletingPathExtension() // https:/daikihatada-url/image/sample

// delete file name behind the last slash
let imageNameDeleteURL = extensionDeletedURL.deletingLastPathComponent() // https:/daikihatada-url/image/

// get file name behind the last slash
let fileName = completeURL.lastPathComponent // sample.jpg

//  get extension of the file name behind the last slash
let fileExtension = completeURL.pathExtension // jpg

// get each component of URL in array
let components = completeURL.pathComponents // ["/", "daikihatada-url", "image", "sample.jpg"]

localのdirectory構造

アプリの詳しいdirectory構造の説明は省きますが下に参考画像を載せておきます。
本当はもう少しdirectoryがあります。

localのdirectory構造
localのdirectory構造

localのdirectoryへのURLを取得する方法

まずはDocumentsのdirectoryへのURLを取得してみます。
以下のコードは根っこのdirectoryの下から"Documents"という名前のdirectoryを探して、そこまでのURLを配列で返すものです。
for:の部分は、.cachesDirectoryなどにもできて様々なdirectoryを検索できます。
in:の部分は.allDomainMaskなどにもできて検索する範囲を指定できますが、.userDomainMaskを使うことがほとんどだと思います。

let documentsDirURLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)

これの0番目の要素がアプリのホームディレクトリ直下のDocumentsディレクトリであるので、それを取得すればアプリのDocumentsディレクトリにアクセスできるというわけです。
一応、nil safetyを噛ませておくと安全ですね。

// with nil safety
guard let documentsDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }

ちなみに、配列に対してsomeArray[0]とすると、その要素がない場合にerrorを起こし、someArray.firstとすると、その要素がない場合にnilを返します。
値がnilであった場合にアプリを落としたい場合は前者を、optionalなどで処理を進ませたい場合は後者を選択するべきでしょう。
また、tmpディレクトリへのアクセスだけは特別です。

let tmpDirURL = FileManager.default.temporaryDirectory

tmpディレクトリのURLの取得方法が違うのはtmpディレクトリの特殊性に原因があるようです。
tmpディレクトリは一つのアプリケーションにただ一つしかなく、またその存在が保証されているため、tmpDirnilとなることはないと考えられるからだと思います。

localのdirectoryへのpathを取得する方法

以下はpathを直接取得するためのソースコードです。 ホームディレクトリとルートディレクトリとtmpディレクトリ以外は直接pathを取得できません。

// path to home directory
let homeDirPath = NSHomeDirectory()

// path to root directory
let rootDirPath = NSOpenStepRootDirectory()

// path to tmp directory
let tmpDirPath = NSTemporaryDirectory()

// url to documents directory
let documentsDirURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

// path to documents directory
let documentsDirPath = documentsDirURL.path

メディアのファイル形式について

動画を録画して音声を分離、特定の形式に変換して保存した経験より(2020-08-06)

畑田です。
OTOnectの開発において動画や音声を細かく操作する機会があったので、メディアのファイル形式について調べたのでまとめておきます。
よくコンテナ形式(メディアの保存形式)と圧縮形式、拡張子に対する理解が混同されて用いられますので注意してください。

基礎知識

PCM

PCM (pulse code modulation)とは、音声などのアナログ信号をデジタルデータに変換する方式の一つです。
信号の強度を一定周期でサンプリングしたもので、そのまま保存すれば非圧縮データとなります。
アナログ信号の強度をサンプリング周波数に従って一定間隔で測定し、定められたビット数の範囲で整数値として量子化します。
例えば、CDの音声はサンプリング周波数 44.1 kHz、量子化深度(bit depth) 16 bit で記録されているのですが、これは毎秒44,100回信号を測定し、その強度を65536(216)段階の値で表しているという意味です。

interleave

音声技術におけるinterleaveとは、ステレオインターリーブ(ステレオ)のことを指します。

動画ファイル形式

MP4

MP4はMPEG-4規格の一部で、MPEG-4 Part 14で標準化されています。
ちなみにMPEGはMoving Picture Experts Groupの略であり、ISO/IECのワーキンググループ、または彼らが規定した規格の総称です。
拡張子.mp4を推奨されていて、MPEG-4規格の動画・音声の記録に用いられています。
2004年に定めたれたMPEG-4 Part 15からはMP4 AVCEと呼ばれ、H.264AACというコーデックで圧縮されたファイルを格納できるようになりました。
またこの形式はMOV形式から派生したものであり、現在のデファクトスタンダードです。
ストリーミングサイトなどにポストする場合などはこの形式を使うべきです。

MOV

MOVはApple Inc.製のマルチメディア技術QuickTimeの一部で、動画・音声の記録に用いられています。
標準の拡張子は.mov.qtです。
圧縮方式としては、動画についてはMPEG-1、MPEG-2、MPEG-4/H.264、H.263、DVなどを、音声についてはMP3、AACApple Lossless、WAV、MIDI(音楽)などを選択することができます。
各データはトラックという単位で格納されて再生時に重ね合わせて出力されるため、特定のタイミングで字幕を表示するといった複合的なデータを表現することができます。
MP4はMOVから派生したのでこのような性質を受け継いでいます。
ちなみにmacOSのdefaultの設定で録画した場合やSwiftのAVCaptureMovieFileOutputで録画した場合はMOV形式で保存されます。

MKV

MKVの正式名称はMatroskaMatroska.orgによって開発されました。
どんなコーデックにも対応しています。
字幕表示のON・OFF、多重字幕、多重音声、ファイル自体が欠損したままの再生など非常に多くのことができる。
拡張子は.mkv(Matroska Video)や.mka(Matroska Audio)が一般的です。
Windows 10から標準対応したので、世界的に流行する機運が高まっています。

音声圧縮方式

WAVE

WAVまたはWAVE(RIFF waveform Audio Format)は、Microsoft CorporationとIBMにより開発された音声データ記述のためのフォーマットで、RIFF(Resource Interchange File Format、タグ付きデータを格納するための汎用メタファイル形式)の一種です。
主としてWindowsで使われ、ファイルに格納した場合の拡張子は、.wavです。
通常は非圧縮linear PCM (linearは非圧縮を意味する)のサンプリングデータ用のフォーマットとして扱われますが、WAVはいわゆるコンテナ形式で、データ形式は自由なので、μ-lawや、ADPCM、MP3、WMAなどの圧縮データを格納することもできます。

AIFF

AIFFApple Inc.製のコンテナフォーマットで、データ形式はRIFFと似ています。
AIFFは主としてMacintoshAmiga上で使われ、ファイルに格納した場合の拡張子は、.aiff.aifです。
一般には非圧縮linear PCMのサンプリングデータ用のフォーマットとして扱われますが、圧縮音声も記録でき、非可逆圧縮を利用した場合のファイル拡張子は.aifc(AIFF-Compression)となります。

AAC

AAC(Advanced Audio Coding)は、不可逆のデジタル音声圧縮を行う音声符号化規格で、1997年にISO/IEC JTC 1のMoving Picture Experts Group (MPEG)において規格化されました。
ここで不可逆圧縮というのはdecompressionしたときに、完全に復元されることがないという意味であり、復元がまったくもって不可能という意味ではないという点に注意してください。
MP3の後継フォーマットとして策定され、一般的にAACは同程度のビットレートであればMP3より高い音声品質を実現しています。
ただしMP3がMPEG-1の拡張形式であるのに対して、AACMPEG-4形式がベースとなっている。
AACはISOとIECにより、MPEG-2およびMPEG-4仕様の一部として標準化されました。
ファイル形式としては、MOV, MP4, AVI, Matroska, MPEG-2 TSに対応しています。m4aという拡張子がつくこともあるようです。

MP3

MP3は、元々ビデオ圧縮規格であるMPEG-1のオーディオ規格として開発されました。
不可逆圧縮であり、それ以前の規格であるMP1およびMP2を改良したものにあたります。
当初はMPEG-1 Audio Layer-3の略称だったが、のちに互換性を持つMPEG-2 AudioBC (MPEG-2 Audio Layer-3)が加わったので、合わせてMPEG-1/2 Audio Layer-3とすることもあります。
ファイル形式としては、ASF、AVI、Ogg、OGM、Matroska、MOV、MP4、MPEG-2、WAVに対応しています。

.m4aってなんやねん

拡張子.m4aApple Inc.が使いだしたもので、MP4コンテナにAAC形式かApple Lossless形式で圧縮された音声データや楽曲情報(タグ)を入れたものです。
本来のルールからすると拡張子.mp4にすべきものですが、iPod/iTunesの隆盛により今や一般化しています。
拡張子.aacのファイルは、AAC形式の音声データをコンテナなどに入れずに裸のままファイルにしたものです。
楽曲情報(タグ)などが必要な場合はMP3と同じID3タグか、Monkey's Audioと同じAPEタグを付ける場合が多いようです。
ちなみにこういったものについて、(例えばAACの場合、)音声データ形式だけは同じでもファイルの構造が異なりますので、拡張子だけを.m4a.aacなどと書き換えてはいけません。
音声ファイルとしての機能、性能面での優劣は殆どありませんが、iPod/iTunesのおかげで.m4aは現在デファクトスタンダードであり、.m4aの方が対応ソフトや機器が多いと思います。
このように、拡張子が多様化してしまうのは、拡張子というものが「どの形式のコンテナにどの形式で処理されたメディアを格納するか」に対応しているからなのです。
したがって、これらのデータに変換などの処理をかませるときは拡張子に注目するというよりは、格納されているデータの形式に注目する必要があります。

OSって何?

OSとはコンピュータをコンピュータたらしめるもの

は?どういうことやねん。ということでさらに詳しく見ていきましょう。 OSって例えばどんなものがあるか挙げられますか。 Linux, macOS, Windows, iOS, Androidなどがありますね。 OSはoperation systemの略で、コンピュータの基本的な動作を司ります。 例えば、メモリやストレージなどの記憶デバイスに情報を書き込むのもOSですし、ファイルに書かれたプログラムを実行させるのもOSです。 OSがなければコンピュータはただの箱でしかないのです。

カーネルとシェル、、、核と殻?

OSの核となる部分のことをカーネルと呼びます。(kernelは英語で、核とか種とかいう意味なのでそのままですね!) カーネルは主に次のような仕事をしています。 - デバイス管理 - プロセス管理 - メモリ管理

バイス管理

バイス管理のデバイスってなんやねんって感じなんですが、コンピュータを構成しているハードウェアのことです。 CPUとかメモリとかストレージとかのことですね。 これが適切な動作を行うようにデバイスドライバーというソフトウェアを通して管理しています。 これが行われないと、コンピュータの部品たちが協調的に動いてくれないってことですね!

プロセス管理

これまた、プロセスってなんやねんって感じです。プロセスとは一連のプログラム(コンピュータにわかる形で書かれた命令)が実行された動作のことです。 カーネルはプロセス対してid(プロセスID)を与えて管理します。 複数のプロセスを同時に制御したり、それぞれのプロセスに効率よくCPUを割り振ったりして、プログラムの実行を可能にする役割を担います。

メモリ管理

メモリは一次記憶装置などとも呼ばれ、ここの上にプログラムやデータが展開されることで、プログラムが実行されます。ストレージとは別物です。 適切にメモリをデータに割り振ったり、仮想メモリを作るのもカーネルの仕事なのです。

シェルはカーネルとユーザーを繋いでいる!

シェルはその名の通りカーネルを包む殻のように我々ユーザーとカーネルを繋ぐインターフェイスです。 シェルにできるのは次のようなことです。 - アプリケーションの起動、停止などの命令 - 環境変数の管理 - コマンド履歴の管理 - コマンド実行結果の表示

また、シェルにはbash, zsh, cshなどがあり、macmacOS Catalinaからbashでなく、zshを標準にしたことは記憶に新しいですね。

終わりに

すごくざっくりとした説明でしたが、深すぎず浅すぎずということを意識して説明させていただきました。 この皆さんのOSに対する理解の助けになることを祈っています。
これは余談なんですけど、macOSの各バージョンにはコードネームがあります。
以下一覧。
Mac OS X 10.0: Cheetah
Mac OS X 10.1: Puma
Mac OS X 10.2: Jaguar
Mac OS X 10.3: Panther
Mac OS X 10.4: Tiger
Mac OS X 10.5: Leopard
Mac OS X 10.6: Snow Leopard
Mac OS X 10.7: Lion
OS X 10.8: Mountain Lion
OS X 10.9: Maveriks
OS X 10.10: Yosemite
OS X 10.11: El Capitan
macOS 10.12: Sierra
macOS 10.13: High Sierra
macOS 10.14: Mojave
macOS 10.15: Catalina
macOS 10.16: Big Sur
2013年10月にリリースされたバージョンは「OS X Mavericks(マーヴェリックス)」です。カリフォルニア州の海岸の名前のことですね。
2014年10月16日にリリースされたバージョンは「OS X Yosemite(ヨセミテ)」です。ヨセミテとは、米国カリフォルニア州にある地名(渓谷)のことで、ヨセミテ国立公園が有名ですね。ヨセミテ国立公園はロッククライミングの名所で巨大な絶壁が多くあり、豊かな山や川、滝などの自然が世界遺産(自然遺産)に登録されています。
2015年10月1日に「OS X El Capitan(エル・キャピタン)」がリリースされました。El Capitan(エル・キャピタン)とは、ヨセミテ国立公園の中にあるでっかい花崗岩の一枚岩のことです。
2016年9月20日に「macOS Sierra (シエラ)」がリリースされました。カリフォルニア州東部にある山脈ですね。
2017年9月25日に「macOS High Sierra (ハイ シエラ)」がリリースされました。カリフォルニア州東部にあるシエラ国有林の中にある領域です。
2018年9月25日に「macOS Mojave (モハベ)」がリリースされました。カリフォルニア州ユタ州ネバダ州、アリゾナ州にまたがる砂漠です。
2019年10月7日に「macOS Catalina (カタリナ)」がリリースされました。カリフォルニア州南部にある島です。
2020年秋に「Big Sur (ビッグサー)」がリリース予定です。