-->

iOS

マルチスレッドで処理するプログラムを書いていると、同時にアクセスするとまずいリソースがあってそれでも別々のスレッドでアクセスしないといけない場合がままある。こんなとき、排他制御ってのをやってそういうリソースにアクセスするのは必ず1つのスレッドにするようにする。ここでは、DispatchSemaphoneを使ってやってみる。

counterを100回カウントアップする処理を100個並列処理して結果を表示するプログラムを書いてみた。(100個並列処理と書いたが実際には同時に何個動いているかは不明)

        // セマフォを用意する。
        let semaphore = DispatchSemaphore(value: 1)

        var counter = 0
        // counterをカウントアップする関数を用意する。
        let op1 = {
            for _ in 0..<100 {
                semaphore.wait()
                counter+=1;
                semaphore.signal()
            }
        }
        let queue = OperationQueue()
        for _ in 0..<100 {
            queue.addOperation(op1)
        }

        // キューにあるオペレーションが実行されて終わるのを待つ
        queue.waitUntilAllOperationsAreFinished()

        // counterを表示
        //   10000って表示される。
        //   セマフォを使わないと10000以外の値になる場合がある。
        print(counter)

これを実行すると必ず10000と表示される。semaphore.wait()、semaphore.signal()コメントアウトして実行すると、10000以外になることがある。




このエントリーをはてなブックマークに追加

並列処理を行うには、ディスパッチキュー、オペレーションキュー、Threadクラスの3種類の方法がある。
ディスパッチキュー、オペレーションキューはGrand Central Dispatch(GCD)って言うスレッドを管理する機能を使って実行する。ディスパッチキューはGCDをラップしただけのもの(と思う)で、オペレーションキューはより高機能にしたもの(と思う)だ。
Threadクラスは、まーマルチスレッドの基本的な機能を提供しているもので、ディスパッチキューやオペレーションキューの下働きを行っている。

ここでは、オペレーションキューを使う。
まずは、単純に別スレッドでなんか実行する。

        // キューを用意する。
        let queue = OperationQueue()
        // キューに処理を追加する。
        queue.addOperation {
            // ↓ 時間のかかる処理の代わり
            Thread.sleep(forTimeInterval: 10)

            print("hoge1")
        }
        print("hoge2")
        
        // キューにあるオペレーションが実行されて終わるのを待つ
        //   これはGUIアプリのメインスレッドでは使用しない方がいい
        queue.waitUntilAllOperationsAreFinished()
        print("END")

これを実行すると、こんな表示になる。

hoge2
hoge1
END

addOperationでキューに追加した処理が別スレッドで実行され、hoge2が表示されたあと、約10秒後、hoge1が表示される。

3つの処理を並列実行させてみる。

        print(Thread.current.description)

        // 3種類の処理(BlockOperation)を用意する。
        let op1 = BlockOperation {
            for i in 0..<10 {
                Thread.sleep(forTimeInterval: 1.0)
                print(Thread.current.description + ",op1,\(i)")
            }
        }
        let op2 = BlockOperation {
            for i in 0..<10 {
                Thread.sleep(forTimeInterval: 1.0)
                print(Thread.current.description + ",op2,\(i)")
            }
        }
        let op3 = BlockOperation {
            for i in 0..<10 {
                Thread.sleep(forTimeInterval: 1.0)
                print(Thread.current.description + ",op3,\(i)")
            }
        }
//        // オペレーションの依存関係を設定
//        // op1->op2->op3の順で実行されるようにする。
//        //   op2はop1が終わってから実行されるようにする。
//        op2.addDependency(op1)
//        //   op3はop2が終わってから実行されるようにする。
//        op3.addDependency(op2)
        
        // キューを用意する。
        let queue = OperationQueue()
        // キューに処理を追加する。
        queue.addOperation(op1)
        queue.addOperation(op2)
        queue.addOperation(op3)
        
        // キューにあるオペレーションが実行されて終わるのを待つ
        //   これはGUIアプリのメインスレッドでは使用しない方がいい
        queue.waitUntilAllOperationsAreFinished()

これを実行すると、3つの処理の表示が入り乱れてこんな感じに表示される。

<NSThread: 0x600000062c40>{number = 1, name = main}
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,0
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,0
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,0
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,1
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,1
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,1
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,2
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,2
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,2
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,3
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,3
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,3
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,4
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,4
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,4
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,5
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,5
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,5
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,6
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,6
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,6
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,7
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,7
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,7
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,8
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,8
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,8
<NSThread: 0x60800027ed00>{number = 4, name = (null)},op2,9
<NSThread: 0x60800046eac0>{number = 3, name = (null)},op3,9
<NSThread: 0x600000068280>{number = 2, name = (null)},op1,9

ソースでコメントアウトしているコードで処理(オペレーション)の依存関係が設定できる。op2.addDependency(op1)でop1の処理が終わらないとop2が実行されないようになる。つまり、コメントアウトしている依存設定を有効にすると、op1の実行がおわってから、op2が実行され、op2が終わってからop3が実行される。

さて、並列処理では排他処理が必要になってくるんですが、それはまたの機会にw




このエントリーをはてなブックマークに追加

さて、どうやって使うんだろーとグーグル先生に尋ねてみると
GRDBでSQLiteデータベースを使う(Swift3.0) - Swiftサラリーマンにたどり着いた。このサイトではCocoaPodsを使っているみたいなんだが、ここでは手動でやってみようと思う。macOSを対象にやっていくが、iOSでも同じような感じだ。

ここからダウンロードして展開しておく。
Sqliteを使うプロジェクトを用意して、ダウンロードしたGRDBにあるGRDB.xcodeprojを追加する。メニューのFile - Add Files to "〜〜"で追加する。

001

Target DependenciesのBuild PhasesにmacOSならGRDBOSX、iOSならGRDBiOSを追加する。
Target DependenciesのBuild Phasesはプロジェクトを選んでBuild Phasesタブを選択するとそこにある。

002

+ ボタンを押してGRDBOSX(GRDBiOS)を追加する。

003

GeneralのEmbedded BinariesにGRDB.frameworkを追加する。
GeneralのEmbedded Binariesはプロジェクトを選んでGeneralタブを選択するとそこにある。

004

+ ボタンを押してGRDB.frameworkを追加する。

005

[Java] SQLiteを使う。と同じことをやってみる。
ソースの頭の方に

import GRDB

を追加する。
テーブル作ったり、データ追加したり、検索したりするのはこんな感じになる。

        let dbFilename = NSTemporaryDirectory() + "test.db"
        print(dbFilename)
        do {
            let dbQueue = try DatabaseQueue(path: dbFilename)

            try dbQueue.inDatabase({db in
                // テーブルを作る
                var sql = "CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY, name TEXT)"
                try db.execute(sql)

                // データを登録
                sql =  "INSERT INTO test(id, name) VALUES(?, ?)"
                for i in 0..<10 {
                    try db.execute(sql, arguments: [i+1, "hoge" + i.description])
                }
                
                // 検索する
                sql =  "SELECT * FROM test"
                let rows = try Row.fetchCursor(db, sql)
                while let row = try rows.next() {
                    let id:Int? = row.value(named: "id")
                    let name:String? = row.value(named: "name")
                    print("\(id!):\(name!)")
                    
                }
            })
            
        } catch {
            
        }



このエントリーをはてなブックマークに追加

JSONSerializationというクラスがあってこれを使う。
JSONの文字列からオブジェクト(ArrayやDictionary)に変換する。

        let jsonStr = "[ { \"name\":\"山田太郎\", \"age\":22 }, { \"name\":\"山田花子\", \"age\":24 } ]"
        // JSON文字列をNSDataに変換
        let jsonData = jsonStr.data(using: String.Encoding.utf8)
        do {
            // DataからJSONオブジェクトに変換
            let json = try JSONSerialization.jsonObject(with: jsonData!, options: .mutableLeaves) as! Array<Dictionary<String, Any>>
            for item in json {
                if let name = item["name"], let age = item["age"] {
                    print("\(name):\(age)")
                }
            }
        } catch {
        }

オブジェクトをJSON(文字列)に変換する。オブジェクトは、NSArray、NSDirctionaryでその中の値は、NSString、NSNumber、NSDictionary、NSNullでNSDictionaryのキーはNSStringじゃないといけない。

        let json = [
            [ "name":"山田太郎", "age":22 ],
            [ "name":"山田花子", "age":24 ]
        ]
        do {
            if JSONSerialization.isValidJSONObject(json) {  // JSONに変換できるかチェック
                // JSONに変換(.prettyPrintedを指定すると見やすいように改行や空白が挿入される。)
                let jsonData = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
                // ↓こっちだと空白や改行が含まれない
//                let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
                // Dataを文字列に変換する。
                let jsonStr = String.init(data: jsonData, encoding: .utf8)
                if let str = jsonStr {
                    print(str)
                }
            }
        } catch {
            
        }



このエントリーをはてなブックマークに追加

とりあえず、URLSessionを使って通信してみる。(たぶん一番単純なもの)

macOS向けに書くがiOSでも基本的に同じだ。

ボタンを貼ってそれを押した時の処理をこんな感じにする。

    @IBAction func buttonClick(_ sender: NSButton) {
        let url = URL(string: "http://www.livedoor.com/")
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)
        let req = URLRequest(url: url!)
        
        let task = session.dataTask(with: req, completionHandler: {
            (data, res, err) in
            if let uerr = err {
                print(uerr.localizedDescription)
            }
            if let udata = data {
                let str = String(data: udata, encoding: String.Encoding.utf8)
                print(str ?? "エラー")
            }
        })
        task.resume()
    }

これを実行するとこんなエラーがでる。

2017-02-07 23:04:07.107 TestHttp[71203:1144451] App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

App Transport Security(ATS)によってHTTP通信をブロックしてるって書いてある。基本的にHTTPS通信しろと言うことだ。
例外的にこれを解決するにはInfo.plistを変更する必要がある。何種類か方法があるが何個か書いておく。

非推奨の方法

App Transport Security Settingsを追加してその下にAllow Arbitrary Loadsを追加してYESにする。

002

例外ドメインを追加する方法

App Transport Security SettingsにException Domainsを追加してDictionary型でドメイン(ここではwww.livedoor.com)を追加する。NSTemporaryExceptionAllowsInsecureHTTPLoads、NSIncludesSubdomainsをBoolean型でYESで追加する。

003

次にTextViewに受信したデータを表示してみよう。
UI部品を操作するにはメインスレッドで行う必要があるのだが、dataTaskに指定するクロージャは別スレッドで実行されるのでDispatchQueue.main.asyncを使ってアクセスする。TextViewをはっ付けてtextViewでアクセスできるようにして、次のコードをprint(str ?? "エラー")の次あたりに追加する。

                DispatchQueue.main.async {
                    self.textView.string = str
                }

(追記)
DispatchQueue.main.asyncより、OperationQueue.main.addOperationの方がいいのかな。



このエントリーをはてなブックマークに追加

↑このページのトップヘ