#author("2016-06-09T09:44:59+00:00","default:nitta","nitta") #author("2016-07-20T16:09:20+00:00","default:nitta","nitta") [[Swift]] *Swift/SwiftとObjective-Cをビルドする [#hbebec58] *SwiftとObjective-Cをビルドする [#hbebec58] **Objective-Cの情報をSwiftから参照する。 [#s6765542] 2つの言語のソースファイルを一体かして動作させる手順を mix and match と呼ぶ。 Objective-Cだけで作られたプロジェクトに Swift のファイルを追加したり、 Swiftだけで作られたプロジェクトに Objective-C のファイルを追加すると、 Xcode はユーザに 「Bridging Header を作成するかどうか」 を問い合わせる。 "モジュール"に対するBridging Header のファイル名は モジュール-Bridging-Header.h になる。 モジュール名にピリオドや空白、先頭に数字がある場合はアンダースコア(_)で置き換えられることがある。 Bridging Header は最初は空なので、 ユーザはまず Swift 側で参照すべき Objective-C のヘッダをimportしておく。 ここに記述されたヘッダファイル中の public なクラスは、アプリケーション内の どのSwiftプログラムからも参照可能となる。 ** Objective-CのヘッダをSwiftのインタフェースとして見る方法 [#s690d985] Objective-Cのヘッダの内容がSwiftのインタフェースとして表示される。 -Standard EditorからObjective-Cのヘッダを表示させる。 ++「そのビューの左上にある "Related Items" の 表示ボタンからメニューを表示させる」。または 「Objective-Cのヘッダを表示し、メニューで View -> Standard Editor -> Show Related Items を選ぶ」。 ++ 表示されたメニューから "Generated Interface" を選択すると、 - View -> Assistant Editor -> Show Assistant Editor としてアシスタントエディタを表示させてから、丈夫にあるプルダウンメニューを表示させ、 メニューにある "Geneerated Interface" を選択する。 ** Swiftの情報を Objective-C から参照する [#lc698e84] Objective-C のファイル内で "モジュール名-Swift.h"というヘッダファイルをimportする。 このヘッダファイルは Xcode がコンパイルするときに自動生成するファイルである。 Swift側とObjective-C側で定義情報が循環しないように気をつける必要がある。 Objective-Cのヘッダファイルには Swift の自動生成ヘッダファイルを import してはいけない。 すなわち、自動生成ヘッダファイルは実装ファイル (.m, .mm) にだけ import する。 自動生成ヘッダファイルには Swift が利用する Objective-Cの情報も含まれるので、 必要に応じて Bridging Header に含まれる情報をインポートする。 *Objective-Cのオブジェクトを使う。 [#m11d58ab] **Objective-CとSwiftのメソッドとイニシャライザ [#o099d9c7] Objective-CのイニシャライザをSwiftから呼び出せるインタフェースにする場合の変換ルール +Objective-Cのイニシャライザのメソッド名の先頭に init... または initWith... があれば取り除く。取り除いて残りがあれば、それをSwiftのイニシャライザにおける 第1引数の外部変数名にする。ただし、先頭は小文字にする。 残りがなければ、第1引数には外部変数名を使わない。 +Objective-Cのイニシャライザの第2引数以降にキーワードがあれば、 それをSwiftのイニシャライザにおける外部引数名にする。 +Objective-Cのイニシャライザの返り値に関するNull許容性の指定が --nonnullなら、通常のイニシャライザ(init)にする。 --nullableなら、失敗のあるイニシャライザ(init?)にする。 --それ以外は(指定のない場合も)、有値オプショナルを返すイニシャライザ(init!)とする。 // Objective-C の UIBarButtonItemクラスの例 NS_ASSUME_NONNULL_BEGIN - (instancetype) initWithImage: (nullable UIImage *) image style: (UIBarButtonItemStyle) style target: (nullable id) target action: (nullable SEL) action // Swiftのイニシャライザ init(image image: UIImage?, style style: UIBarButtonItemStyle, target target: AnyObject?, action action: Selector) // Objective-CのNSDataクラスの例 NS_ASSUME_NONNULL_BEGIN - (instancetype) initWithBytes: (const void *) bytes length: (NSUInteger) length + (instancetype) dataWithBytes: (const void *) bytes length: (NSUInteger) length // Swiftのイニシャライザ init(bytes bytes: UnsafePointer<Void>, length length: Int) Objective-Cのコンビニエンスコンストラクタ (ファクトリメソッド)がconvenience initializer に変換されるルール +Objective-C のクラスメソッドであって、 instancetype を返り値とし、 メソッド名の先頭がクラス名(先頭は小文字でよい)でなければならない。 先頭のクラス名を取り除き、さらに"With+文字列"の形式であった場合は "With" を取り除く。 残りがあれば、先頭を小文字にしてそれをSwiftの convenience initializer の第1引数の外部変数名にする。残りが無ければ、第1引数に外部変数名は使わない。 +クラスメソッドの第2引数以降のキーワードの扱いはイニシャライザのルールと同じ。 +クラスメソッドの返り値のNull許容性の扱いはイニシャライザのルールと同じ。 +convenience initializerのメソッド名を NS_SWIFT_NAMEマクロで指定できる。 この場合、ルール1のクラスメソッド名に関する制約はない。 // Objective-Cでのインタフェース @interface Member: NSObject - (nonnull instancetype) initWithName: (nonnull NSString *) who; + (nullable instancetype) memberWithJob: (Task) t; + (nonnull instancetype) workingMemberAs: (nonnull NSstring *) who NS_SWIFT_NAME(init(title:)); @end // Swift public class Member : NSObject { public init(name show: String) public convenience init?(job t: Task) public convenience init(title who: String) } **Objective-CのクラスをSwiftから使う例 [#o143987a] サンプルのプロジェクト名 Sample // MyRect.h #import Foundation; @interface MyRect: NSObject { // 2つの点の座標を持つクラス NSPoint origin; NSPoint upperRight; } - (nonnull instancetype) initWithOrigin: (NSPoint) orig upperRight: (NSPoint) upr; @property NSPoint origin; @property NSPoint upperRight; @end // MyRect.m #import "MyRect.h" @implementation MyRect - (instancetype) initWithOrigin: (NSPoint) orig upperRight: (NSPoint) upr { self = [super init]; origin = orig; upperRight = upr; return self; } @synchronize origin, upperRight; @end // Sample-Bridging-Header.h #import "MyRect.h" // main.swift import Foundation let orig = NSPoint(x:0.0, y:12.0) let ur = NSPoint(x:10.0, y:24.0) var rect = MyRect(origin:orig, upperRight:ur) print("Origin: \(rect.origin.x), \(rect.origin.y)") rect.origin.y += 1.5 print("Origin: \(rect.origin.x), \(rect.origin.y)") **Objective-C のプロパティに関する注意 [#u0bf201f] Objective-C では、getterメソッドとして解釈できる名前(getXXX)のメソッドはどれでも "オブジェクト.XXX"を使って読み出し専用のプロパティであるかのように扱うことができた。 また、getter, setter 両メソッドを定義してある場合は、読み書き可能のプロパティである かのように扱うことができた。 しかし、これはSwiftでは機能しない。 Objective-C の @property で宣言したものだけが Swift でプロパティとして利用できる。 **Objective-Cのクラスを継承する [#j33619c7] Swiftのクラス定義でスーパークラスとして Objective-C のクラスを指定できる。 この場合、イニシャライザではスーパークラスのイニシャライザを呼び出す必要があるが、 デイニシャライザの定義ではスーパークラスの dealloc メソッドを呼び出す必要はない。 Objective-C のプロトコルを Swift のクラスで利用することもできる。 // ARect.swift class ARect : MyRect { var area: Double { let w = upperRight.x = origin.x let h = upperRight.y = origin.y return Double(w * h) } } // main.swift import Foundation let orig = NSPoint(x:0.0, y:12.0) let ur = NSPoint(x:15.0, y:24.0) let rect = MyRect(origin: orig, upperRight: ur) print("Origin: \(rect.origin.x), \(rect.origin.y)") let a = ARect(origin:orig, upperRight:ur) print("Origin: \(a.origin.x), \(a.origin.y), Area: \(a.area)") **コマンドラインからコンパイルするには [#waa51f6a] $ clang -c -fmodules MyRect.m $ swiftc -c main.swift ARect.swift -module-name Sample -import-objc-header Sample-Bridging-Header.h $ swiftc -o ARect main.o MyRect.o ARect.o **Swiftに見せる情報を変更する [#n60cd1b4] 略 **エラー情報を返すメソッド [#f5aef37e] エラー情報を返す Objective-C のメソッドは、エラー情報を書き込む NSError 型のインスタンスへののポインタを最後の引数として渡すのが一般的である。 - (nullable instancetype) initWithContentsOfURL: (nonnull NSURL *) path encoding: (NSStringEncoding) enc error: (NSError **) err - (BOOL) writeToFile: (nonnull NSString *) path atomically: (BOOL) useAuxiliaryFile encoding: (NSStringEncoding) enc error: (NSError **) err convenience init(contentsOfURL url: NSURL, encoding enc: UInt) throws func writeToFile(_ path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws 変換のルール +Objective-Cのイニシャライザ、メソッドの最後の引数 (Error **型)を取り除き、 代わりに throws を置く。引数が1つだけの場合、...WithError: のような名前であれば、 他のイニシャライザ名、メソッド名と重複しない限り、"WithError:"の部分を取り除く などして簡単にする。 +メソッドが、成功/失敗をBOOL型で返すものだった場合、Swiftの関数の返り値はVoidにする。 +イニシャライザやメソッドが処理に失敗した場合に nil を返すものだった場合、 Swiftのイニシャライザやメソッドはオプショナル型ではない返り値にする。 +上記の2,3のどちらにも該当しない場合、NSErrorPointer型 (Error **型)の 引数が取り除かれずに残り、throwsは宣言されない。 Errorが発生した場合、catch節で捕捉されるエラー情報はプロトコル ErrorType に 適合した NSCocoaError 型で、Foundation で定義されている。 **セレクタを使ったメソッド呼び出し [#v7010307] Objective-C ではセレクタは SEL 型のデータとして表されるが、 Swiftでは対応する型として Selector 型が用意されている。 Selector 型を使ってメソッドを呼び出す仕組みは NSObject で定義されているので、 適用が可能なのは NSObject のサブクラスのインスタンスのみである。 Selector 型は構造体で、文字列を引数とするイニシャライザを持つ。 引数のないセレクタを呼び出す例 performSelector(_:)メソッドを使う let sel = Selector("say") obj.performSelector(sel) // 引数のないメソッドsel()を呼び出す obj.performSelector("say") // Stringでセレクタ名を与えても動作する。 引数がある場合は performSelector(_:withObject:)を使う。 引数は NSObject を継承したクラスのインスタンスでなければならない。 あるセレクタに対応しているか調べるにはメソッド respondsToSelector(_:) を、 あるクラスかそのサブクラスのインスタンスであることを調べるには メソッド isKindOfClass(_:) を使う。 import Foundation class Person : NSObject { let name: String init(_ s: String) { name = s; super.init() } func say() { print(name + "です") } } class Player : Person { func greet(p: Person) { print("こんにちは、" + p.name + "さん。私は" + name + "です。") } } let a = Player("Alice") let b = Person("Bob") a.performSelector("say") // Aliceです。 と表示される let sel = Selector("greet:") // 引数がある場合はセレクタは":"が必要 if a.respondsToSelector(sel) && b.isKindOfClass(Person) { a.performSelector(sel,withObject:b) // こんにちはbobさん。私はAliceです。 と表示される } 「引数があるのでセレクタ名に ":" がついている」ことに注意。 **セレクタを使ったメソッド呼び出しから返り値を得る [#ob3648ba] func performSelector(aSelector: Selector) -> Unmanaged<AnyObject>! func performSelector(aSelector: Selector, withObject object: AnyObject!) -> Unmanaged<AnyObject>! Unmanaged<AnyObject> は ARC で管理していないインスタンスであることを表わしている。 ただし、ARCの管理下にある通常のメソッドが返した値であれば特別な扱いをする必要はない。 import Foundation class Food: NSObject { let name: String init(_ s: String) { name = s; super.init() } } class Fruit : Food {} class Juice : Food { deinit { print("deinit: " + name) } } class Maker : NSObject { func makeJuice(f: Fruit) -> Juice { return Juice(f.name) } } let a = Juice("pear") let maker = Maker() let sel = Selector("makeJuice:") if maker.respondsToSelector(sel) { let b = maker.makeJuice(Fruit("orange")) let c = Fruit("peach") let d = maker.performSelector(sel,withObject:c).takeRetainedValue() print(d.name) // インスタンスは受け取り側で管理する } 実行結果 peach deinit: peach deinit: orange 実行結果からif文のコードブロックから抜けたとき、英数 b と d に格納されていた インスタンスが解放されていることがわかる。 すなわち、インスタンスを takeRetainedValue()で取り出せば 通常の関数呼び出しで 得られたインスタンスと同じ扱いができるということである。 もしもここで takeUnretainedValue() を使った場合は、 dに格納されているインスタンスはif文のコードブロックから抜けても 解放されない。 *Swift のクラスを Objective-C で使えるようにする [#x4b83c97] 略 *GUI 部品との連携 [#f7e3148f] **ターゲット&アクション・パラダイム [#p2b4c7a7] 略 **Cocoaバインディング [#pff499e9] 略 *文書化コメント [#e141b433] // TODO: ここにメモを記述する。「TODO: メモ」の形式で表示される。 // FIXME: ここにメモを記述する。「FIXME: メモ」の形式で表示される。 // MARK: ここにメモを記述する。「メモ」だけが表示される。 軽量マークアップ言語である Markdown の記法を使ってコメントを処理する。 文書化コメント (documentation comment) とは次の2種類。 - /** から始まるブロック型のコメント、 - /// で始まる行末までのコメント その中で使える記法は次の通り。 +段落~ 段落は空白行で区切る。文中の改行は取り除かれる。 他の段落よりも空白4個分右にインデントした段落は、ソースコードを引用したものと見なされ、 グレーの長方形で囲んで表示される。 あるいはバッククォート3個 "```" で前後の行を挟んだ段落を引用部分にできる。 +既定の項目~ いくつかのキーワードは、箇条書きとして関数の使い方に関する情報を記述できる。 --parameter 引数: 引数に関する説明。~ 記述方法は2種類ある。 -parameter price: 単価 -parameter quantity: 個数 -parameters: -price: 単価 -quantity: 個数 --returns: 返り値に関する説明。 --throws: エラーが投げられる場合の説明。 --Complexity: 計算量をオーダー記法で記述 --Precondition: 事前条件 --Postcondition: 事後条件 --Required: 必要事項 --Node: コメント --Warning: 警告 +見出し~ 行の先頭に # をいくつか連続して置くことで、その行を太字で表示できる。#の数が少ないほど大きい見出しとなる。 +箇条書き~ 段落の先頭に -, +, * を置くと箇条書きにできる。 数字とピリオドを続けたものを置くと、番号付きの箇条書きにできる。 段落の先頭に4個異常の空白を置いてインデントすることで、箇条書きのネストを作ることができる。 +強調~ 文字列を太字にするには "**文字列**" または "__文字列__" のように記述し、 斜体にするには "*文字列*" または "_文字列_" のように記述する。 ソースコードの記述に使うフォントにするには バッククォートを使って"`文字列`" と記述する。 +水平線~ 3個以上連続するハイフン --- 、またはアスタリスク *** は水平線となる。 +リンク~ [リンクの文字列](リンクアドレス"リンクの文字列")