有限状态自动机(FSM “finite state machine” 或者FSA “finite state automaton” )是为研究有限内存的计算过程和某些语言类而抽象出的一种计算模型。有限状态自动机拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。有限状态自动机可以表示为一个有向图。有限状态自动机是自动机理论的研究对象。
我们来看以下片段代码:

1
2
3
4
5
6
7
class ViewController {
@IBOutlet var tableView: UITableView!
@IBOutlet var errorLabel: UILabel!
@IBOutlet var emptyStateLabel: UILabel!
@IBOutlet var activityIndicator: UIActivityIndicatorView!
// Some implementation
}

我们得出4种状态,且每种独立存在:

  • 显示数据
  • 是否在加载数据中
  • 是否显示错误
  • 空状态
    我们在控制器中定义该枚举:
    1
    2
    3
    4
    5
    6
    7
    8
    extension ViewController {
    enum State {
    case loading
    case showingData([Item])
    case empty
    case error(Error)
    }
    }

Swift能实现多继承吗?

虽然多继承在C++中可以实现,但在Swift中,是不能够实现多继承的。但一个class可以遵循多个协议,只能继承一个父类。但值类型比如 struct , enum 就不存在继承这么一说,但还是可以遵循多个协议的。
Swift只支持协议多继承
如:

1
2
3
4
5
6
7
protocol ChildAB:ChildA,ChildB {

}
class MyClass: ChildAB {
func method() {
}
}

Swift与OC最大的区别就是新增了值类型Struct, let变量修饰后无法再次被赋值实现真正的immutable,也就是说一旦赋值不能再次修改。虽然OC中定义NSArray *arr 也是一个immutable,但是arr对象指针指向还可以被再次重新赋值。OC是一门运行时语言,arr所指向的变量都是运行时获取对象。OC中使用immutable不直接等同于线程安全。Objective-C中存在深拷贝、浅拷贝,即使你调用一个[NSMutableArray Copy]得到的NSArray也不代表这个数组中的对象都是经过深拷贝。

我们先看一下如下代码会造成线程安全问题?:

1
2
3
if (self.xxx) {
[self.dict setObject:@"ah" forKey:self.xxx];
}

在单线程模式下肯定是没啥问题,但在多线程情况下比如线程A,线程B两者都同时进入了if条件,当A正要执行set操作,B已经对self.dict = nil。那么线程A肯定是不能设置成功的。
还有另外一种情况,当我们的数据资源比如集合,多线程对资源进行争夺。如添加和移除操作。为了保证最终结果的准确性,我们通常会给操作代码加锁。
1
2
3
OSSpinLock(&_lock);
[self.array addObject:@"data1"];
OSSpinUnlock(&_lock);

我们都想写得一手好代码。这包含防止内存泄露,当在写闭包时业务如果需要用到self,我们会使用[weak self] 修饰。但为什么需要弱捕获呢?是否任何情况下在闭包里都要用weak? 接下来我们将用捕获列表来回答这几个问题。我们将讲解到不同的捕获,以及什么时候用合适的捕获去处理问题。

什么是捕获列表

我们先看以下代码片段,调用闭包后显示的结果:

1
2
3
4
5
6
7
8
var name = "qiu"
var appendToName = { (string: String) -> String in
return name.appending(string)
}

let one = appendToName("gaoying") //qiugaoying
name = "liu"
let two = appendToName("gaoying") //liugaoying

闭包可对同一作用域范围内的属性做引用,也就是闭包内持有的属性在同一上下文作用域内变更受影响。我们再观察以下代码:

1
2
3
4
5
6
7
8
var name = "qiu"
var appendToName = { [name] (string: String) -> String in
return name.appending(string)
}

let one = appendToName("gaoying") //qiugaoying
name = "liu"
let two = appendToName("gaoying") //qiugaoying

这时输出的结果都为qiugaoying。[name] 将属性name放在闭包的捕获列表,明确地告诉闭包强持有闭包首次调用时的name值。如果要在闭包捕获列表内捕获多个值,则用逗号隔开,如[property, anotherProperty] in 方式。

在我们设计网络层响应结果的时候,Result是大家用的比较多的。和Optional有点类似,它是用来表述返回类型是成功或者失败。在Swift5.0中,Swift团队已经把Result加入到了标准库。这篇文章将讲解到何时去使用Result,以及怎样使用Result。
上篇文章,我们讲到Swift异常处理,在处理时候用do{}catch{}捕捉异常,以及在方法名后通过throws关键字将异常往上层抛。和抛错误不同的是,在代码中用Result作为返回类型可以达到我们需要的效果。更大的不同是,Result可以用于在异步处理时候,如鱼得水般地优雅处理成功与失败的回调返回。在讲解之前,我们先看一下以下片段1代码:

1
2
3
4
5
6
7
8
9
10
func loadData(from url: URL, completion:@escaping (Data?) -> Void) throws {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
throw error
}
if let data = data {
completion(data)
}
}.resume()
}

按照以往的方式,当error有异常时候,我们进行抛出异常,然后方法上也加上throws。假设以上代码编译通过,那么在调用的时候代码片段如下:
1
2
3
4
5
6
7
8
9
do {
try loadData(from: aURL) { data in
print("fetched data")
}

print("This will be executed before any data is fetched from the network.") //这行代码会被先执行。由于loadData方法是异步处理请求数据,就算发生错误,error抛出存在一定的执行时长。这就违背了异常捕捉而中止的性质了,也谈不上异常捕捉。
} catch {
print(error)
}

在swift2.0, 苹果在swift中就介绍了throws 关键字。这方便开发者在写代码时候保证清晰的思路以及在执行时可能出现的错误做异常处理。可做异常捕捉或者忽略异常。这篇文章将详细讲解 throws 关键字,以及在代码中如何去处理异常。

处理异常

如果你在Swift中使用过JSONDecoder,就知道在编写代码时候可抛出异常。让我们来看以下代码片段:

1
2
3
4
5
6
7
8
9
var greeting = "Hello, playground"

do {
let data = greeting.data(using: .utf8)!
let decoder = JSONDecoder()
let string = try decoder.decode(String.self, from: data)
} catch { error
print(error)
}

在Playground 执行代码片段,发现控制台输出Swift.DecodingError异常错误,异常被catch捕捉到了。代码中,我们用do {} catch {} 方式把可能发生的代码一次用try 方式写在do{}里面。当捕捉到一次立马进入catch{error }段。catch将捕捉到Error, 在catch时候,我们也可以捕捉精确的异常进行处理。

在App开发中,数据层往往会做本地缓存,以便在网络不好的情况下能够先显示本地缓存数据。接下来将讲解到如何设计本地数据源中间件来实现业务数据的读取。在讲解之前我们会先了解一下简单泛型在Swift中的应用,结合associated types, 你也可以在协议中使用泛型。

在介绍之前,我刚提到我想要对泛型数据存储在本地,然后还需要一个远程数据接口API提供数据以便在本地数据没有的情况下调用。假设我需要用户信息数据:

1
2
3
4
5
6
7
let localDataStore = UserDataStore()
let remoteDataStore = UserApi()
let dataStore = CacheBackedDataStore(localDataStore, remoteDataStore)

dataStore.fetch(userID) { result in
// handle result
}

简介

文章将简要讲述何时使用@State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject。

@State

@State属性包裹器可用于当你的视图对象响应任何状态改变时候。换句话说,视图初始化@State修饰的属性变量。该属性变量的变更只发生在内部View,而不被外界所改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct StateExample: View {
@State private var intValue = 0

var body: some View {
VStack {
Text("intValue equals \(intValue)")

Button("Increment") {
intValue += 1
}
}
}
}

在内部,SwiftUI将会存储 @State修饰的属性值,然后贯穿整个渲染或重复渲染的生命周期中将持久化存储这个值,当在视图刷新或者重新创建的时候,该属性值可以很好地被视图自身管理。当然,你也可以用private修饰词修饰@State属性,保证只在内部修改@State属性。
@State使用场景:

  • 在被修饰的属性需要响应你视图变化的状态时候
  • 你修饰的是一个值类型(结构体、枚举)

大多数产品在本地Sqlite存储的时候,基本都是用了串行模式。即整个应用全局只有一个sql操作句柄,用单例管理着这个SqlHandler。另外还有一个模式叫线程模式。这种模式下很容易出现Sqlite_Busy错误。

  1. 当有写操作时,其他读操作会被驳回。
  2. 当有写操作时,其他写操作会被驳回。
  3. 当开启事务时,在事务提交之前,其他写操作会被驳回。
  4. 当开启事务时,在事务提交之前,其他事务请求会被驳回。
  5. 当有读操作时,其他写操作会被驳回。
  6. 读操作之间能够并发执行。

第三方库WCDB支持多线程度于读与读,读与写并发执行,写与写串行执行。WCDB 在多线程方面明显优于 FMDB 和 ModelSqliteKit,通过 WCDB 的改造,使得SQLite的性能发挥到极致。