僕がSwiftを使っていて不便だなって思うことの1つに、
OptionalなBool型の条件判定の仕方が面倒というのがあります。


特に、Optional Chainingを使った場合なんかによく遭遇します。 例えば、ログイン中のユーザーがプレミアム会員かどうか判定するときに、

// ログインユーザーが存在して、プレミアム会員の場合という条件式
if UserManager.shared.loggedinUser?.isPremium ?? false {
    print("Premium user!!")
}

こんな感じでログイン中ならuserオブジェクトが、非ログインだとnilがloggedinUserに格納される場合なんかに、
loggedinUserがOptionalな型であることでこのなんともいえない条件判定式が出来上がってしまいます。
慣れればどうってこともないのですが、

  • 毎回Optional Bidingで?? falseを付けないといけない
  • この条件の否定を取ろうとすると、!(条件式)と、カッコで括る必要がでてくる

といったことが挙げられて、あまりよろしくない…。
ので、 Custom Operator を駆使してこの面倒な点を解消してみようと思います。

Custom Operatorを定義してみる

postfix(後置演算子)として、|?を定義してみます。

postfix operator |? {}

postfix func |?(b: Bool?) -> Bool {
    return b.map { $0 } ?? false
}

こうすることで、OptionalなBoolが、アンラップできて且つtrueなら true を、falseまたはnilなら false を返すことができるようになります。
これを用いると、

if UserManager.shared.loggedinUser?.isPremium|? {
    print("Premium user!!")
}

とすっきりかけます!また、

// "プレミアム会員ではないまたはログインユーザーが存在しない場合"という条件式
if !UserManager.shared.loggedinUser?.isPremium|? {
    print("No premium user or no logged in user.")
}

このように、否定を撮った場合も、先に演算子|?が適応されるので、その結果を受けて、演算子!(論理NOT)が適応されます。

これで少しは扱いが楽になった気がします。

余談1: アンラップできて且つfalseの時をtrueにしたい場合

先ほどの例だと、|?で評価したものの否定を取ると、 falseまたはnil が当てはまることになります。
状況によっては、 アンラップできて且つfalseの時のみtrue(真) で、 アンラップできて且つtrueまたはnilの時はfalse として扱いたい場合があるかもしれません。
そんなときは、

postfix operator |!? {}

postfix func |!?(b: Bool?) -> Bool {
    return b.map { !$0 } ?? false
}

こんな感じで演算子を定義してあげることで、叶えることができます。

// "ログインユーザーが存在して、プレミアム会員ではない場合"という条件式
if UserManager.shared.loggedinUser?.isPremium|!? {
    print("No premium user.")
}

余談2: 最初書いててボツにしたもの

実は、だいぶ前からこの問題に悶々としていて、当初は

func isTrue(flag: Bool?) -> Bool {
    return flag ?? false
}

func isFalse(flag: Bool?) -> Bool {
    return !(flag ?? true)
}

func isFalseOrNil(flag: Bool?) -> Bool {
    return !isTrue(flag)
}

みたいな感じでグローバルな関数を用意していたのですが、なんとなく Swiftっぽくない と思っていて実装を断念していました。