swiftにおける構造体の型?について質問です。
現在、swiftでiOSアプリを開発していて、documents配下のファイルパスを取得しようとした際、以下のような記載をするかと思うのですが、
swift
1let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
この.userDomainMaskって何者なのかと思い、⌘を押しながらクリックしてみたところ以下のようになっていました。
(fileManagerのExtensionに含まれている。)
swift
1public struct SearchPathDomainMask : OptionSet { 2 3 public init(rawValue: UInt) 4 5 6 public static var userDomainMask: FileManager.SearchPathDomainMask { get } // user's home directory --- place to install user's personal items (~) 7 8 public static var localDomainMask: FileManager.SearchPathDomainMask { get } // local to the current machine --- place to install items available to everyone on this machine (/Library) 9 10 public static var networkDomainMask: FileManager.SearchPathDomainMask { get } // publically available location in the local area network --- place to install items available on the network (/Network) 11 12 public static var systemDomainMask: FileManager.SearchPathDomainMask { get } // provided by Apple, unmodifiable (/System) 13 14 public static var allDomainsMask: FileManager.SearchPathDomainMask { get } // all domains: all of the above and future items 15 } 16
この定義が全く理解できません。
まず、SearchPathDomainMaskという構造体は、OptionSet型なのでしょうか??そして、public staticで宣言されている変数達の型はこの構造体自身の型になっている??
Google検索などしてみましたが、このような記載方法をしている例を上手く見つけられませんでした。。。
どなたかswiftの文法を説明頂ける方、ご教授いただけないでしょうか??
よろしくお願いいたします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
#構造体の文法の説明
public struct SearchPathDomainMask : OptionSet{}
の文法的な意味は「構造体SearchPathDomainMaskはOptionSetというプロトコルを採用している」という意味になります。
Swiftでは、構造体(struct)や列挙型(enum)にもプロトコルを採用することができます。
Objective-Cではクラスしかプロトコルを採用できなかったのですが、それが拡張されました。
構造体でプロトコルを採用する文法は以下の通りです。構造体の型の名前の後ろに:(コロン)をつけて定義します。
struct <構造体の型の名前> :<プロトコル名>{ }
質問で書かれていた
SearchPathDomainMaskという構造体は、OptionSet型なのでしょうか??
ですが、クラスの定義とごっちゃになっていそうなので、クラスの定義もお伝えすると
class <クラスの型の名前>:<親クラス or プロトコル名>,<プロトコル名>{ }
というように、クラスの定義では:(コロン)の後は継承する親クラスか、採用するプロトコル名を指定して、その後「,(カンマ)」で区切ってプロトコル名を書いていきます。
クラスでは親クラスから継承できるんですが、構造体は継承ができないので:(コロン)の後は必ずプロトコルになります。
#OptionSetって何?
じゃあ、OptionSetプロトコルってどんな時に使うんじゃいという話になりますが、OptionSetにカーソル当てて⌘+クリックして定義を見るとどうやらビットマスクを行うためのプロトコルになるそうです。ちょっと抜粋します。
A type that presents a mathematical set interface to a bit mask.
You use the
OptionSet
protocol to represent bit mask types, where
individual bits represent members of the set. Adopting this protocol in
your custom types lets you perform set-related operations such as
membership tests, unions, and intersections on those types. What's more,
when implemented using specific criteria, adoption of this protocol
requires no extra work on your part.
*意訳:OptionSetは数学上の集合を表現する型です。OptionSet
プロトコルを使用して、ビットマスクタイプを表し、それらを集合として表現できます。このプロトコルを採用することで論理和や論理積などの計算が楽に表現できます。
Swift2ですがOptionSetについて説明した記事があったので紹介します。
Swift 2.0 の新しいOption、OptionSetType
http://www.toyship.org/archives/2208
どうやら、iOSのアプリのフォルダ指定をビットマスクによって管理しているようです。
ビット自体の説明は以下の記事がわかりやすいです。
ビットをどう使うかよく判らない人へ
http://qiita.com/satoshinew/items/566bf91707b5371b62b6
userDomainMask等の型指定について
私も初めてこの形をみましたし、⌘+クリックの定義ファイルでは具体的な実装まではわからないのでなんとも言えないですが、
public static var userDomainMask: FileManager.SearchPathDomainMask { get }
と書いた時に結局userDomainMask
プロパティってOptionSet
プロトコルを採用してることになるんですよね。
「構造体SearchPathDomainMaskのプロパティ(userDomainMaskなど)に自身の型を指定しているのか?」が何故なのかですが、予想するに、FileManagerのextensionでプロパティを増やしたいからだと思います。
extension(拡張)では保持型のプロパティの追加はできないです。
ですが、ネストする形でまず構造体(ここではSearchPathDomainMask)を定義して、その構造体のプロパティを追加するとextensionでもプロパティを追加できます。
今Playgroundで確認しました。
swift
1class A {} 2extension A { 3 struct StructA { 4 static var nestValue : A.StructA { 5 get { 6 return A.StructA() 7 } 8 } 9 } 10 //var addNumber :Int ->保持型のプロパティをextensinoでは追加できない!! 11} 12 13A.StructA.nestValue//A.StructA型になる 14
上記のコードを実行するとA.StructA.nestValue
はA.StructA型
になります。
extensionってコードの可読性を上げるために、追加の機能は全てextensionで書かれることがあり、Foundationライブラリーはその書き方にならっているので、こんな書き方になっていると予想します。
【追記】 iOSのアプリのフォルダ指定をビットマスクによって管理するとはどうゆうことなのか?
ビットをどう使うかよく判らない人へ
ではビットフラグを作り、フラグを立てることで変数に複数の状態を表すことができると書かれています。
それを今回のSearchPathDomainMask例で提示します。
突然ですが、今すぐXcodeで言語をObjective-Cで新しいプロジェクトを作成して、ViewControllerのviewDidloadで以下のコードを書いてください
objective
1- (void)viewDidLoad { 2 [super viewDidLoad]; 3 //キャッシュディレクトリを取得する 4 NSArray *cachesArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSLocalDomainMask | NSUserDomainMask | NSNetworkDomainMask | NSSystemDomainMask , YES); 5 NSLog(@"キャッシュディレクトリ配列:%@", cachesArray); 6} 7
NSSearchPathForDirectoriesInDomains
関数の第一引数には検索するディレクトリを入れます。
今回はNSCachesDirectory
を指定してキャッシュディレクトリを取得します。
第二引数はドメインマスクを指定します。今回はNSLocalDomainMask | NSUserDomainMask | NSNetworkDomainMask | NSSystemDomainMask
とor演算をして、全て選択します。
これを実行するとこんな結果が得られると思います。
Objectie
1キャッシュディレクトリ配列:( 2 "/Users/user/Library/Developer/CoreSimulator/Devices/C89BA657-CB47-4142-ADAD-0278E32047CF/data/Containers/Data/Application/D0584736-5F37-4A87-87E4-83F29064D0E9/Library/Caches", 3 "/Library/Caches", 4 "/System/Library/Caches" 5)
NSLocalDomainMaskとして"/Users/user/Library/Developer/CoreSimulator/Devices/C89BA657-CB47-4142-ADAD-0278E32047CF/data/Containers/Data/Application/D0584736-5F37-4A87-87E4-83F29064D0E9/Library/Caches"
NSUserDomainMaskとして"/Library/Caches"
NSSystemDomainMaskとして"/System/Library/Caches"
の3つのディレクトリが取得できました。
さて、先程のソースのNSUserDomainMask
にカーソル当てて⌘クリックするとこんな定義に飛びます。
Objectie
1//NSPathUtilities.h 2typedef NS_OPTIONS(NSUInteger, NSSearchPathDomainMask) { 3 NSUserDomainMask = 1, // user's home directory --- place to install user's personal items (~) 4 NSLocalDomainMask = 2, // local to the current machine --- place to install items available to everyone on this machine (/Library) 5 NSNetworkDomainMask = 4, // publically available location in the local area network --- place to install items available on the network (/Network) 6 NSSystemDomainMask = 8, // provided by Apple, unmodifiable (/System) 7 NSAllDomainsMask = 0x0ffff // all domains: all of the above and future items 8}; 9
NS_OPTIONS
修飾子はObjective-CとSwiftを橋渡しする修飾子で、文法的にはC言語のenumになります。
(Objective-Cという言語は[]で囲われていない部分はC言語の文法で動く言語です。)
C言語のenumは数値に名前をつけられるものです。上記のソースでは「1という数値にNSUserDomainMask
という名前を2という数値にNSLocalDomainMask
を...以下略」としています。
なぜ2の階乗ごとの数値になっているかというと2進数で表した時に都合がいいからです。
この数値をビットマスクすることで複数のディレクトリを選択することができます。
例えば、最終的なNSSearchPathDomainMaskの値が2進数で
1001(イチゼロゼロイチ)
ならばNSUserDomainMask
(2進法で1桁目。10進法で1の数字)とNSSystemDomainMask
(2進法で4桁目。10進法で8の数値)が選択されていることがわかります。
2進数で各位に1があればそれが選択されているということが分かるのがビット演算で、NSSearchPathDomainMaskはそのようにしてディレクトリを選択しています。
因みに以下のように数値を直に記入しても同じ結果が得られると思います。C言語のenumは数値を置き換えたものだからです。
Objectie
1 //キャッシュディレクトリを取得する 2 NSArray *cachesArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, 1 | 2 | 4 | 8 , YES);//第二引数を1 | 2 | 4 | 8 にした。 3 NSLog(@"キャッシュディレクトリ配列:%@", cachesArray); 4
話をSwiftに戻します。
Swift2からOptionSetType
(Swift3でOptionSet
に改名)が追加されてこのビットマスク操作をインスタンスのメソッドによる操作でできるようになりました。
上記のObjective-CのコードをSwiftに置き換えてみます。
swift
1 var useMask = FileManager.SearchPathDomainMask.localDomainMask 2 useMask.insert(.userDomainMask) 3 useMask.insert(.networkDomainMask) 4 useMask.insert(.systemDomainMask) 5 6 let cachesArray = NSSearchPathForDirectoriesInDomains(.cachesDirectory, useMask, true) 7 print(cachesArray) 8
FileManager.SearchPathDomainMask.localDomainMask
で変数useMask
を作成し、insert
メソッドで追加をしています。ビット演算は使っていません。
ビット演算を直接やるかOptionSet
を採用してインスタンスによるメソッド操作に書き方にするかどちらがいいのかは議論があるところですが、少なくともAppleのFoundation開発者は「インスタンスによるメソッド操作に書き方」がいいと思ってOptionSet
で定義しているのだと思います。
SearchPathDomainMask
とは関係ないので完全に余談ですが、Swift3から「Swift2までC言語の関数をブリッチして呼び出していたものをインスタンスのメソッドによる操作に置き換える」方向でアップデートされました。
「インスタンスによるメソッド操作に書き方」の方が受け入れられつつあります。
[iOS][Swift] Swift 3.0の変更点まとめ
http://dev.classmethod.jp/smartphone/iphone/implemented_proposals_for_swift3/
のSE-0044: Import as Memberを参照してください。
投稿2017/01/10 15:56
編集2017/01/14 03:54総合スコア113
0
回答が文字数制限を超えてしまったため、別枠で回答します。ごめんなさい
#【追記】なぜextensinoでプロパティ追加にわざわざ構造体SearchPathDomainMaskを定義したのか?
Foundationの開発者がなぜわざわざpublic struct SearchPathDomainMask : OptionSet{}
とFileManagerクラスに構造体SearchPathDomainMaskをネストする形で定義したのかですが、
「可読性を上げる」が考えられると思います。
@hirddさんがおっしゃるように、extensinoではコンピュートプロパティでプロパティは追加できるので、以下のようにuserDomainMaskなどを定義することができます。
swift
1class AltFileManager {}//自分で定義したのでAltFileManagerと名前をつけました。 2extension AltFileManager {//exitensinでプロパティを追加 3 struct SearchPathDomainMask :OptionSet { 4 let rawValue: UInt 5 } 6 //exitensinでプロパティを追加 7 static var userDomainMask : SearchPathDomainMask { 8 return SearchPathDomainMask(rawValue: 1 << 0) 9 } 10 //exitensinでプロパティを追加 11 static var localDomainMask : SearchPathDomainMask { 12 return SearchPathDomainMask(rawValue: 1 << 1) 13 } 14 //exitensinでプロパティを追加 15 static var networkDomainMask : SearchPathDomainMask { 16 return SearchPathDomainMask(rawValue: 1 << 2) 17 } 18 //exitensinでプロパティを追加 19 static var systemDomainMask : SearchPathDomainMask { 20 return SearchPathDomainMask(rawValue: 1 << 3) 21 } 22} 23//使用例 24var loalDomain = AltFileManager.localDomainMask 25//追加もできる 26loalDomain.insert(AltFileManager.networkDomainMask) 27
今回は、AltFileManagerというクラスに構造体でSearchPathDomainMaskをOptionSetを採用する形で定義し、コンピュートプロパティとしてuserDomainMask、localDomainMask、networkDomainMask、systemDomainMaskを定義しました。
使用する場合は、AltFileManager.localDomainMask
などで各ドメインマスクを取得できます。
コンパイルも通ります。
動いてるならいいじゃないかと思いますが、ここはFoundationというiOSの根幹をなすライブラリーです。
利用する開発者が迷わないように定義するのが重要になってきます。
その考えて行くと、ドメインマスクがAltFileManager.localDomainMask
とFileManagerの直下にバラバラにドメインマスクがあるのは読みにくいです。
なぜなら、userDomainMask、localDomainMask、networkDomainMask、systemDomainMaskは4つで1つのビット演算をしようとしているからです。
ビットマスクで「2進数で4桁の数値を設定して1がある位を選択状態とみなす」と考えた時にバラバラのプロパティを定義して「userDomainMaskは2進数の1桁目を表します。localDomainMaskは2進数の2桁目を表します」とドキュメントに書くよりは、「SearchPathDomainMaskという構造体を定義し範囲の中でlocalDomainMask、userDomainMaskを定義」するほうがSearchPathDomainMaskというグループに区切ることができるので読みやすくなります。
userDomainMask、localDomainMask、networkDomainMask、systemDomainMaskは意味的にドメインマスクという同じグループに属しているので、それをまとめるSearchPathDomainMask構造体を定義しているのだと思います。
因みにObjective-Cの定義をもう一度確認しても、NSSearchPathDomainMaskの下に各ドメインマスクがあるという定義になっています。
NSSearchPathDomainMaskが各ドメインをまとめるものとして表現されています。
Objectie
1//NSPathUtilities.h 2typedef NS_OPTIONS(NSUInteger, NSSearchPathDomainMask) { 3 NSUserDomainMask = 1, // user's home directory --- place to install user's personal items (~) 4 NSLocalDomainMask = 2, // local to the current machine --- place to install items available to everyone on this machine (/Library) 5 NSNetworkDomainMask = 4, // publically available location in the local area network --- place to install items available on the network (/Network) 6 NSSystemDomainMask = 8, // provided by Apple, unmodifiable (/System) 7 NSAllDomainsMask = 0x0ffff // all domains: all of the above and future items 8}; 9
SwiftではSwift2までObjective-Cの定義をブリッチしていましたがSwift3では、ブリッチの方法を見直し、よりSwiftらしい書き方ができるように変更されています。
ということでSwift2での定義も確認してみました。
swift2
1public struct NSSearchPathDomainMask : OptionSetType { 2 public init(rawValue: UInt) 3 4 public static var UserDomainMask: NSSearchPathDomainMask { get } // user's home directory --- place to install user's personal items (~) 5 public static var LocalDomainMask: NSSearchPathDomainMask { get } // local to the current machine --- place to install items available to everyone on this machine (/Library) 6 public static var NetworkDomainMask: NSSearchPathDomainMask { get } // publically available location in the local area network --- place to install items available on the network (/Network) 7 public static var SystemDomainMask: NSSearchPathDomainMask { get } // provided by Apple, unmodifiable (/System) 8 public static var AllDomainsMask: NSSearchPathDomainMask { get } // all domains: all of the above and future items 9}
Swift2ではpublic struct NSSearchPathDomainMask : OptionSetType
とグローバルな構造体としてNSSearchPathDomainMask(swift3でSearchPathDomainMaskに改名)が定義されてました。(どこにもextensionされていないグローバルな構造体です。)
NSSearchPathDomainMaskの下にUserDomainMaskなどの各ドメインマスクが定義されてます。
NSSearchPathDomainMask > UserDomainMaskのツリー構造になっています。
Swift3でよりよくリファクタリングしようとしたときにこのツリー構造は壊したくないです。
そしてどのように変更されたのかを見ると
FileManager > SearchPathDomainMask > UserDomainMask
というように、FileManagerをトップとしてその下にSearchPathDomainMask、その下にUserDomainMaskとする構造に変更されました。
考えてみれば、SearchPathDomainMaskはファイルを扱う時に使用するので、グローバルで定義するよりFileManagerの下にあった方がわかりやすいです。
ここで質問の答えをしたいと思います。
「extensionでプロパティを増やしたいならなぜわざわざ構造体を定義したのか?コンピュートプロパティなど他のやり方があるのになぜ?」という質問には
「昔からSearchPathDomainMask > UserDomainMaskのツリー構造でObjcもSwift2も定義されていたので、バラバラに定義するのは不合理。Swift3でSearchPathDomainMaskがFileManagerの配下になり、FileManager > SearchPathDomainMask > UserDomainMaskというツリー構造になった。わざわざ構造体を定義した理由はFileManagerというファイルを扱うものの下にSearchPathDomainMaskを定義することで、構造を明確化し可読性を上げるため」
とお答えします。
#追記 OptionSetの使い方
公式サイトにoptionsetの使い方が書かれていました。
https://developer.apple.com/reference/swift/optionset
ECサイトなどで購入者の商品配送方法をオプションセットで表す例です。
swift
1struct ShippingOptions: OptionSet { 2 let rawValue: Int 3 4 static let nextDay = ShippingOptions(rawValue: 1 << 0)//次の日 5 static let secondDay = ShippingOptions(rawValue: 1 << 1)//二日後 6 static let priority = ShippingOptions(rawValue: 1 << 2)//優先的に 7 static let standard = ShippingOptions(rawValue: 1 << 3)//標準 8 9 static let express: ShippingOptions = [.nextDay, .secondDay]//特急オプション 10 static let all: ShippingOptions = [.express, .priority, .standard]//全部入り 11}
nextDayなどオプションをstaticで保持して、型ShippingOptionsになっているところ、SearchPathDomainMaskと同じですね。プロパティが自身の型になっているところが。staticで型プロパティにしているからできるんだと思います。
OptionSetはこんなふうに使うんですね。
私も勉強になりました。
参考にしてください!
投稿2017/01/14 03:32
編集2017/01/14 06:45総合スコア113
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
こちらのリンク内の記事を読むことをお勧めしますが、読んでばかりいると、プログラムソースをかけなくなるので、
まず動かしてみることも必要かと思いました。
プログラムを読む、プログラムを考える、プログラムを動かす、バランスですね。
投稿2017/01/13 13:01
総合スコア120
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
プロトコルはそのままだと思いますが、
Int32の値は4バイトですから、最小値のバイト数を求める場合はInt8の1バイトだと思います。
public staticで宣言されている変数達の型はこの構造体自身の型になっている??
ことに関して、モデルの構造体ではこのようにIntを宣言して、rawValueを使用しています。
この宣言を自分の用途に応じて対応すればよろしいかと。
let rawValue: Int32のような形で。
struct ShippingOptions: OptionSet { → let rawValue: Int → let rawValue: Int32 にする。 static let nextDay = ShippingOptions(rawValue: 1 << 0) static let secondDay = ShippingOptions(rawValue: 1 << 1) static let priority = ShippingOptions(rawValue: 1 << 2) static let standard = ShippingOptions(rawValue: 1 << 3) static let express: ShippingOptions = [.nextDay, .secondDay] static let all: ShippingOptions = [.express, .priority, .standard] }
投稿2017/01/10 11:10
総合スコア120
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
まず、SearchPathDomainMaskという構造体は、OptionSet型なのでしょうか??
プロトコルの実装です。
そして、public staticで宣言されている変数達の型はこの構造体自身の型になっている??
それが何か?
例えばInt32型の最小値:minプロパティはやっぱりInt32型です。
投稿2017/01/10 11:01
編集2017/01/10 11:01総合スコア13521
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
日本語訳の Appleリファレンスにかなり詳しい詳細が記載されています。
Appleリファレンス
投稿2017/01/10 10:22
編集2017/01/10 10:23総合スコア120
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/01/13 11:07
2017/01/14 01:27
2017/01/14 03:34