GCD
,另一个是操作队列(Operation Queues)
。GCD是一套基于C的底层API,而操作队列是在GCD基础上的面相对象抽象,GCD提供了更加底层的控制,而操作队列则在GCD之上实现了一些方便的功能。Operations(操作)
以一种面向对象的方式来封装需要执行的异步任务,是一套较为高级的API。Operations在Swift中主要有Operation
和BlockOperation
两个类,他们可以各自单独使用,也可以被添加到Operation Queue(操作队列)
中执行任务。
Operation是操作的一个基类,我们一般可以通过继承的方式来实现一些自定义的操作类,例如通过自定义操作类我们能够更改操作的执行方式或者打印作业执行过程中的状态变化。如果我们直接继承Operation实现的操作类是同步(Sync)执行的,我们也可以通过手动管理state来实现操作类的异步(Async)执行。下面我们将自定义实现一个AsyncOperation:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52class AsyncOperation: Operation {
enum State: String {
case Ready, Executing, Finished
fileprivate var keyPath: String {
return "is" + rawValue
}
}
var state = State.Ready {
willSet {
willChangeValue(forKey: newValue.keyPath)
willChangeValue(forKey: state.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: state.keyPath)
}
}
}
extension AsyncOperation {
// NSOperation Overrides
override var isReady: Bool {
return super.isReady && state == .Ready
}
override var isExecuting: Bool {
return state == .Executing
}
override var isFinished: Bool {
return state == .Finished
}
override var isAsynchronous: Bool {
return true
}
override func start() {
if isCancelled {
state = .Finished
return
}
main()
state = .Executing
}
override func cancel() {
state = .Finished
}
}
实现了AsyncOperation之后,我们可以通过以下步骤调用:
1.继承AsyncOperation
2.重写main方法,在main方法中执行Async操作
3.在Async的callback方法中将state置为.Finished
调用示例:1
2
3
4
5
6
7
8
9
10class MyLoginOperation: AsyncOperation {
override func main() {
RequestAPI.login(params, callback: {
self.state = .Finished
// TODO: login success
})
}
}
let op = MyLoginOperation()
OperationQueue().addOperation(op)
BlockOperation,继承自Operation,可以并发的执行一个或多个block,只有当所有的block都执行完毕,整个操作才算执行完毕。示例代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17let blockOperation = BlockOperation {
sleep(2)
print("🍎")
}
blockOperation.addExecutionBlock {
print("🍏")
}
blockOperation.addExecutionBlock {
print("🍐")
}
blockOperation.addExecutionBlock {
print("🍇")
}
blockOperation.completionBlock = {
print("all works cmplete!")
}
blockOperation.start()
示例输出:1
2
3
4
5🍇
🍏
🍐
🍎
all works cmplete!
上面我们提到了我们可以调用operation自身的start方法出发作业执行,但是在日常开发中,我们往往采用将operation添加到一个指定的queue中来执行作业。操作队列是OperationQueue
的一个实例,任务一旦被添加到操作队列中不久便会自动执行操作,所以如果需要设置操作或队列的一些属性,需要在其被添加到队列之前设置,在添加到队列之后的设置将不会生效。操作队列默认是一个并行队列,我们可以通过maxConcurrentOperationCount
这个属性设置队列的并发数,当并发数为1的时候,操作队列就是一个串行队列。操作队列默认是(Async)异步执行的,我们也可以通过调用waitUntilAllOperationsAreFinished()
方法或将addOperations(_ ops: [Operation], waitUntilFinished wait: Bool)
方法的waitUntilFinished
属性设置为true,使其成为同步(Sync)同步队列。下面通过示例代码来详细讲解操作队列的使用。
1.首先我们创建了4个operation和一个操作队列:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let op1 = BlockOperation {
sleep(2)
print("🍎")
}
let op2 = BlockOperation {
print("🍏")
}
let op3 = BlockOperation {
print("🍐")
}
let op4 = BlockOperation {
print("🍇")
}
let queue = OperationQueue()
2.然后我们设置了队列的并发数为4,并将4个操作对象添加到了操作队列中:1
2queue.maxConcurrentOperationCount = 4
queue.addOperations([op1,op2,op3,op4], waitUntilFinished: true)
示例输出1:1
2
3
4🍏
🍐
🍇
🍎
这个结果我们并不感到意外,由于是并发队列,并且op1的作业中含有延时操作,所以前三个的输出顺序并不一定,但是第四个输出的一定是🍎。如果我们把上面第2步的代码修改如下:1
2queue.maxConcurrentOperationCount = 4
queue.addOperations([op1,op2,op3,op4], waitUntilFinished: false)
示例输出2:1
2
3🍏
🍐
🍇
此时队列并发异步执行操作,由于op1的延时处理,op1的执行将被直接返回,所以最终输出结果中并没有op1的输出。如果将上面的代码修改如下:1
2queue.maxConcurrentOperationCount = 1
queue.addOperations([op1,op2,op3,op4], waitUntilFinished: true)
示例输出3:1
2
3
4🍎
🍏
🍐
🍇
此时操作队列是一个串行队列,所以程序执行2s后依次执行队列中的操作,按FIFO的顺序输出了结果。如果我们将上面的代码修改为如下:1
2queue.maxConcurrentOperationCount = 1
queue.addOperations([op1,op2,op3,op4], waitUntilFinished: false)
示例输出4:1
是的,代码执行后控制台将没有任何输出。由于我们设置队列的并发数为1,所以当前只有一个线程用于任务调度,同时我们设置了waitUntilFinished
为false,说明这是一个异步执行的队列,所以当执行op1延时函数时,唯一的线程直接返回了,所以后续的操作都不会被执行。
如果在开发过程中我们想要操作按我们指定的顺序来执行,operation为我们提供了一种十分便捷的方式,operation支持互相设置依赖,如op1依赖于op2,op2依赖于op3,那么操作的执行顺秀就会是op3->op2->op1。示例代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let op1 = BlockOperation {
print("🍎")
}
let op2 = BlockOperation {
print("🍏")
}
let op3 = BlockOperation {
print("🍐")
}
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
op1.addDependency(op2)
op2.addDependency(op3)
queue.addOperations([op1,op2,op3], waitUntilFinished: false)
示例输出:1
2
3🍐
🍏
🍎
另外操作队列可以非常方便的进行挂起和恢复操作,我们可以通过队列的isSuspended
属性设置队列的挂起和恢复,但是队列的挂起,不会影响已经被添加到队列中的操作,只有后续被添加到队列的操作会收到影响。我们直接看示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let op1 = BlockOperation {
print("🍎")
}
let op2 = BlockOperation {
print("🍏")
}
let op3 = BlockOperation {
print("🍐")
}
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.addOperations([op1,op2], waitUntilFinished: false)
queue.isSuspended = true
queue.addOperation(op3)
示例输出:1
2🍎
🍏
在实际开发过程中,我们常常将一些耗时操作放到子线程,待操作完成后,我们切回main线程,进行一些UI相关的操作。其实这一过程可以用操作队列很方便的实现。1
2
3
4
5
6OperationQueue().addOperation {
// TODO: some time consuming operations
OperationQueue.main.addOperation {
// TODO: some UI operations
}
}
通过上面的介绍和使用,我们发现操作队列还是比较灵活和方便的,可以很方便的添加依赖。虽然它是高层级的API,相对于底层API来说可控制的权限会少一些,性能上可能稍有偏差,但在一定的场景下,还是很值得推荐的。
GCD是Apple提供的另一套基于C的高性能底层API,利用它我们可以进行高性能的并发编程。Swift3之前它还保持了C语言的调用分格,Swift3之后Apple对其进行了重新封装,新的API更符合面向对象思维,所有的任务都会被包装到一个函数或者闭包中,可读性更强,也更便于使用了。
Dispatch queues(调度队列)
可以很方便的创建并执行(Async)异步和(Sync)同步任务。例如你可以将一些耗时任务添加到一个函数或者block,然后将其添加到调度队列中执行。GCD默认实现了一些队列,我们也可以通过GCD提供的方法创建自定义队列。
在GCD中如不进行特殊处理默认创建的都是串行队列。用代码说话,我们可以通过如下方式创建串行队列:1
let queue1 = DispatchQueue(label: "SerialQueue1")
向队列中添加作业可以通过如下方式,同时你还可以控制队列中的任务将以(Async)异步或(Sync)同步的方式执行。1
2
3
4
5
6
7
8
9
10
11
12queue1.sync {
print("🍊 \(Thread.current)")
}
queue1.async {
sleep(2)
print("🍎 \(Thread.current)")
}
queue1.sync {
print("🍐 \(Thread.current)")
}
示例输出:1
2
3🍊 <NSThread: 0x100a061e0>{number = 1, name = main}
🍎 <NSThread: 0x100a0b410>{number = 2, name = (null)}
🍐 <NSThread: 0x100a061e0>{number = 1, name = main}
调度队列中的main主队列就是一个串行队列,它只能存在于主线程之中。所以我们不能够往主队列中添加(Sync)同步操作,因为同步操作会堵塞主线程。
系统默认实现了一个全局的并发队列,如果没有什么特殊需求一般我们直接可以拿来使用,示例如下:1
2
3
4
5
6DispatchQueue.global().async {
// TODO: some time consuming operations
DispatchQueue.main.async {
// TODO: some UI operations
}
}
这是我们日常开发中常用的一种方式,我们一般将比较耗时的操作放入全局并发队列,待耗时任务完成切换回主队列进行UI相关的操作。如果我们有特殊需求,同样可以自定义并发队列:1
let queue = DispatchQueue(label: "concurrencyQueue", attributes: .concurrent)
如果在实际开发中我们希望多个网络请求都返回了在执行相应的操作,我们可以使用Dispatch Group很方便的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14let group = DispatchGroup()
let queue = DispatchQueue(label: "groupQueue")
queue.async(group: group) {
print("🍏")
}
queue.async(group: group) {
print("🍎")
}
group.notify(queue: queue) {
print("all tasks complete")
}
示例输出:1
2
3🍏
🍎
all tasks complete
在开发中我们经常会遇到一些延时操作,Dispatch queue提供了一套非常简单的方法。示例如下:1
2
3
4let queue = DispatchQueue(label: "queuename")
queue.asyncAfter(deadline: .now() + 2) {
// TODO: some task
}
本文列举了GCD在并发编程中常见的一些操作,还有一部分关于Dispatch Sources的没有展开,希望感兴趣的朋友可以自行研究。
我认为在并发编程中,在相同的场景下,如果实现难度相当,我们首选GCD,毕竟它是比较底层的实现,性能也会出色些。但是如果有的场景操作队列实现起来方便得多,我们完全可以直接使用操作队列实现。另外,选择操作队列还是GCD也因人而异,不管是哪一种只要使用起来得心应手,都是可以的。
]]>自动
反映在 ViewModel,反之亦然。于是我就想利用业余时间好好学习一下RxSwift,理解这种响应式的方式是如何实现的。RxSwift官方文档中写到了所有Observable
的实例都相当于Swift中的Sequence
,并且和Sequence
相比它还能异步的接受参数;ObservableType.subscribe(_:)
就相当于Swift中的Sequence.makeIterator()
,不同的是在RxSwift中不需要手动的调用next()
方法,Observable
就能自动的将事件发送给Observer
。这就是RxSwift的本质,一切都建立在这个基础上。
虽然已经很清楚的明白的RxSwift的本质所在,但是不搞清楚Observable
和Observer
这两个之间的关系,我始终对RxSwift一团污水。其实通过字面意思我们就可以知道Observable
是可以被观察的意思,所以它是被观察者
,那么Observer
就是观察者
,在RxSwift中Observer
实现了ObserverType
,这是一个protocol
。ObserverType
约定了一个方法on(_ event: Event<E>)
用于封装被订阅者发送的事件回调。事件回调分为next
、error
和completed
三种。同理,Observable
也实现了一个协议ObservableType
,ObservableType
主要约定的方法为subscribe<O: ObserverType>(_ observer: O)
,subscribe
方法开放了自身被订阅的入口,观察者可以通过这个方法订阅被观察对象。
这里我省略了RxSwift的一些语法教程,因为本文主要是想要和大家分享我对RxSwift响应式实现的理解。如果有不理解的地方可以直接到github上下载RxSwift-master,里面的Rx.playground
是很好的一份RxSwift语法教程。进入正题,以下是一段我用RxSwift写的伪代码,模拟了用户发起网络请求,并将网络请求的结果异步返回给观察者。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24let observable = Observable.create({ (observer) -> Disposable in
let request = MyRequestAPI.get(url, ( (result, error) -> {
if let err = error {
observer.onError(err)
}
else if let response = result {
observer.onNext(response)
observer.onComplete()
}
})
return AnonymousDisposable {
request.cancel()
}
})
observable.subscribe(onNext: { (response) in
// receive data do some thing
}, onError: { (err) in
// error do some thing
}, onCompleted: {
// complete request do some thing
}, onDisposed: {
// deinit
})
在上面这段代码中,我通过create
方法创建了一个observable
实例,将MyRequestAPI的返回职能移交给了一个observer
对象,当网络请求异常时,我会调用observer.onError()
,这意味着,只要监听了这个observable
对象,就会接收到错误的消息。事实上在第二段代码中我正是这么做的,通过对observable
的subscribe
,分别对网络请求的成功返回、异常、结束、释放等状态进行了监听。从而能够在相应的事件到来之际,进行相应的操作。
现在我们已经对RxSwift的思维有所了解。但心中可能还是有一个疑问,RxSwift是如何实现响应式这种方式的?通过阅读Observable.create
的源码,我从中找到了答案。在Rx内部有一个集合Observers,在我们执行监听方法subscribe
的时候,以上面代码的onNext
为例,此时在其内部会执行_observers.insert(observer.on)
这样一段代码,就是将这种状态的监听加入到集合中,待我们调用observer.onNext(response)
方法的时候,它会去遍历_observers
,如果集合中存在相同的状态类型,则执行对应状态的eventHander
闭包。
如果要使用RxSwift实现一个MVVM架构的应用,那上面提到的应用知识只是冰山一角,毕竟RxSwift是一个很庞大的项目,他还有一个名字类似的仓库叫RxCocoa,基本上来说,RxCocoa给所有的 Cocoa 类建立了扩展方法,从而可以让UI视图建立诸如rx.text
之类的东西。这样我们就不需要像上面一样手动创建Observable对象,可以少写一点subscribeNext方法,从而在多个不同的视图点中将值和观察值之间建立关联。类似下面的代码这样。1
2
3
4
5
6
7
8
9
10
11
12let textField = UITextField()
let delegate = TextFieldDelegate()
textField.delegate = delegate
var rxDidChange = false
_ = textField.rx.text
.skip(1) // Initial value
.subscribe(onNext: { _ in
rxDidChange = true
}, onCompleted: {
completed = true
})
以上是我对RxSwift主要思想的理解,旨在帮助想要入坑RxSwift的朋友更好地入门。毕竟RxSwift是一个比较庞大的体系,通过这次学习还有很多RxSwift的知识和语法没有了解,我也会在这个基础上,继续学习。
]]>WCDB(WeChat DataBase)是微信官方的移动端数据库组件,它基于SQLCipher,是一个关系型数据库,支持iOS, macOS和Android。WCDB提供了三个基础类进行数据库操作:WCTDatabase、WCTTable、WCTTransaction。它们的接口都是线程安全的。WCDB几乎涵盖了常用的数据库操作,同时还开放了核心层接口,方便用户扩展一些未封装的复杂SQL操作。安全方面它继承了SQLCipher的加密方式,内建了Repair Kit用于修复损坏的数据库,通过内建宏的方式实现了ORM(Object Relational Mapping)
,方便地实现了对象属性到数据表字段的映射,通过一种名为WINQ
的规则对SQL进行了抽象,避免了冗长的SQL胶水代码,也防止了SQL注入。
WCDB使用内置的宏来连接类、属性与表、字段。共有三类宏,分别对应数据库的字段、索引和约束。所有宏都定义在WCTCodingMacro.h中。WCDB的ORM会自动识别property的类型,并映射到适合的数据库类型。其与Objective-C类型对应关系为:
关于ORM官方github文档给出了详细的中英文教程包括字段宏、索引宏、约束宏等在此不一一列举。
WINQ避免了冗长复杂的SQL拼接操作,对SQL语句进行了抽象。抽象的思路还是从SQL语法规则出发,SQL的语法规则实则上是一种链式操作,可以理解为一些固定的keyword、object和expr的集合。所以WINQ的实现思路是将固定的keyword,封装为函数名,作为连接;将可以展开的token,封装为类,并在类内实现其不同的组合。这句话可能说的有点绕,通过下面的代码块可能比较好理解些。在下面的代码块中,把SELECT
定义成了一个类StatementSelect
,这个类有三个函数,函数名我们并不陌生,正是where
、limit
、having
三个SQL关键字,同时函数的返回也是一个StatementSelect对象,这样我们就能实现一种链式操作。实现了上面提到的连接
。另外我们可以发现三个函数还有一个共同点,它们的参数都是Expr类型,因为在实际的SQL语法规则中where、limit、having等操作都是接受表达式的,所以这里StatementSelect的类方法都是表达式。然后在Expr内部可以实现表达式的各种组合。1
2
3
4
5
6
7
8class StatementSelect : public Statement {
public:
//...
StatementSelect &where(const Expr &where);
StatementSelect &limit(const Expr &limit);
StatementSelect &having(const Expr &having);
//...
};
1 | class Expr : public Describable { |
基于这个抽象方式,就可以对复杂查询中的条件语句进行重写为:1
2
3
4
5
6
7
8
9
10
11
12
13Column content("content");
Column createTime("createTime");
Column modifiedTime("modifiedTime");
Column type("type");
StatementSelect select;
//...
//WHERE content IS NOT NULL
// AND createTime!=modifiedTime
// OR type NOT BETWEEN 0 AND 2
select.where(Expr(content).isNotNull()
&&Expr(createTime)!=Expr(modifiedTime)
||Expr(type).notBetween(0, 2));
//...
WCTDatabase表示一个数据库,可以进行所有数据库操作,包括增删查改、表操作、事务、文件操作、损坏修复等。对于同一个路径的数据库,不同的WCTDatabase、WCTTable、WCTTransaction对象共享同一个WCDB核心。因此,你可以在代码的不同位置、不同线程任意创建不同的基础类对象,WCDB会自动管理它们的共享数据和线程并发。1
2
3
4
5
6NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"test.db"];
_database = [[WCTDatabase alloc] initWithPath: path];
[_database close:^{
[_database removeFilesWithError:nil];
}];
1 | if (![_database isTableExists:@"student"]) { |
1 | Student *student = [[Student alloc] init]; |
1 | return [_database getOneObjectOfClass:Student.class |
以上是我通读了WCDB的iOS官方文档和API做的一些总结,其中有一些自己的理解,同时也概括了WCDB的核心理念和使用方法。另外还有一些关于数据库修复、事务、约束的操作,没有详细列举。可以直接访问其github里面的中英文教程讲的非常详细。另外截止到2016年12月微信全球共计8.89亿月活用户,而新兴的公众号平台拥有1000万个,WCDB作为支撑这么一个平台的移动端数据库,其安全性和稳定性应该还是值得信赖的。如果是纯OC的项目,如果觉得CoreData学习成本高,不容易掌握,不是很稳定的话,推荐可以使用WCDB,但是项目中所有用到WCDB依赖的类实现文件都必须改成.mm
的形式。但如果是Swift或者OC/Swift混编项目,WCDB目前对Swift还不是很友好,希望WCDB团队后续能对Swift进行支持吧。
现在,WCDB的Swift版本已经发布了,官方wiki地址:关于 WCDB Swift
]]>首先我们创建GoodsViewController
和GoodsDetailViewController
分别作为商品页面和商品详细页面。创建Shop.storyboard
,以及两个ViewController对应的视图如下。
创建CustomTransitioningDelegate
代理,用于dismiss方法回调。
1 | protocol CustomTransitioningDelegate { |
CustomTransitioningAnimator
,作为我们所有转场动画的总代理。没错!转场导读需要花多少时间、转场该以怎样的动画进行都有它来控制。“点击购物车”按钮的事件处理如下,在这里比较重要的是我们需要设置modalPresentationStyle
为Custom
类型,并把transitioningDelegate
设置为CustomTransitioningAnimator
类的实例animator
.
1 | @IBAction func addGoodsToShopCart(sender: UIBarButtonItem) { |
由于我们上一步中提到的transitioningDelegate
是UIViewControllerTransitioningDelegate
类型的,所以我们的Animator(CustomTransitioningAnimator以下都简称Animator)首先需要实现UIViewControllerTransitioningDelegate
协议。
UIViewControllerInteractiveTransitioning
协议,幸运的是系统为我们提供了UIPercentDrivenInteractiveTransition
类,此类本生就实现了UIViewControllerInteractiveTransitioning
协议,支持百分比变换,并在此基础上扩展updateInteractiveTransition
、finishInteractiveTransition
、cancelInteractiveTransition
等方法,大大简化了手势驱动。UIViewControllerAnimatedTransitioning
协议1 | extension CustomTransitioningAnimator: UIViewControllerTransitioningDelegate { |
在非手势的情况下,上面提到了我们需要实现UIViewControllerAnimatedTransitioning
协议。UIViewControllerAnimatedTransitioning
的方法介绍:
1 | public func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval // 用于设置转场动画的时间 |
非手势情况实现具体代码如下:
1 | // 非手势情况 |
手势操作我们上面提到了,只要继承UIPercentDrivenInteractiveTransition
这个类,就可以很方面的实现手势操作以及百分比动画。UIPercentDrivenInteractiveTransition
有几个主要的方法介绍如下:
1 | public func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning) // 实现自`UIViewControllerInteractiveTransitioning`协议,手势刚触发时调用 |
手势操作百分比动画的具体实现如下:
1 | class CustomTransitioningAnimator: UIPercentDrivenInteractiveTransition { |
Github地址:HCustomTransition
]]>fastlane是一组工具套件,旨在实现iOS应用发布流程的自动化,并且提供一个运行良好的持续部署流程,只需要运行一个简单的命令就可以触发这个流程。
fastlane是一个ruby脚本集合,其中囊括了向苹果商店提交新应用或更新已有应用所需要的最常用任务。
首先需要安装ruby环境,然后在命令行中输入:
sudo gem install gym
使用CocoaPods
管理的工程在执行完pod install
之后生成的workspace文件
显示在Xcode的左上角,run图标的右边,设备选择的左边
在Xcode中选项目,Info tab下会列出所有的configuration,一般有Debug和Release两种
在Xcode中选target,在Build Settings tab下搜索Provisioning Profile,默认应该是Automatic,点击看到下拉列表中的就是所有可用的名称
清理项目
生成archive
导出ipa
上传到fir
1 | #!/bin/bash |
生成器(Generators)与序列(Sequences)构成了Swift式循环。
提到数组我们就会想到遍历,一般的遍历可能都是从头到尾进行的。但是如果你有特殊的需求呢。你可能不想呆板的进行遍历。这时候Generators就可以派上用场了。
Generators的存在是进行特殊癖好的数组遍历,其筛选出符合该癖好的下标索引到数组没有元素为止。
任意一个generator都需要遵从如下协议:
1 | protocol GeneratorType { |
根据上述协议,该协议需要元素类型以及一个next()函数。
举个倒序索引的generator:
1 | class CountdownGenerator: GeneratorType { |
输出:
Element 2 of the array is C
Element 1 of the array is B
Element 0 of the array is A
优点:
尽管这个小例子看起来有点小题大做,可生成器却封装了数组序列值的计算。如果你想要用另一种方式排序序列值,我们只需要更新生成器,而不必再修改这里的代码。
Generators在循环过程中每个元素提供的服务是一次性的。所以我们想做倒回操作的话需要生成一个新的generator。若不想这样则需要用上sequence,其遵从另外一个协议SequenceType:
1 | protocol SequenceType { |
通过协议我们知道每个sequence都与一个generator类型已经一个generator构造器绑定在一起。我们可以使用这个遍历sequence。
举个栗子,我们可以使用CountdownGenerator来定义一个sequence从而生成一个倒序的数组。
1 | struct ReverseSequence<T>: SequenceType { |
输出:
Index 2 is C
Index 1 is B
Index 0 is A
Swift在处理序列时有一个特别的语法。不同于创建一个序列的关联生成器,你可以编写一个for-in循环。比如,我们也可以将上面的代码段写成这样:
1 | for i in ReverseSequence(array: xs) { |
输出:
Index 2 is C
Index 1 is B
Index 0 is A
实际上,Swift做的只是使用generate方法生成了一个生成器,然后重复地调用其next函数直到返回nil。
优点:
对比之前仅仅使用生成器的例子,同一个序列可以被第二次遍历–为此我们只需要调用generate来生成一个新的生成器就可以了。
]]>iOS9之前,我们经常使用MPMoviePlayerController
实现视频、音频文件的播放。iOS9之后,MPMoviePlayerController被苹果弃用,苹果推荐使用AVPlayerViewController
,AVPlayerViewController的功能比MPMoviePlayerController更为强大。单独使用AVPlayer我们就可以实现视频和音频两种文件的播放。由于其基于AVFoundation和AVKit,所以在开始使用之前我们需要先导入AVFoundation
框架。具体实现如下:
如果需要加载本地音频文件只需修改资源的后缀为音频对应的后缀名,如MP3。
let filePath = NSBundle.mainBundle().pathForResource("赵传 - 每次都想呼喊你的名字", ofType: "mp3")
1 | import UIKit |
AVFoundation支持三种视频填充模式:
1 | AVLayerVideoGravityResizeAspect保留长宽比,未填充部分会有黑边 |
1 | lazy var playerController: AVPlayerViewController = { |
AVAudioPlayer封装了播放单个音频的能力。可以通过NSURL或NSData来初始化,AVAudioPlayer不具备流媒体播放能力,但是我们能够通过网络请求后获取的NSData间接实现播放网络资源。首先,我们同样需要导入AVFoundation框架,具体实现如下:
1 | import UIKit |
- 加载网络音频文件,我们的策略是通过网络请求获取音频流的NSData数据,然后使用AVPlayer的初始化方法 获取player实例,进行播放;
- 对于iOS7以上的系统(含iOS7),在确定文件格式的情况下可以使用-initWithData:fileTypeHint:error:和-initWithContentsOfURL:fileTypeHint:error:生成实例,或者把NSData保存为对应后缀名的文件后使用-initWithContentsOfURL:error:后再生成实例;
- 对于iOS7以下的系统,在确定文件格式的情况下,最为安全的方法是把NSData保存为对应后缀名的文件后使用-initWithContentsOfURL:error:生成实例。
关于加载网络音频的更多注意事项,可以参见“码农人生”的技术博客:
AVAudioPlayer的1937337955错误研究
AVPlayer
支持本地和网络流媒体播放,AVPlayerViewController实际上是AVPlayer的封装,如果我们想要自定义一款流媒体视频或音频播放器,我们可以简单的使用AVPlayer实现。AVPlayer的具体实现如下:
此处的url可以上本地媒体资源url也可以是网络资源url
1 | let playerItem = AVPlayerItem(URL: NSURL(string: "url")!) |
此处利用KVO监听player的currentItem的status和loadTimeRanges两个属性。status是表示player状态的一个枚举,只有当status状态为ReadyToPlay时,我们才能对资源进行播放,并获取音频时长等信息。通过对loadTimeRanges属性的实时监听,获取资源的缓冲进度。
1 | public enum AVPlayerStatus : Int { |
1 | // 添加监听 |
播放:player.play()
暂停:player.pause()
获取当前播放时间:timeScale指的是1秒需要有几个帧构成(fps),所以要得到秒数,需要用如下方式
1 | let currentTime = Double(player.currentTime().value) / Double(player.currentTime().timescale) |
获取总时长:
1 | let duration = Double(player.currentItem!.duration.value) / Double(player.currentItem!.duration.timescale) |
拖动进度条:player.seekToTime(time: cmTime)
这个方法可以指定播放到哪一个时刻,参数是一个CMTime类型的数据,CMTime是一种用于表征媒体资源时间的专用类。我们可以通过CMTime构造方法创建CMTime实例。
1 | timeScale = player.currentItem.duration.timeScale |
1 | enum VerticalAlignment { |
1 | class HLable: UILabel { |
1 | let label = HLabel() |