2017年03月21日

[Swift2]NSCalendarUnitの合成


NSDateから年・月・日などの数字だけを抜き出すときにNSDateComonentsを使うんだけど、Swiftでちょっと書き方が変わった部分がある。

Objective-C時代はこんな書き方でした。リファレンスからの抜粋。


unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDate *date = [NSDate date];
NSDateComponents *comps = [gregorian components:unitFlags fromDate:date];


これがSiwft2では以下のようになるとリファレンスには書かれている。これだとObjective-Cとさほど代わりはない。


let flags: NSCalendarUnit = .DayCalendarUnit | .MonthCalendarUnit | .YearCalendarUnit
let date = NSDate()
let components = gregorian.components(flags, fromDate: date)


ところがこれを Xcode 7.2.1 で書くと、エラーになってしまってコンパイルできない。


Type of expression is ambiguous without more context


型が分からないというなら直接的に指定するしかないと指定する。


let flags2: NSCalendarUnit = NSCalendarUnit.Day | NSCalendarUnit.Month | NSCalendarUnit.Year


が、するとまた別のエラーになる。


Binary operator '|' cannot be applied to two 'NSCalendarUnit' operands'



じゃあ、一体どう指定するんだよ?と強引に指定するとこんな風になる。


let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let flags = NSCalendarUnit(rawValue: NSCalendarUnit.Year.rawValue
+ NSCalendarUnit.Month.rawValue
+ NSCalendarUnit.Day.rawValue
)
guard let comps = calendar?.components(flags, fromDate: date) else {
return
}
let year = comps.year, month = comps.month, day = comps.day


確かにこれだと正常に実行できるんだけど、あまりにも強引すぎてもっときれいな書き方があっていいはずなんだけど。

Swift3ではこんな書き方になる(クラス名等はSwift2のまま)。


let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
guard let comps = calendar?.components([.Year,.Month,.Day], fromDate: date) else {
return
}


そしてこれがまたコンパイルが通って正常に実行される。Swift3ではメソッドがSetで定義されているのでこれでもいいのだが、Swift2ではcomponents(flags:NSCalendarUnit, fromDate: NSDate)とNSCalendarUnitで定義されているので[NSCalendarUnit]で渡して渡せる理由がわからない。

そこでこんな書き方をしてみたら、ようやくなぜだかわかった。


let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let flags = NSCalendarUnit([.Year,.Month,.Day])
guard let comps = calendar?.components(flags, fromDate: date) else {
return
}


リファレンスとかからはわからないけどNSCalendarUnitのコンストラクタに配列を取るものがあるってことなんでしょう。

posted by 永遠製作所 at 15:16| 東京 ☔| Comment(0) | TrackBack(0) | Swift | このブログの読者になる | 更新情報をチェックする

2017年03月20日

[macOS, Swift2] ファイル選択ダイアログ

未だに古い環境のOS Xでプログラミングしてるんだけど、その中の基本的な操作であるファイルオープンダイアログの使い方を一応覚え書きとして。

NSOpenPanelはObjective-C時代そのまま。インスタンスはそのままコンストラクタで作る。シートでダイアログを開いて、結果はブロックで受け取る。OK/Cancelは NSFileHandlingPanelOKButton / NSFileHandlingPanelCancelButton で判定。

以下はNSViewControllerのサブクラス中で使う場合。


func showOpenPanel() {
let panel = NSOpenPanel()
panel.beginSheetModalForWindow(self.view.window!) {
[weak self] (response) -> Void in

switch ( response ) {
case NSFileHandlingPanelOKButton:
let urls = panel.URLs
self?.readFromURL(urls[0])
break
default:
return
}
}
}


シートを表示している最中だからselfがなくなることはないと思うけど、一応ブロック中でselfを使う場合は習慣化させたいので weak self 。

ファイルを1つだけ選択する場合。NSURLで受け取るのが現代的。複数選択する場合やディレクトリ可の場合には beginSheet... の前に下記コードを入れておくだけ。


self.allowsMultipleSelection = true
self.canChooseDirectories = true


posted by 永遠製作所 at 00:07| 東京 ☀| Comment(0) | TrackBack(0) | Swift | このブログの読者になる | 更新情報をチェックする

2016年03月24日

Swift: conformProtocol()、respondsToSelector:


Objective-Cでプロトコルに対応しているかどうかを判定するconformProtocol関数や、メソッドを持っているかどうかを調べるrespondsToSelectorのSwiftでの代替について。

ただし、Objective-Cとは違いSwiftでは静的な型の決定を行っているのでほとんどがコンパイル時に決定でき、動的に判定することはない。というかそのような使い方は避けるべきと考えられる。

ここではごく基本的なものを示す。個別の事情によりまた別の判定方法を使った方がよいことがあるので、それらはいつか実際の事例毎にまとめたい。
続きを読む
posted by 永遠製作所 at 00:47| 東京 ☀| Comment(0) | TrackBack(0) | Swift | このブログの読者になる | 更新情報をチェックする

2016年03月22日

Swift: クラスの判定


Objective-Cでクラスを判定してなんらかの処理をする場合を、Swiftで実現するのはどうするのか。例えば、isKindOfClass: とか、isMemberOfClass: とかの処理。

「if ( [object isKindOfClass:[Root class]] )」は、そのオブジェクトが、Rootクラスのインスタンスかまたはそのサブクラスのいずれかのインスタンスであれば真になる。もし、判定後にそのクラスとしてなんらかの操作を行うのであれば、キャストして代入すればよい。「Sub1 *sub = (Sub1*)object;」のようにする。

一方、そのインスタンスそのものである時だけ真にしたい場合には、「isMemberOfClass:」を使う。
続きを読む
タグ:クラス Swift
posted by 永遠製作所 at 12:12| 東京 ☀| Comment(0) | TrackBack(0) | Swift | このブログの読者になる | 更新情報をチェックする

2016年03月21日

Swift: クラス名を取得する


SwiftのクラスにはObjective-CのNSObjectにあったclassNameプロパティのようなものがないので、文字列でクラス名を取得するには工夫が必要。

Objective-Cのメソッドを使ってNSStringFromClass(String)のようにする。これを実行するにはFoundationをインポートする必要がある。
※Stringは構造体であってクラスではないが、Swiftでは構造体やクラスはタイプという上位概念で包含され様々な点で似た振る舞いをする。

ただし、独自に定義したクラスではランタイムモジュールを含めた修飾が定義したクラス名の前につくのでこれを削除する必要がある。結局、以下のようになる。

import Foundation

func className(type: AnyClass) -> String {
print("AnyClass : ", terminator:"" )
// print( NSStringFromClass(type) ) // "__lldb_expr_198.Root"
return NSStringFromClass(type).componentsSeparatedByString(".").last!
}

class Root { }
className( Root ) // "Root"


単にクラス名を出力するだけであればprint( Root.self )のようにしても"Root"という結果が得られるのだが、これはprintメソッドが内部でクラス名文字列を取り出す処理を行っているだけのようだ。文字列として取り出して使うことはできない。

インスタンスからそのクラス名を取り出すには、同様に出力だけならprint( Root() )でできる。が、ちゃんとするには先ほどのメソッドに Root().dynamicType を渡すとよい。

func className(o: AnyObject) -> String {
print("AnyObject : ", terminator:"" )
return className(o.dynamicType)
}

let root = Root()
className(root)

続きを読む
タグ:Swift クラス
posted by 永遠製作所 at 01:16| 東京 ☀| Comment(0) | TrackBack(0) | Swift | このブログの読者になる | 更新情報をチェックする