libuvをつかうTIPS
S w i f
はじめての
@TokyoYoshida
t か ら
自己紹介@tokyoyoshida
Sierで業務システムを作っています。
趣味でSwiftプログラミングしています。
!
オッズ 3.3
オッズ13.3
オッズ 4.5
自己紹介@tokyoyoshida
ç√
①マカヒキ
③ディーマジェスティ
ヴァンキッシュラン
とは
1日おくれ
・非同期I/Oを実現するためのC言語ライブラリ
・Node.jsで使用されている
・ノンブロッキングI/O ・・・ I/O時にブロックせず次の処理ができる
・イベント駆動 ・・・ I/O発生時に処理を動作させる
・シングルスレッド ・・・ 接続数に対してメモリを消費を抑えられる
(Node.jsの説明より抜粋)
libuvをSwiftから呼びたい
・Server Side Siwftが盛り上がっていそう
・Swift言語の創世記に立ち会いたい
・Swiftのコードをもっと書けるようになりたい
・Cライブラリをラップして、型安全かつ簡単に呼び出せる
ようにしたい
SwiftLibuv (6/11に公開しました!)
https://github.com/TokyoYoshida/SwiftLibuv
Swiftからlibuv(の一部)を呼び出すためのライブラリ
簡単なWebフレームワークもついてきます。
現在はCookieをRailsっぽく扱える機能を製作中。
今回は、このライブラリを作ったときのTIPSを紹介します。
Swift Packageマネージャを使う
今回、プロジェクト作成はSwift Packageマネージャを使いました。
Swift Packageマネージャはコマンドラインから使います。
⬛︎プロジェクトの作成
swift build —— init ・・・ パッケージマネージャのプロジェクトを作成
swift build —X ・・・ Xcodeのプロジェクトを作成
Swiftで「Hello World」を出力するコマンドラインコマンドの
プロジェクトが作成されます。
⬛︎コーディング
Sourceフォルダ以下にコードを書いていきます。XCodeを使って編集できます。
⬛︎ビルド
swift build
⬛︎実行ファイルを実行する
.build/debug/パッケージ名
SwiftからCライブラリを呼ぶ方法
❶ライブラリをインストールする
❷ヘッダファイルを参照できるようにする
❸コードを書く
❹ビルドする
SwiftでCライブラリを呼ぶ方法
❶ライブラリをインストールする
それぞれのライブラリのReadmeに従って
インストールします。
たとえば、Macにlibuvをインストールするなら。
brew install libuv
でOK。
SwiftでCライブラリを呼ぶ方法
❷ヘッダファイルを参照できるようにする
ClangのModule機能を用いるとC言語の#includeをSwiftのimportに変換することができ
ます。
module.modulemapファイルの例
(uv.hをswiftでimport Cuvとして呼び出し、ビルド時は-luvオプションでリンクしてくれます)
module Cuv [system] {
header "/usr/local/include/uv.h"
link "uv"
export *
}
このような宣言をSwift Packageとしてgitのリポジトリに登録し、
Package.swiftでパッケージとして読み込めばヘッダの読み込み、ライブラリ
のリンクができるようになります。libuvなら実際にそのようなパッケージを
作っている人がいるので、それをPackage.swiftに記載すれば使えます。
package.swiftファイルの例
import PackageDescription
let package = Package(
name: "NetLib",
dependencies: [
.Package(url: "https://github.com/bontoJR/libCuv.git", majorVersion: 1)
]
)
SwiftでCライブラリを呼ぶ方法
❸コードを書く
Cの関数がいたって普通に呼び出すことができます。
(詳しくは後述)
main.swiftの例
import Cuv
var loop = uv_loop_new()
uv_run(loop, UV_RUN_DEFAULT)
uv.hを読み込む
C関数の戻り値を受け取ったり、引数を渡す
こともそのままできる
SwiftでCライブラリを呼ぶ方法
❹ビルドする
swift build
でビルドすると、Cのライブラリもリンクしてくれます。
Swiftのバージョンによっては、なぜかlibuvのリンクエラーが出ます。
<エラーの内容>
ld: library not found for -luv for architecture x86_64
<unknown>:0: error: link command failed with exit code 1 (use -v to see invocation)
<unknown>:0: error: build had 1 command failures
そんな時は、リンカーにパスを指定してあげれば良い。
swift build -Xlinker -L/usr/local/lib
コードの書き方
変数の型は読み替える必要があります。
//コメントはc言語の型
var a:CChar = 1 // = char
var b:CInt = 1 // = Int
var c:CLong = 1 // = long
var d:CFloat = 1.1 // = float
var e:CDouble = 1.1 // = double
//ポインタ
var f:UnsafePointer<CChar> // const char *
var g:UnsafePointer<CInt> // const int *
var h:UnsafeMutablePointer<CChar> // char *
var i:UnsafeMutablePointer<CInt> // int *
//構造体
var j:sockaddr_in = sockaddr_in() // struct sockaddr_in { ... }
コードの書き方
C関数を呼ぶための書き方いろいろ。
// swift変数をポインタの引数として渡す場合は、&をつける
var sock_addr_in = sockaddr_in()
if uv_ip4_addr(host, Int32(port), &sock_addr_in) != UVResult.Success {
print("Socket address init failed!")
}
// swift変数のアドレスをUnsafePointerに変換するにはwithUnsafePointerを使う
var a:Int8 = 1 // = char
var h:UnsafeMutablePointer<Int8> = withUnsafePointer(&a) {
UnsafeMutablePointer<Int8>($0)
}
var x = h.pointee // ポインタから取り出すときはpointeeを使う。
// (Swift 3より。Swift 2まではmemory)
print("value = (x)")
// UnsafePointer同士は自由にキャストできる
var i:UnsafeMutablePointer<Int32> = UnsafeMutablePointer<Int32>(h)
コードの書き方
C関数を呼ぶための書き方いろいろ。
// StringをC言語の文字列に変換するにはwithCStringを使う
string.withCString {
let cString = $0
var wbuf = uv_buf_init( UnsafeMutablePointer<Int8>(cString), UInt32(strlen(cString)))
}
//引数が関数ポインタをとる場合、クロージャをそのまま渡すことができる
let ret = uv_listen(calBackBag, backLog){
server ,status in
print(“dummy”)
}
// (補足)uv_listenのシグニチャは
// int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb)
// となっている。このうち、uv_connection_cbの宣言は
// void (*uv_connection_cb)(uv_stream_t* server, int status) という関数ポインタである。
Tips
⬛︎Swiftのコマンドラインコンパイラはいろいろ難しい
・ちまたにあるライブラリは、swiftのdevブランチをターゲットバージョンにしている
場合があるので、利用するにはdevブランチを使わざるをえないことがあります
(今回はHTTPのParserとしてZewo、OpenSwiftのライブラリを使用したかった)
・devブランチのswiftは不安定
構文エラーがあるとコンパイラエラーではなくてsegumentation faultが出ることがありま
す。
バージョンが1ヶ月違うだけで大量のコンパイルエラーが出ることがあります。
⬛︎対策
・そんな状況を楽しむのが⭕️
・コマンドラインコンパイラについては、swift-usersメーリングリストで質問する人が
多い印象です。(https://lists.swift.org/mailman/listinfo)
・コンパイラのバージョンの切り替えは「swiftenv」というツールを使うと簡単に切り替え
られます。
Tips
⬛︎関数ポインタを渡す場合はいろいろと制約がある
・クロージャをC関数に渡そうとするとき、変数のキャプチャはできません。
<エラーの内容>
error: a C function pointer cannot be formed from a closure that captures context
・関数ポインタにはリテラククロジャーかトップレベルファンクションしか呼べません。
When calling a function that takes a function pointer
argument, you can pass a top-level Swift function, a
closure literal, or nil.
(Apple The Swift Programming Languageより)
・他にもErrorをthrowするクロージャを渡せないという制約がありますが、このTips
では扱いません。
Tips⬛︎対策
・呼び出せるタイプのクロージャから、本来呼び出したいクロージャを呼び出してあげる。
let calBackBag = unsafeBitCast(handle_addr, to: UnsafeMutablePointer<uv_stream_t>.self)
calBackBag.pointee.data = retainedVoidPointer { [unowned self]
(bytesRead:Int ,buffer:UVBuffer)->Void in
let uvData = UVData(lenBytes:bytesRead ,buffer: buffer)
callBack(self ,data: uvData)
}
uv_read_start(calBackBag, _alloc_buffer){ stream, bytesRead, buffer in
if let callback = unsafeFromVoidPointer(x: stream.pointee.data) as
((Int,UVBuffer) -> Void)? {
defer {
releaseRetainedPointer(x: callback)
buffer.pointee.base.deallocateCapacity(bytesRead)
}
呼び出せるタイプのクロージャ
呼び出せないタイプのクロージャ
(Selfをキャプチャしているため)
ここで本来呼び出したいクロージャを呼び出してい
る
Cから呼べないクロージャ
Cから呼べるクロージャ C関数
CallBack登録
呼び出し
Tips
⬛︎libuvの関数にcallbackを登録するときは、メモリ管理に気を使わなければならない
・libuvの関数はcallbackをメモリを保持(retain)してくれません。
その一方でlibuvの関数はノンブロッキングなので呼び出したらすぐに
戻ってきます。戻ってきてブロックを抜けると、ARCはローカル変数を解放
(release)してしまいます。
・読み取り用のバッファなどはこちらで領域確保(alloc)・解放(dealloc)を管理
する必要があります。
クロージャ C関数
①CallBack登録
②処理がすぐに戻る
(non blocking)
③処理を抜けると消滅
④❌実際に呼ぼうと
するとエラー
Tips
⬛︎対策
・自分で変数のretain、release、バッファ領域の確保(allocate)、解放(deallocate)します
手動でのretain,releaseはSwiftのUnmanaged(struct)を用います。
let calBackBag = unsafeBitCast(handle_addr, to:UnsafeMutablePointer<uv_stream_t>.self)
calBackBag.pointee.data = retainedVoidPointer { [unowned self]
(bytesRead:Int ,buffer:UVBuffer)->Void in
let uvData = UVData(lenBytes:bytesRead ,buffer: buffer)
callBack(self ,data: uvData)
}
uv_read_start(calBackBag, _alloc_buffer){ stream, bytesRead, buffer in
if let callback = unsafeFromVoidPointer(x: stream.pointee.data) as
((Int,UVBuffer) -> Void)? {
defer {
releaseRetainedPointer(x: callback)
buffer.pointee.base.deallocateCapacity(bytesRead)
}
callback(bytesRead, UVBuffer(buffer))
}
}
retain
alloc
releaseとdealloc
Tips
⬛︎Libuvのネットワークからの読み取り関数(uv_read_start)は、セッションがCloseされるまでメモ
リ内にセッションを維持する必要があります。先ほど、クロージャをC関数に登録するときに手動
でRetainしましたが、クロージャの親オブジェクト(self)がセッション情報を持つ場合、selfも保持
しておく必要があります。しかし、クロージャがselfを保持すると循環参照が発生してしまいます。
ク
ロ
|
ジ
ャ
Libuv読み取り関数
(uv_read_start)
②CallBack登録
④親オブジェクトが持つ
セッション情報を使用し
て処理する(例えば、
Webクライアントに
レスポンスを返す)
※ここで、親オブジェクト
を保持したいが、保持す
ると循環参照になる
③読み取りが起きると
呼び出し。
⑤再度同じクライアント
からの読み取りが
起きると呼び出し
⑥③の時と同じクライ
アントへの処理をする
ために同じセッション
情報を使用する。
親オブジェク
ト
(セッション
情報を保持)
①クロージャの生成
Tips
⬛︎対策(もっといい方法があるかもしれませんが。。)
循環参照をローカル変数を使って制御する。
private func doReadPhase(){
var _selfKeeper:HttpProcessor? = self
do {
try client.read { [unowned self] result in
switch result {
case .success(let data):
self.doParsePhase(data: data)
case let .error(error) where error is Closed:
self.client.close()
_selfKeeper = nil
case .error(let error):
self.errorRespond(error: error)
}
}
} catch (let error) {
self.errorRespond(error: error)
}
}
クラス内でSelfを保持する(ローカル変数を介して
循環参照させる)
終わったらSelfを開放する。
このクラスごとARCが回収する。
クロージャはSelfを保持させないが、ローカル変数の
_selfKeeperはキャプチャーさせる
今回作ったlibuvのラッパーライブラリを使ってHTTPのリクエスを受けてみました。
main.swiftの内容
func exampleOfWebServer(){
print("Now web starting.")
let framework = WebFrameWork() { error in
return Response(body: Data("error page."))
}
framework.add(responder: StaticWebPage(uri: URI(byPath:"/"), filePath: "public_html/test.html"))
framework.run()
}
exampleOfWebServer()
実際にLibuvを呼び出してみる
実際にLibuvを呼び出してみる
サーバを起動して
結果が却ってきた!!
ターミナルから呼び出してみます。
HTTPリクエストを発行すると
参考文献
⬛︎使用ライブラリ
libuv
https://github.com/libuv/libuv
Zewo
https://github.com/Zewo/Zewo
OpenSwift
https://github.com/open-swift
⬛︎先人たち
bontoJR/Wing (https://github.com/bontoJR/Wing) this week in Swiftに記事がリンクされた方
noppoMan/Suv (https://github.com/noppoMan/Suv) Tokyo Serverside Meetupで登壇された方
chriseidhof/libuv-test(https://github.com/chriseidhof/libuv-test) try! Swiftで登壇された方
⬛︎Cライブラリを呼ぶ時に参考になる資料
Apple The Swift Programming LanguageのC言語呼び出し関連の記述
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html
詳解Swift
SwiftでC言語のライブラリを使おう
https://speakerdeck.com/yusukeito/swiftdecyan-yu-falseraiburariwoshi-ou

Swiftからlibuvを呼び出すTIPS