Design Patterns in Swift:单例模式(中)

转自:http://115.159.192.200/2016/01/14/design-patterns-in-swift-dan-li-mo-shi-zhong/

如有侵犯,请来信oiken@qq.com


这里的阐述是基于Design Patterns in Swift:单例模式(上),当单例模式与多线程碰头,两者应该如何愉快的合作呢?

处理并发情况

如果你正在一个多线程的环境下使用Singleton,那么你需要去考虑不同模块同一时间去操作Singleton会带来什么样的后果以及如果应对可能出现的问题。

潜在的多线程问题在项目中经常见到,包括单例模式(上)中涉及到的LoggerbackupServer类都存在线程安全问题。因为他们涉及对数组进行操作,数组在Swift中是线程不安全的,Swift数组的扩容会重新创建一个新的数组(意味着地址完全不同)。然后ARC会为我们自动释放旧的数组,但这时候可能另一个线程还在访问旧的数组对象,极有可能crash(如果没有是你运气太好)

并发操作Singleton

在上一节的代码基础上进行修改

import Foundation
var server = BackupServer.server;

let queue = dispatch_queue_create("workQueue", DISPATCH_QUEUE_CONCURRENT);
let group = dispatch_group_create();

for count in 0 ..< 100 {
    dispatch_group_async(group, queue, {() in
        BackupServer.server.backup(DataItem(type: DataItem.ItemType.Email,
            data: "bob@example.com"))
    }); }

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

println("\(server.getData().count) items were backed up");

这里是使用了GCD的方式来进行多线程操作,调用backup方法100次。虽然对GCD有一些了解,但是非常碎片化,最近两天打算 总结一下GCD的用法,这了先简单介绍一下代码中用到的知识。

let queue = dispatch_queue_create("workQueue", DISPATCH_QUEUE_CONCURRENT);

代表创建一个名为workQueue的并发队列

let group = dispatch_group_create();

代表创建一个GCD任务组

for count in 0 ..< 100 {
    dispatch_group_async(group, queue, {() in
        BackupServer.server.backup(DataItem(type: DataItem.ItemType.Email,
            data: "bob@example.com"))
    }); }

把队列queue放到调度组中以异步的方式执行,代表100个闭包的内容可以同时执行,不会因为其他block而阻塞。

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

代表会等待到group中的所有任务都完成再去执行下面的代码。

我们运行一下程序,发现crash掉了,并且debuggerdata.append()方法处中断(break),上面说到了数组扩容的原理,对于线程不安全的操作导致crash(错误提示:manipulating the contents of a Swift array isn’t a thread-safe operation, and singletons that use arrays need concurrency protections.)

串行操作

为了解决这个问题,我需要确定在一个时间点只有一个block去调用backup方法来对数组进行扩容。在这里采取了GCD的串行方式。

import Foundation
class DataItem {
    enum ItemType : String {
        case Email = "Email Address";
        case Phone = "Telephone Number";
        case Card  = "Credit Card Number";
    }
    var type:ItemType;
    var data:String;
    init(type:ItemType, data:String) {
        self.type = type; self.data = data;
    } }

final class BackupServer {
    let name:String;
    private var data = [DataItem]();
    private let arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL);
        private init(name:String) {
    self.name = name;
    globalLogger.log("Created new server \(name)");
}
func backup(item:DataItem) {
    dispatch_sync(arrayQ, {() in
        self.data.append(item);
        globalLogger.log("\(self.name) backed up item of type \(item.type.rawValue)");
    })
}

func getData() -> [DataItem]{
    return data;
}

class var server:BackupServer {
    struct SingletonWrapper {
        static let singleton = BackupServer(name:"MainServer");
    }
    return SingletonWrapper.singleton;
}
}

对部分代码进行分析

private let arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL);

创建名为arrayQ的串行队列

 dispatch_sync(arrayQ, {() in
        self.data.append(item);
        globalLogger.log("\(self.name) backed up item of type \(item.type.rawValue)");
    })

同步访问data.append(),每一个时间点只有一个线程在操作数组。

我们上一节主要解决了BackupServerLogger两个类的单例问题,同样这里,也需要对两个类都进行线程安全的保护,虽然在BackupServer中我们是同步的调用globalLogger.log()方法,但是可能在其他模块也调用了globalLogger.log()方法,那么我们就需要在Logger类中进行修改保护。

import Foundation;

let globalLogger = Logger();

final class Logger {
    private var data = [String]()
    private let arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL);
    private init() {
        // do nothing - required to stop instances being
        // created by code in other files
    }

    func log(msg:String) {
        dispatch_sync(arrayQ, {() in
            self.data.append(msg);
        });

    }

func printLog() {
    for msg in data {
        println("Log: \(msg)");
    }
} }

这里也使用相同的方法创建串行队列,同步方法确认一个时间点只有一个线程对数组进行修改操作。

这时候运行我们的程序正常结果被打印

100 items were backed up

如果叙述存在问题,请联系我,谢谢


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值