Swift/C&Objective-Cとのデータの受け渡し †データ型の互換性 †C言語の単純型とSwiftの型 †
C援護の関数 pow, sqrt の例(Foundationのモジュールのimportが必要) func powf(_: Float, _: Float) -> Float func pow(_: Double, _: Double) -> Double func sqrtf(_: Float) -> Float func sqrt(_: Double) -> Double C言語のポインタとSwiftの型 †APIを経由してひとまとまりのデータの受け渡しを行う場合は、 Swiftで配列を用意しておき、その配列への「ポインタ」をCの関数に渡して 値を読み書きしてもらう方法が一般的。 ポインタには nil を代入でき、演算子 ==, != を用いてポインタとnilと比較することもできる。
型の名称に Unsafe がついているのはメモリ管理を ARC で行っていないことを表している。 これらを Unsafe Pointer と呼ぶことにする。 Mutable は、ポインタが指すメモリ領域に書き込んで変更できることを表す。 変数を用意して関数の結果を書き込む †func time(_: UnsafeMutablePointer<time_t>) -> time_t func localtime_r(_: UnsafePointer<time_t>,_:UnsafeMutablePointer<tm>) -> UnsafeMutablePointer<tm> import Darwin var current = time(nil) // current は time_t 型になる ポインタには「変数のポインタを渡す」場合と「配列の先頭要素を渡す」場合がある。 変数のポインタを渡す場合は、実引数として "&" をつけた変数を記述する(inout 演算子がついた引数に渡す場合と同様)。 var current: time_t = time_t() // 変数を用意して初期化 time(¤t) // 変数 current に値が書き込まれる import Darwin // Foundation でもよい var current : time_t = time(nil) // 現在時刻を time_t 型で取得 var tnow = tm() // 変数を用意して初期化 localtime_r(¤t, &tnow) // 変数 tnow に情報が書き込まれる print("\(tnow.tm_mon + 1)月\(tnow.tm_mday)日") // 本日の日付が表示される 配列を用意して関数の結果を書き込む †time_t型データから、日時を表す文字列を生成する ctime_r 関数を使ってみる。 第1引数は time_t型へのポインタで、第2引数が文字列領域へのポインタである。 C 言語の char 型は Swift では Int8 型なので 文字列領域へのポインタは UnsafeMutablePointer<Int8> となる。 func ctime_r(_: UnsafePointer<time_t>, _: UnsafeMutablePoiner<Int8>) -> UnsafeMutablePointer<Int8> 配列のポインタを関数に渡す場合、使い方は次の2通り。
import Darwin var buf = (Int8)(count: 26, repeatedValue: 0) // 26個の0 var current: time_t = time(nil) ctime_r(¤t,&buf) // 文字列がbufに得られる puts(buff) // 現在時刻が表示される // puts(&buff) // &をつけても同じ結果が得られる 変数bufの値は Swift の String 型ではないので、表示するのに print 関数は使えない。 C言語の puts を使うことはできる。 putsではポインタの内容を変更しないので、putsに与える実引数には & をつけても つけなくてもどちらでも同じ動作となる。 var tt = [time(nil) ] // time_t 型の配列を作り、第1要素を初期化する ctime_r(tt,&buf) // &tt でも動作する ポインタへの値の渡し方
C言語ポインタ引数に関するルール †Swift でC言語の fopen関数を使ってファイルをオープンするコード let fpin = fopen(path,"r") // 引数はSwiftの String 型 関数 fopen の Swift インタフェース func fopen(_: UnsafePointer<Int8>, _: UnsafePointer<Int8>) -> UnsafeMutablePointer<FILE> String型と UnsafePointer<Int8> 型は互換ではない。 C言語のAPIを用いてSwiftを動作させるために、ポインタ引数については特別ルールがある。
このルールにより Swift の String 型を C 言語の文字列のように渡すことができているが、 逆はできない。 引数として UnsafePointer<T> を受け取るC言語の関数に UnsafeMutablePointer<T> を渡しても動作する。しかし、 Swift では UnsafeMutablePointer<T> と UnsafePointer<T> は異なる型なので、 型の変換を明示する必要がある。 let p: UnsafeMutablePointer<Int8> = /* 何かの値 */ let q: UnsafePointer<Int8>(p) // ポインタの型を変換する let z: UnsafePointer<Int8> = p // エラー。Swiftではこの代入はできない。 unsafe pointer 間での相互変換は自由にできる。 つまり、T型とU型が無関係であっても UnsafePointer<T> を UnsafePointer<U> に、 UnsafeMutablePointer<T> を UnsafeMutablePointer<U> に変換できる。どちらの型も2つのイニシャライザを持っている。 init<U>(_ from: UnsafeMutablePointer<U>) init<U>(_ from: UnsafePointer<U>) 型パラメータ U には何の制限もないので、次のような変換も可能である。 let p: UnsafePointer<Int8> = /* char 型を読み出し可能な何らかのポインタ */ let q = UnsafeMutablePointer<Int32>(p) = /* 整数値を書き込み可能なポインタ */ 渡されたポインタから値 を取り出す †リエントラント(再入可能)でスレッドセーフな関数は名前の末尾に _r がついている。 元々 Unixで用意されていた関数は localtime, ctime でありリエントラントではない。 これらを Swift から利用してみる。 func localtime(_: UnsafePointer<time_t>) -> UnsafeMutablePointer<tm> func ctime(_ UnsafePointer<time_t>) -> UnsafeMutablePointer<Int8> 3種類の unsafe pointer は Swift では構造体として定義されており、 ポインタの指す内容にアクセスするプロパティ memory と、 参照しているメモリを先頭から配列のように参照できる添字付けの機能を提供している。 // UnsafeMutablePointer<T> 型の場合 var memory: T { get nonmutating set } subscript (i: Int) -> T { get nonmutating set } UnsafeMutablePointer<T> は C 言語の (T*) 型に相当する。 これらの型をもつ変数 p の値にアクセスするには、C言語では *p と、 Swift では p.memory と記述する。 ポインタの位置からデータ n 個分先のデータにアクセスするには C言語でもSwiftでも p[n] と記述する。 // unsafe pointer に直接アクセスする例 import Darwin var current = time(nil) let jnow: tm = localtime(¤t).memory // 構造体をコピーして代入する print("\(jnow.tm_mon + 1)月\(jnow.tm_mday)日") let p = ctime(¤t) // pは UnsafeMutablePointer<Int8>型 for i in 0..<25 { // 添字を使って文字にアクセスする例 putchar(Int32(p[i])) // putcharの引数はint型(Int32型) } // 最後の文字は改行文字 var ptr = p // ポインタを進めながら文字にアクセスする例 for i in 0..<25 { putchar(Int32(ptr.memory)) // ポインタの指すデータを表示する ptr = ptr.successor() // ポインタを1つ進める } Swift ではポインタを直接変数に代入することはできないので、定数jnow には tm 型の構造体の コピーが代入される。 ポインタから文字列への変換 †String型のタイプメソッドを使う。 static func fromCString(_: UnsafePointer<CChar>) -> String? let p = ctime(¤t) // pはUnsafeMutablePointer<Int8>型 if let s = String.fromCString(p) { // オプショナル束縛構文 print(s) } Foundation を import している場合、String型に対して次のイニシャライザが利用可能である。 文字列が UTF-8 でなかった場合は nil が返される。 init?(UTFString: UnsafePointer<CChar>) コマンドラインの引数にアクセスする。 †コマンド引数にアクセスするために、列挙型 Process が定義されていて、 タイププロパティを使って引数リストにアクセスできる。 static let arguments: [String] static var argc: CInt { get } static var unsafeArgv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>> { get } var i = 0 for elm in Process.arguments { print("\(i++): \(elm)") } UnsafeBufferPointer<T>を利用する †unsafe pointer には添字でアクセスする方法以外に、 プロトコル CollectionType に適合している 構造体 UnsafeBufferPointer<T> を利用する方法もある。 uptr: UnsafePointer<Int> = /* 略 */ let array = UnsafeBufferPointer<Int>(start: uptr, count: 100) // 先頭から100個分のInt型 T**型の引数の扱い †
対応付けられたデータ型 †数値と数値オブジェクト †Objective-C の NSInteger と NSUInteger はどちらも Swift の Int 型に対応付けられる。 Objective-C のインタフェースで NSNumber のインスタンスが使われる部分には Swift の Int, UInt, Float, Double, Bool 型のインスタンスがに 対応付けられる。 var n: NSNumber = 10.25 print(n as Float) // 10.25 が表示される print(n as Int) // 10 が表示される オブジェクト †Swift には AnyObject? 型があり、任意のクラスのインスタンスに対応付けられる。 Foundation を import してあれば、Objective-C のクラスに対応付けられた型は 全て Swift の AnyObject? に対応付けることが可能である。 C++ のオブジェクトは Swift では利用できない。 文字列 †Swift のString 型と Objective-C のNSString型は 対応付けられている。 String型には NSString のメソッドを適用できるが、逆はできない。 var s: String = "protects the knowledge of the strong" print(s.rangeOfString("owl")!) // 15..<18 が表示される。 s = "クロネコ\u{1F431}" // 顔文字を含む print((s as NSString).length) // 6 が表示される var m = NSMutableString(string: "小鳥\u{1F426}") s = m as String // var mm = s as NSMutableString // エラー。このキャストは不加能。 NSString型は UTF16 を用いて Unicode を扱っているため、 顔文字などが含まれる文字列の長さを1つずつ多く間違えてしまうという問題がある。 NSStringのほとんどの機能は Foundation を import すれば String 型から使えるので NSString型に変換して機能を利用する必要はない。 Swift の String 型と Objective-C の NSMutableString 型は対応付けられていないので、 互いに自由に変換はできない。 ただし、NSMutableString型は NSString型のサブクラスなので、String型に変換することはできない。 NSString はクラスクラスタなので、実際の型が NSString であるとは限らない。 配列と集合 †Swift の Array 型と Objective-C の NSArray 型は対応付けられており、 NSArray 型で型パラメータを指定しない配列オブジェクトは Swift の [ AnyObject ] という型に対応付けられる。 let ns1: NSArray = [ 152.0, 4, UInt(1) ] let ar1 = ns1 // [ AnyObject ] 型になる let ar2 = ns1 as! [Int] // [ Int ] 型にできる print(ar2) // [ 152 4 1 ] が表示される let dat:[Int] = [ 161, 152, 160, 153 ] let ns2: NSArray = dat // NSNumber* の配列になる as! で型変換した場合、キャストできない要素があると実行時エラーになる。 as? で型変換した場合は、キャストできない要素があると nil が返される。 Objective-C から渡された [ AnyObject ]型の配列の要素がそれぞれ異なる型の場合、 たとえば文字列だけを取り出したい場合は次のようにクロージャを利用する。 var array: [ AnyObject?] = /* NSArray型 */ let strs = array.filter{ $0 is String } 集合型では、 Swift の Set 型と Objective-C の NSSet 型が対応付けられている。 Swiftの Set 型の要素はプロトコル Hashable に適合している必要がある。 辞書 †Swift の Dictionary 型と、Objective-C の NSDictionary は対応付けられている。 型パラメータを指定しない場合 Swift の [ NSObject : AnyObject ] という型となる。 let info: NSDictionary = [ "Height" : 161.0, "Grade" : 2, "Level" : 5 ] let pinfo = info as! [String:Int] // このキャストは可能。 print(pinfo) // [ "Height": 161, "Grade" : 2, "Level" : 5 ] が表示される 一般に Objective-C から渡される辞書には複数の型が含まれているので、 ダウンキャストには as? 演算子を用いるか、要素毎に個別に対応した方がよい。 for (key, value) in info { if let k = key as? String, v = value as? Int { // [String:Int]な要素だけを扱う print(k + ": ", v) } } または for enry in info { if case let (k as String, v as Int) = entry { // (String, Int)だけを扱う print(k + ": ", v) } } NSValue と CoreGraphics? の構造体 †CFLoat型はDoubleの場合とFloatの場合があるので、 Objective-Cでは自動型変換で大丈夫であっても、 Swiftではエラーとなることがある。 イニシャライザを使って明示的に型変換を行うとよい。 var xp: CFLoat = CGFloat(sin(th)) // sin() の返り値はDouble var x2: Double = Double(xp * xp) CoreGraphics で定義されている構造体
Swiftでは Objective-C で使われている CGPoint, CGSize, CGRect 構造体をそのまま 利用できる。 NSData †Objective-C の NSData に対応づけられた Swift の型はないので、 [Int8] 型、または [UInt8] 型でバイト列のデータをやりとりする。 Objective-C から受け取ったNSDataからデータを読み出すには、プロパティ bytes か、 メソッド getBytes を使うとよい。 var bytes: UnsafePointer<Void> { get } func getBytes(_: UnsafeMutablePointer<Void>, length: Int) Swift で NSData のインスタンスを作ることができる。2番目のイニシャライザはデータをコピーせず共有する。 init(bytes: UnsafePointer<Void>, length: Int) init(bytesNoCopy: UnsafePointer<Void>, length: Int) データのバイト数を調べる †Swift では、型名を指定してバイト数を得る sizeof() 関数、 変数や値を指定してそのバイト数を得る sizeofValue() 関数。 sizeof(Int) // 8 (64bitマシンの場合) sizeof((Int8,Int8)) // 2 sizeofValue(3.14) // 8 パディングを考慮した値を得るには strideof() と strideofValue() 関数を使う。 strideof(Float80) // 16 strideofValue(["Cinderella":346]) // 8 Core Foundation †Foundation を import すると Core Foundation も import されている。 Core Foundation のオブジェクトを表す型名は CFArrayRef , CFURLRef のように 参照型であることを示す Ref がついているが、Swiftでは 「Ref のある型名」も「Refのない型名」も両方使える。 Core Foundation の CFTypeRef? 型は Swift の AnyObject? 型として扱われる。 Core Foundation の型には toll-free (型変換なしにObjective-Cで相互利用できる) なものがあるが、これは Swift でも利用できる。
列挙型とその他のデータ †列挙型 †C言語やObjective-Cにおける列挙型は整数型とあまり明確な区別はなかったが、 次のどの形式で定義されたかによって Swift からの見え方が異なる。
型名のある列挙型 †enum AIMAbility { AIMAccelerator = 1, AIMDarkMatter, AIMElectromaster }; Swift向けのインタフェースは次のような構造体として定義される。 プロトコル RawRepresentable? はこの型が実体を持つことを表わし、 プロパティ rawValue が各要素の値を返す。 public struct AIMAbility : RawRepresentable, Equatable { public init(_ rawValue: UInt32) public init(rawValue: UInt32) public var rawValue: UInt32 } public var AIMAccelerator : AIMAbility { get } public var AIMDarkMatter : AIMAbility { get } public var AIMElectromaster : AIMAbility { get } 型名のない列挙型 †enum { Male, Female } この場合、Swiftの宣言ではメンバ名自体が Int 型の定数になり、構造体は構成されない。 public var Male: Int { get } public var Female: Int { get } マクロ NS_ENUM で定義された列挙型 †略 マクロ NS_OPTIONS で定義された列挙型 †略 マクロ定義 †CやObjective-Cで定義された定数値を表す単純なマクロは、 Swift ではグローバルな定数として表現される。 #define WLRegulation 1.048596 // Objective-Cのマクロ定義 let WLRegulation = 1.048596 // Swift のグローバルな定数 引数を持つ関数マクロは、Swiftには情報として取り込まれることはない。 共用体とビットフィールド †Swift では、通常の方法ではC言語の共用体のようなデータ構造は定義できないが、 そのようなデータにアクセスしたり、新しいインスタンスを生成したりすることは可能である。 typedef union IntChars { // C言語における共用体の定義 int i; unsigned char c[4]; } IntChars; public struct IntChars { // 上記の共用体に対応する Swift のインタフェース public var i: Int32 public var c: (UInt8, Uint8, Uint8, Uint8) public init(i: Int32) public init(c: (UInt8, Uint8, Uint8, Uint8)) public init() } ビットフィールドについても同様である。 struct WNumber { // C言語におけるビットフィールド unsigned int flag:1 // 1 bitのみのフィールド unsigned int pad:7, // 7 bitのみのフィールド IntChars data; // 共用体 } public struct WNumber { // 上記のビットフィールドに対応するSwiftのインタフェース public var flag: UInt32 public var pad: UInt32 public var data: IntChars public init() public init(flag: UInt32, pad: UInt32, data: IntChars) } 各国語対応の文字列を得る †Foundationの関数NSLocalizedString?()を利用する。 func NSLocalizedString(key: String, tableName: String? = default, // 省略時は Localizable.strings bundle: NSBundle = default, // 省略時は メインバンドル value: String = default, // 省略時は nil comment: String) -> String 例 let mesg = func NSLocalizedString("Welcome", comment:"起動メッセージ") 日本語リソースの Localizable.strings に "Welcome" = "ようこそ" という 登録があれば、日本語を取り出せる。 関数ポインタ †Objective-C の関数へのポインタは、Swiftでは @convention(c) T として扱う。Tは関数を表す型で、たとえば「()->Int32」。 @convention(c) 属性によって、 「この関数がC言語の関数へのポインタであり、 Swiftのクロージャのように環境から値をキャプチャすることはない」 ことを示している。 C言語のAPIに関数ポインタとして渡すことができるのは、
void qsort(void *base, size_t nel, size_t width, int(*compar)(const void *, const void *)); typealias Episode = (String, Int) var buf:[Episode] = [ ("Parade",10), ("Baltazar", 5), (Pacusi", 1) ] qsort(&buf,buf.count,strideof(Episode)){ (x:UnsafePointer<Void>, y:UnsafePointer<Void>) -> Int32 in let s: Episode = UnsafePointer<Episode>(x).memory let r: Episode = UnsafePointer<Episode>(y).memory return Int32(s.1 - r.1) }) print(buf) // [ (Pacusi", 1), ("Baltazar", 5), ("Parade",10) ] が表示される C言語のブロックオブジェクト †Objective-C で定義されたブロックオブジェクトを Swift に渡して処理を行わせることができる。 Tはブロックオブジェクトの引数と返り値を表す型であり、たとえば「() -> Int32」。 @convention(block) T int scale = 15; [calc setScaleBlock:^(int n) { return n * scale; }]; このブロックオブジェクトは、swiftでは次のような型の変数に格納できる。 外側の括弧がないと型の解釈があいまいになることに注意(typealias宣言を使って別の名前をつけるとよい)。 var block: (@convention(block) (Int32) -> Int32) ! 変数 block は Swift のクロージャであるかのように、引数を渡して実行が可能。 実行時には渡されるときにキャプチャした値(scale = 15)が使われる。 Null許容性 (nullability) †Swiftのオプショナル型は変数がインスタンスを参照する場合としない場合(nil)を区別して扱うためのものであるが、 Objective-Cではあまり区別していなかった。 このようなObjective-Cの特性を扱うために導入された概念が Null 許容性 (nullability) である。
Objective-C の Null 許容性の表現
クラス UIImage のクラスメソッド + imageWithData?: は引数に関しては Null非許容であり、返り値は Null許容である。 + (UIImage * _Nullable) imageWithData: (NSData * _Nonnull) data; 型修飾子ではなく、キーワード nullable, nonnull, null_resettable(初期値にだけNULLやnilを許容する) も使うことができる。 + (nullable UIImage *) imageWithData: (nonnull NSData *) data; オプショナル型と Null 許容性の関係
//Objective-C @interface HestiaParty: Party @property (retain, nonnull) NSString *hero; @property (retain, nullable) NSString *helper; @property (retain, null_resettable) NSString *knife; @property (weak) NSString *supporter; + (nonnull instancetype) familia: (nullable NSString *) famous; + (nullable instancetype) initWithhero: (nonnull NSString *) hero; @end // Swift public class HestiaParty : Party { public var hero: String public var helper: String? public var knife: String! public var supporter: String? // weakだと、インスタンスが開放されたときゼロ化される(nilになる)ので public class func familia(famouse: String?) -> Self public init?(hero: String) } |