マルチスレッドのデザインパターン
以下を参照しつつマルチスレッドのデザインパターンを概観し、適宜Swiftでの実装を考える。
Single Threaded Execution (同時にできない)
Immutable (不変)
Guarded Suspension (用意できるまで待つ)
Balking (用意できないならやめる)
Producer-Consumer (生産者と消費者をわける)
Read-Write Lock (書く人は一人、読むのは誰でも)
Thread-Per-Message (処理を他のスレッドに任せる)
Worker Thread (スレッドを貯めておいて仕事を振る)
Future (任せておいた仕事の結果を同期的に受け取る)
Two-Phase Termination (適切な終了処理)
Thread-Specific Storage (スレッド毎に保存できる領域を確保しておく)
Active Object (自律的なオブジェクト)
Switで実装してみた。
github.com
GLKitミニマム2 OpenGL
テンプレートからOpenGL部分を残し、データを三角形の回転するだけに変えてみたもの。
頂点シェーダー
attribute vec4 position;
uniform mat4 modelViewProjectionMatrix;
void main()
{
gl_Position = modelViewProjectionMatrix * position;
}
フラグメントシェーダー
void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }
GLKViewController本体
import GLKit import OpenGLES func BUFFER_OFFSET(i: Int) -> UnsafePointer<Void> { let p: UnsafePointer<Void> = nil return p.advancedBy(i) } let UNIFORM_MODELVIEWPROJECTION_MATRIX = 0 let UNIFORM_NORMAL_MATRIX = 1 var uniforms = [GLint](count: 2, repeatedValue: 0) class GameViewController: GLKViewController { var program: GLuint = 0 var modelViewProjectionMatrix:GLKMatrix4 = GLKMatrix4Identity var normalMatrix: GLKMatrix3 = GLKMatrix3Identity var rotation: Float = 0.0 var vertexArray: GLuint = 0 var vertexBuffer: GLuint = 0 var context: EAGLContext? = nil var effect: GLKBaseEffect? = nil deinit { self.tearDownGL() if EAGLContext.currentContext() === self.context { EAGLContext.setCurrentContext(nil) } } override func viewDidLoad() { super.viewDidLoad() self.context = EAGLContext(API: .OpenGLES2) if !(self.context != nil) { print("Failed to create ES context") } let view = self.view as! GLKView view.context = self.context! view.drawableDepthFormat = .Format24 self.setupGL() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() if self.isViewLoaded() && (self.view.window != nil) { self.view = nil self.tearDownGL() if EAGLContext.currentContext() === self.context { EAGLContext.setCurrentContext(nil) } self.context = nil } } func setupGL() { EAGLContext.setCurrentContext(self.context) self.loadShaders() self.effect = GLKBaseEffect() self.effect!.light0.enabled = GLboolean(GL_TRUE) self.effect!.light0.diffuseColor = GLKVector4Make(1.0, 0.4, 0.4, 1.0) glEnable(GLenum(GL_DEPTH_TEST)) glGenVertexArraysOES(1, &vertexArray) glBindVertexArrayOES(vertexArray) glGenBuffers(1, &vertexBuffer) glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer) glBufferData(GLenum(GL_ARRAY_BUFFER), GLsizeiptr(sizeof(GLfloat) * gVertexData.count), &gVertexData, GLenum(GL_STATIC_DRAW)) glEnableVertexAttribArray(GLuint(GLKVertexAttrib.Position.rawValue)) glVertexAttribPointer(GLuint(GLKVertexAttrib.Position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(sizeof(GLfloat)*3), BUFFER_OFFSET(0)) glBindVertexArrayOES(0) } func tearDownGL() { EAGLContext.setCurrentContext(self.context) glDeleteBuffers(1, &vertexBuffer) glDeleteVertexArraysOES(1, &vertexArray) self.effect = nil if program != 0 { glDeleteProgram(program) program = 0 } } // MARK: - GLKView and GLKViewController delegate methods func update() { let aspect = fabsf(Float(self.view.bounds.size.width / self.view.bounds.size.height)) let projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 0.1, 100.0) self.effect?.transform.projectionMatrix = projectionMatrix var baseModelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, -4.0) baseModelViewMatrix = GLKMatrix4Rotate(baseModelViewMatrix, rotation, 0.0, 1.0, 0.0) // Compute the model view matrix for the object rendered with ES2 var modelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, 1.5) modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, rotation, 1.0, 1.0, 1.0) modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix) normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), nil) modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix) rotation += Float(self.timeSinceLastUpdate * 0.5) } override func glkView(view: GLKView, drawInRect rect: CGRect) { glClearColor(0.65, 0.65, 0.65, 1.0) glClear(GLbitfield(GL_COLOR_BUFFER_BIT) | GLbitfield(GL_DEPTH_BUFFER_BIT)) glBindVertexArrayOES(vertexArray) // Render the object again with ES2 glUseProgram(program) withUnsafePointer(&modelViewProjectionMatrix, { glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, UnsafePointer($0)) }) withUnsafePointer(&normalMatrix, { glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, UnsafePointer($0)) }) glDrawArrays(GLenum(GL_TRIANGLES), 0, Int32(gVertexData.count/3)) } // MARK: - OpenGL ES 2 shader compilation func loadShaders() -> Bool { var vertShader: GLuint = 0 var fragShader: GLuint = 0 var vertShaderPathname: String var fragShaderPathname: String // Create shader program. program = glCreateProgram() // Create and compile vertex shader. vertShaderPathname = NSBundle.mainBundle().pathForResource("Shader", ofType: "vsh")! if self.compileShader(&vertShader, type: GLenum(GL_VERTEX_SHADER), file: vertShaderPathname) == false { print("Failed to compile vertex shader") return false } // Create and compile fragment shader. fragShaderPathname = NSBundle.mainBundle().pathForResource("Shader", ofType: "fsh")! if !self.compileShader(&fragShader, type: GLenum(GL_FRAGMENT_SHADER), file: fragShaderPathname) { print("Failed to compile fragment shader") return false } // Attach vertex shader to program. glAttachShader(program, vertShader) // Attach fragment shader to program. glAttachShader(program, fragShader) // Bind attribute locations. // This needs to be done prior to linking. glBindAttribLocation(program, GLuint(GLKVertexAttrib.Position.rawValue), "position") glBindAttribLocation(program, GLuint(GLKVertexAttrib.Normal.rawValue), "normal") // Link program. if !self.linkProgram(program) { print("Failed to link program: \(program)") if vertShader != 0 { glDeleteShader(vertShader) vertShader = 0 } if fragShader != 0 { glDeleteShader(fragShader) fragShader = 0 } if program != 0 { glDeleteProgram(program) program = 0 } return false } // Get uniform locations. uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX] = glGetUniformLocation(program, "modelViewProjectionMatrix") uniforms[UNIFORM_NORMAL_MATRIX] = glGetUniformLocation(program, "normalMatrix") // Release vertex and fragment shaders. if vertShader != 0 { glDetachShader(program, vertShader) glDeleteShader(vertShader) } if fragShader != 0 { glDetachShader(program, fragShader) glDeleteShader(fragShader) } return true } func compileShader(inout shader: GLuint, type: GLenum, file: String) -> Bool { var status: GLint = 0 var source: UnsafePointer<Int8> do { source = try NSString(contentsOfFile: file, encoding: NSUTF8StringEncoding).UTF8String } catch { print("Failed to load vertex shader") return false } var castSource = UnsafePointer<GLchar>(source) shader = glCreateShader(type) glShaderSource(shader, 1, &castSource, nil) glCompileShader(shader) //#if defined(DEBUG) // var logLength: GLint = 0 // glGetShaderiv(shader, GLenum(GL_INFO_LOG_LENGTH), &logLength) // if logLength > 0 { // var log = UnsafeMutablePointer<GLchar>(malloc(Int(logLength))) // glGetShaderInfoLog(shader, logLength, &logLength, log) // NSLog("Shader compile log: \n%s", log) // free(log) // } //#endif glGetShaderiv(shader, GLenum(GL_COMPILE_STATUS), &status) if status == 0 { glDeleteShader(shader) return false } return true } func linkProgram(prog: GLuint) -> Bool { var status: GLint = 0 glLinkProgram(prog) //#if defined(DEBUG) // var logLength: GLint = 0 // glGetShaderiv(shader, GLenum(GL_INFO_LOG_LENGTH), &logLength) // if logLength > 0 { // var log = UnsafeMutablePointer<GLchar>(malloc(Int(logLength))) // glGetShaderInfoLog(shader, logLength, &logLength, log) // NSLog("Shader compile log: \n%s", log) // free(log) // } //#endif glGetProgramiv(prog, GLenum(GL_LINK_STATUS), &status) if status == 0 { return false } return true } func validateProgram(prog: GLuint) -> Bool { var logLength: GLsizei = 0 var status: GLint = 0 glValidateProgram(prog) glGetProgramiv(prog, GLenum(GL_INFO_LOG_LENGTH), &logLength) if logLength > 0 { var log: [GLchar] = [GLchar](count: Int(logLength), repeatedValue: 0) glGetProgramInfoLog(prog, logLength, &logLength, &log) print("Program validate log: \n\(log)") } glGetProgramiv(prog, GLenum(GL_VALIDATE_STATUS), &status) var returnVal = true if status == 0 { returnVal = false } return returnVal } } var gVertexData: [GLfloat] = [ -0.5, -0.0, 0.0, 0.5,-0.5,0.0, -0.5,0.5,0.0 ]
GLKitミニマム
テンプレートを三角形が回転するだけのコードに削ってみた。
import GLKit import OpenGLES func BUFFER_OFFSET(i: Int) -> UnsafePointer<Void> { let p: UnsafePointer<Void> = nil return p.advancedBy(i) } class GameViewController: GLKViewController { var rotation: Float = 0.0 var vertexArray: GLuint = 0 var vertexBuffer: GLuint = 0 var context: EAGLContext? = nil var effect: GLKBaseEffect? = nil deinit { self.tearDownGL() if EAGLContext.currentContext() === self.context { EAGLContext.setCurrentContext(nil) } } override func viewDidLoad() { super.viewDidLoad() self.context = EAGLContext(API: .OpenGLES2) if !(self.context != nil) { print("Failed to create ES context") } let view = self.view as! GLKView view.context = self.context! view.drawableDepthFormat = .Format24 self.setupGL() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() if self.isViewLoaded() && (self.view.window != nil) { self.view = nil self.tearDownGL() if EAGLContext.currentContext() === self.context { EAGLContext.setCurrentContext(nil) } self.context = nil } } func setupGL() { EAGLContext.setCurrentContext(self.context) self.effect = GLKBaseEffect() self.effect!.light0.enabled = GLboolean(GL_TRUE) self.effect!.light0.diffuseColor = GLKVector4Make(1.0, 0.4, 0.4, 1.0) glEnable(GLenum(GL_DEPTH_TEST)) glGenVertexArraysOES(1, &vertexArray) glBindVertexArrayOES(vertexArray) glGenBuffers(1, &vertexBuffer)// 頂点バッファポインタ生成 glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)// 有効化 // バッファに転送 glBufferData(GLenum(GL_ARRAY_BUFFER), GLsizeiptr(sizeof(GLfloat) * gVertexData.count), &gVertexData, GLenum(GL_STATIC_DRAW)) // 属性指定に頂点の位置を用いる事を指定 glEnableVertexAttribArray(GLuint(GLKVertexAttrib.Position.rawValue)) // glVertexAttribPointer(GLuint(GLKVertexAttrib.Position.rawValue), 3,// 1頂点あたりの要素の数 三次元なのでx,y,zの3 GLenum(GL_FLOAT), GLboolean(GL_FALSE), // ノーマライズされているか GLsizei(sizeof(GLfloat)*3), // 格納されているデータの間隔 BUFFER_OFFSET(0)) // データは並んでいる。 glBindVertexArrayOES(0) } func tearDownGL() { EAGLContext.setCurrentContext(self.context) glDeleteBuffers(1, &vertexBuffer) glDeleteVertexArraysOES(1, &vertexArray) self.effect = nil } // MARK: - GLKView and GLKViewController delegate methods func update() { let aspect = fabsf(Float(self.view.bounds.size.width / self.view.bounds.size.height)) let projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 0.1, 100.0) self.effect?.transform.projectionMatrix = projectionMatrix var baseModelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, -4.0) baseModelViewMatrix = GLKMatrix4Rotate(baseModelViewMatrix, rotation, 0.0, 1.0, 0.0) // Compute the model view matrix for the object rendered with GLKit var modelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, -1.5) modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, rotation, 1.0, 1.0, 1.0) modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix) self.effect?.transform.modelviewMatrix = modelViewMatrix rotation += Float(self.timeSinceLastUpdate * 0.5) } override func glkView(view: GLKView, drawInRect rect: CGRect) { glClearColor(0.65, 0.65, 0.65, 1.0) glClear(GLbitfield(GL_COLOR_BUFFER_BIT) | GLbitfield(GL_DEPTH_BUFFER_BIT)) glBindVertexArrayOES(vertexArray) // Render the object with GLKit self.effect?.prepareToDraw() glDrawArrays(GLenum(GL_TRIANGLES) , 0, Int32(gVertexData.count/3)) } } var gVertexData: [GLfloat] = [ -0.5, -0.0, 0.0, 0.5,-0.5,0.0, -0.5,0.5,0.0 ]
Swift落穂ひろい プトロコル準拠
Swiftで少しわかりにくかった所を整理しておきます。Swift3です。
Switch文で特定の型に対して
protocol Animal { } protocol Helloable { func hello() } struct Dog : Animal,Helloable { let name:String let legs:Int let age:Int func hello() { print("bow!") } } struct Cat : Animal, Helloable { let name:String let legs:Int let age:Int func hello() { print("nyao!") } } struct Fish : Animal { let name:String let age:Int } let seri = Dog(name: "Seri", legs: 4, age:13) seri.hello() let tama = Cat(name: "Tama", legs: 4, age:5) tama.hello() let kin = Fish(name: "Kin", age:1) let animal:Animal = seri let animal2:Helloable = seri let animal3:Animal & Helloable = seri let animal4:Any&Helloable = seri let animals:[Animal] = [seri, tama, kin] animals.forEach { (p:Animal) in switch(p) { case let p as Animal & Helloable: p.hello() default: break } }
コンパイル時間計測
other Swift Flagsに-Xfrontend -debug-time-function-bodiesをつける。
ネットワーク
引き続き
developer.apple.com
の一部の要点
NSURLSessionを使う。
旧iOSとの互換がいるときはNSURLConnectionを使う。
dataTaskWithRequest:completionHandler:ないしdataTaskWithURL:completionHandler:を呼ぶ
NSURLRequestにはリクエストに必要な情報をつめる。
もう少し制御が必要な場合はNSURLSessionでデリゲートを実装する。
独自認証、リダイレクトに従う等をデリゲートで都度訊かれる。
ディスクにダウンロードするならNSURLSession
一時停止等もできる。
POST要求
- やはりリクエストを作る。
- コンテンツタイプの指定はsetValue:forHTTPHeaderField:
- メモリ上の短いデータはURLエンコード
- ディスクのデータはNSMutableURLRequest.setHTTPBodyStream でNSInputStreamとして。
- 大量のデータはCFStreamCreateBoundPairでストリームの組を作りNSMutableURLRequest.setHTTPBodyStreamで。
- 進捗状況はconnection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:
認証
- URLSession:task:didReceiveChallenge:completionHandler:の実装
- 証明書を渡す場合 NSURLSessionAuthChallengeUseCredentialで証明書を渡す。
- 認証せず処理を続行 NSURLSessionAuthChallengeUseCredentialでnilを渡す。
- キャンセルしたい NSURLSessionAuthChallengeCancelAuthenticationChallengeを渡す。
- OSにまかせる NSURLSessionAuthChallengePerformDefaultHandling
- 特定の型の認証拒否 NSURLSessionAuthChallengeRejectProtectionSpace
認証書オブジェクト作成
- ログインパスワード認証 credentialWithUser:password:persistence:
- 証明書ベース credentialWithIdentity:certificates:persistence:にSecIdentityRefを渡す。(SecIdentityRefはユーザのキーチェーンから、SecItemCopyMatchingで取得)
ネットワーク
iOSDevネットワーキングオーバービュー
developer.apple.com
詳細はリンク先をみるとして冒頭の要点をまとめてみる。
ネットワークは信頼性が低いから以下に注意。
現実
対策
- まずデータのアップロード・ダウンロードはユーザに時間と費用を課していることに気づけ。これを最小限にせよ。
なるべくまとめる。
- どんなときも一括ダウンロード。都度ダウンロードすると遅延にも脆弱になるし、操作性も悪くなる。
- ダウンロードの量を最小にしてローカルにキャッシュしてね。前回から更新があるかを問い合わせてダウンロードしてね。
- 大きな画像を小さく表示するならはじめから小さな画像を用意してね。
- キャッシュ容量は各自工夫して下さい。
- トレードオフがいつもあるよ。例えば商品画像が並ぶとき、この画像をどこまで一括ダウンロードすべきか。。
ネットワークの状況変化への備え
- いつでも圏外、機内モード、Wifiを切る等の自体に対処してね。
- ユーザが意図的に接続しようとしているかが重要だよ。そういうときはいつでも接続しようとするべきだよ。すぐに接続できないときはユーザがキャンセルしない限り再接続を試みようとして下さいね。SCNetworkReachabilityをうまくつかってね。接続状態はモダールでない手段で表示してね。
- バックグラウンドで接続しようとした場合もモーダルでダイアログとか表示しないでね。再試行するにしても時間を開けていくようにしてね。
- SCNetworkReachabilityは状態が変換したときに通知が飛ぶよ。でもこのAPIは接続を事前に判断するものではなくて、接続に失敗してその原因を調べる時に使ってね。
通信速度の判断
少量のデータをダウンロードしてそれに費やした時間から評価してね。この評価値は都度図り直して精度を高めてみてね。
動画配信とかで速度が再生に追いついていないときは低帯域幅用の処理に一時的に切り替えるとか。
遅延
- 遠隔ホストへの要求で、応答を待ってからやっているとどんどん遅れていくよ。同時に送ったほうが良い。
- NSURLConnectionのパイプライン処理 NSMutableURLRequest.setHTTPShouldUsePipeliningを使うと複数のHTTP要求を同時に出せるよ。
プロトコル指向の例
WWDC2015から
developer.apple.com
重要だが未消化なのであとで感想を追記したい。
値型
WWDC2015においてSwiftでは参照より値型を用いることを推奨している。
Building Better Apps with Value Types in Swift - WWDC 2015 - Videos - Apple Developer
参照型を混ぜる際にはgetにmutatingをつけてコピーして渡すようにする。
isUniquelyReferenceNonObjCを用いるとなお良い。
以下の説明が参考になる。
wpdev.hatenablog.com
iPhoneの実機でネットワーク環境を試す
図は「On-Demand Resources Guide: Optimization with Testing」を参照
設定
Settings/Developer/Network Link Conditioner/Status
enableをオンにし、
Profileを設定する。
railsやってみたインデックス
Modelからデータを取り出す
辞書でシンボルの利用
rake
scaffold
railsチュートリアル
RubyGem
Asset Pipe, Sass
モデルをつくる
AVFoundation関連のサンプルコード
Apple
基本AV Foundation for iOS and macOS - Apple Developerのコーナーへ見に行けばよい
sample code
かなりあるので用途別に整理したい。
- AVLoupe
- StitchedStreamPlayer
- AVFoundationPiPPlayer: Picture-in-Picture Playback with AVKit
- AVReaderWriter: Offline Audio / Video Processing
- Using AVAudioEngine for Playback, Mixing and Recording
- Real-time Video Processing Using AVPlayerItemVideoOutput
- AVFoundationSimplePlayer-iOS: Using AVFoundation to Play Media
- AVFoundationQueuePlayer-iOS: Using a Mixture of Local File Based Assets and HTTP Live Streaming Assets with AVFoundation
- AVCustomEdit
- VideoSnake
- AVMetadataRecordPlay: Timed Metadata Capture Recording and Playback
- AVPlayerDemo
- avTouch
- AVMovieExporter
- AVSimpleEditoriOS
- RosyWriter
- AVCam-iOS: Using AVFoundation to Capture Images and Movies
- AVCamManual: Extending AVCam to Use Manual Capture API
- AVLocationPlayer: Using AVFoundation Metadata Reading
- AVFoundation - Timecode Reader/Writer (avtimecodereadwrite)
- BracketStripes: Using the Bracketed Capture API
- Using AVFoundation APIs to record a movie with location metadata
- AVTimedAnnotationWriter: Using Custom Annotation Metadata for Movie Writing and Playback
- AVARLDelegateDemo
- AVCompositionDebugVieweriOS
- MTAudioProcessingTap Audio Processor
- SquareCam
- GLCameraRipple
- StopNGo for iOS
- AVCamBarcode: Using AVFoundation to Detect Barcodes and Faces
- HLS Catalog: Using AVFoundation to play and persist HTTP Live Streams
- AVAutoWait: Using AVFoundation to play HTTP assets with minimal stalls
- AVCaptureAudioDataOutput To AudioUnit iOS
document
前述のappleのコーナーをみにいくのが早い
- About AVFoundation
- Exploring AVFoundation
- Technical Note TN2310 AVFoundation - Timecode Support with AVAssetWriter and AVAssetReader
- Technical Q&A QA1668 Playing media while in the background using AV Foundation on iOS
- Technical Q&A QA1772 How to determine whether an AVPlayerItem can be played at rates greater than 1.0
- Technical Note TN2288 Example Playlist Files for use with HTTP Live Streaming プレイリストの内容