-->

Swift

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




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

Finderのカラム表示を実現するやつだ。

こんなのを作ってみる。

001

データのクラスを用意する。

class Node {
    // 表示名
    var name:String
    // 子要素
    var children:[Node]
    
    init(name:String) {
        self.name = name
        self.children = [Node]()
    }
}

ViewControllerクラスにNSBrowserDelegateプロトコルを実装する。"class ViewController: NSViewController"の後に", NSBrowserDelegate"を追加する。必要なメソッドや表示するデータをクラスに追加する。

表示するデータのメンバ変数を追加。

    var rootNode = Node(name: "ROOTNODE")

viewDidLoad()でデータの初期化する。

        // データを用意(↓このようなデータ)
        // item0
        // item1 -+- item1-0
        //        +- item1-1 -+- item1-1-0
        //        |           +- item1-1-1
        //        +- item1-2
        // item2
        // item3
        for i in 0..<4 {
            rootNode.children.append(Node(name: "item\(i)"))
        }
        for i in 0..<3 {
            rootNode.children[1].children.append(Node(name: "item1-\(i)"))
        }
        for i in 0..<2 {
            rootNode.children[1].children[1].children.append(Node(name: "item1-1-\(i)"))
        }

必要なメソッドを追加する。

    // ルートアイテムを返す
    func rootItem(for browser: NSBrowser) -> Any? {
        return rootNode
    }

    // 子要素のアイテムの数を返す
    func browser(_ browser: NSBrowser,
                 numberOfChildrenOfItem item: Any?) -> Int {
        let node = item as! Node
        return node.children.count;
    }

    // index番のアイテムを返す
    func browser(_ browser: NSBrowser,
                 child index: Int,
                 ofItem item: Any?) -> Any {
        let node = item as! Node
        return node.children[index]
    }
    
    // リーフかどうかを返す
    //   子要素がない場合はtrue(リーフアイテムだよって意味)を返す
    func browser(_ browser: NSBrowser,
                 isLeafItem item: Any?) -> Bool {
        let node = item as! Node
        return node.children.count == 0 ? true:false
    }

    // アイテムの表示文字列を返す
    func browser(_ browser: NSBrowser,
                 objectValueForItem item: Any?) -> Any? {
        let node = item as! Node
        return node.name
    }

ストーリーボードを開いてNSBrowserを貼り付けてdelegateとView Controllerを接続する。

これで完成!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 {
            
        }



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

↑このページのトップヘ