听说南方有你


  • 首页

  • 归档

  • 搜索
close

Swift Non-Nil Values In An Array Of Optionals

发表于 2018-01-30

原文:https://useyourloaf.com/blog/swift-non-nil-values-in-an-array-of-optionals/

我发现除了配合我们超级熟悉的 switch语句,很难想起有别的地方使用 case语句,这里有个很有用的案例,当你需要对可选的数组中非空值做一些操作时。

这是一个使用场景,一个String类型的数组,里面有一些值可能为nil。

1
let names = ["Tom", nil, "Fred"]

names的类型是 [String?], 一个可选的字符串数组,假设我想对数组里每一个非空的值做一些操作:

1
2
3
func doSomething(_ name: String) {
print(name)
}

这个doSomething函数的参数是一个非可选的String类型. 可以通过if let语句遍历数组获取non-nil值 :

1
2
3
4
5
for name in names {
if let name = name { doSomething(name) }
}
// Tom
// Fred

这是记住flatMap高阶函数跳过nil值的好时机:

1
2
3
4
5
for name in names.flatMap({ $0 }) {
doSomething(name)
}
// Tom
// Fred

这个方法是不差,但我喜欢用case语句与可选项匹配的方式:

1
2
3
4
5
for case let name? in names {
doSomething(name)
}
// Tom
// Fred

enum 有两个语句:.none和.some(T)。上面例子可选项(let name?) 是一个简写,用于匹配 .some。我们也可以用.some来写:

1
2
3
for case .some(let name) in names {
doSomething(name)
}

匹配 nil 元素的范式可以这样写:

1
2
3
for case .none in names {
print("found nil")
}

你可以用 ? 范式来匹配可选项中的特定值

1
2
3
for case "Fred"? in names {
print("Found Fred")
}

使用 where 和 case

你可以更进一步,添加一个where子句作为数组的过滤器。

1
2
3
4
5
6
7
let scores = [1,5,8,3,10,nil,7]
for case let score? in scores where score > 6 {
print(score)
}
// 8
// 10
// 7

在执行过滤操作之前,我更喜欢使用flatMap删除nil值,

1
2
3
for score in scores.flatMap({ $0 }).filter({ $0 > 6 }) {
print(score)
}

推荐阅读

Swift Guide to Map Filter Reduce

Reactive-MVVM

发表于 2018-01-15

原文:https://medium.com/@mecid/mastering-mvvm-on-ios-f875d2b99816

译者:Extra Mo

Mastering MVVM on iOS

网上有很多关于app架构的文章,今天,我将展示一些使用MVVM架构开发iOS应用时的技巧。下面我不将介绍其他架构,如果你需要了解更多,这有一篇不错的文章 (https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52).

Why MVVM?

MVC的主要问题是混合责任,这导致了一些问题的出现,比如重量级视图控制器。

我们都知道UIViewController是苹果iOS SDK的主要组件,所有的操作都是在这里面开始和构建。尽管是叫UIViewController,但它更View和来自于MVC(或MVP)的一个经典控制器(或Presenter)相比,因为它里面有viewDidLoad、viewWillLayoutSubviews和其他视图相关方法的回调。这就是为什么我们应该忽略名称中的Controller关键字,并将其作为视图使用,而实际控制器的角色则采用ViewModel。

ViewModel是视图的完整数据表示。每个视图应该仅仅只能拥有一个ViewModel实例。通常,ViewModel使用一个管理器来获取数据并将其转换为试图需要的格式。请看以下列子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
class ItemsViewModel {
var items: [Item] = []
var error: Error?
var refreshing = false
private let dataManager: DataManager
init(dataManager: DataManager) {
self.dataManager = dataManager
}
func fetch(completion: @escaping () -> Void) {
refreshing = true
dataManager.fetchItems { [weak self] (items, error) in
self?.items = items ?? []
self?.error = error
self?.refreshing = false
completion()
}
}
}

这里我们的ViewModel,通过DataManager获取items并将其保存在某个变量中。它还有一个错误和刷新状态的变量,这就带来了在所有需要的条件下构建UI的机会。

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
import UIKit
class ItemsViewController: UIViewController {
@IBOutlet private weak var tableView: UITableView!
private var viewModel: ItemsViewModel
init(viewModel: ItemsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.fetch { [weak self] in
self?.tableView.reloadData()
}
}
}
extension ItemsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.items.count
}
}

正如您在上面看到的,我们的ItemsViewController包含显示列表数据的UITableView。它含有ViewModel实例,并要求它在viewDidLoad回调中获取数据。我们还传递闭包,当数据被获取时,它将重新加载UITableView。 UITableViewDataSource方法也使用ViewModel来获取cell个数。

Reactive Bindings

View和ViewModel之间的绑定是MVVM模式的主要思想,开发人员可以在ViewModel中编写逻辑代码,在View中做布局。在第一个例子中,我们使用了闭包,因为iOS SDK不支持绑定。在实际应用中,您可以使用一些流行的FRP扩展,比如ReactiveCocoa、RxSwift或Bond。我倾向于使用Bond,因为它简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Bond
class ItemsViewModel {
let items = Observable<[Item]>([])
let error = Observable<Error?>(nil)
let refreshing = Observable<Bool>(false)
private let dataManager: DataManager
init(dataManager: DataManager) {
self.dataManager = dataManager
}
func fetch() {
refreshing.value = false
dataManager.fetchItems { [weak self] (items, error) in
self?.items.value = items ?? []
self?.error.value = error
self?.refreshing.value = false
}
}
}

这是相同的ItemsViewModel,但现在我们使用响应式编程来观察变化。

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
class ItemsViewController: UIViewController {
@IBOutlet private weak var tableView: UITableView!
private let activityIndicator = ActivityIndicatorView()
private var viewModel: ItemsViewModel
init(viewModel: ItemsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
bindViewModel()
viewModel.fetch()
}
func bindViewModel() {
viewModel.refreshing.bind(to: activityIndicator.reactive.isAnimating)
viewModel.items.bind(to: self) { strongSelf, _ in
strongSelf.tableView.reloadData()
}
}
func setupUI() {
view.addSubview(activityIndicator)
}
}
extension ItemsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.items.value.count
}
}

让我们看一下bindViewModel方法,这里我们将把ViewModel绑定到视图。每当刷新值改变时,它设置activityIndicator的isAnimating属性。或者当items被更改时,UITableView重新加载。正如您所看到的,在大多数情况下,响应式绑定简化了代码。

ViewModel Composition

有时,我们有多个数据源的复杂视图。例如,在Instagram应用中的用户资料中,我们有一些关于用户的信息以及所有与该用户有关的照片。这里好的做法是将这个逻辑分成两个或多个视图模型。但是我们有一个规则:每个视图应该只有一个ViewModel。在这种情况下,最好的选择是使用ViewModel组合。让我们来看看例子:

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
52
53
54
import Bond
import ReactiveKit
class UserProfileViewModel {
let refreshing = Observable<Bool>(false)
let username = Observable<String>("")
let photos = Observable<[Photos]>([])
private let userViewModel: UserViewModel
private let photosViewModel: PhotosViewModel
init(userManager: UserManager, photoManager: PhotoManager) {
userViewModel = UserViewModel(manager: userManager)
photosViewModel = PhotosViewModel(manager: photoManager)
userViewModel.username.bind(to: username)
photosViewModel.photos.bind(to: photos)
combineLatest(userViewModel.refreshing, photosViewModel.refreshing)
.map { $0 || $1 }
.bind(to: refreshing)
}
func fetch() {
userViewModel.fetch()
photosViewModel.fetch()
}
}
class UserViewModel {
let refreshing = Observable<Bool>(false)
let username = Observable<String>("")
func fetch() {
refreshing.value = true
manager.fetch(user: id) { [weak self] (user, error) in
self?.username.value = "@" + user.username
self?.refreshing.value = false
}
}
}
class PhotosViewModel {
let refreshing = Observable<Bool>(false)
let photos = Observable<[Photo]>([])
func fetch() {
refreshing.value = true
manager.fetch(for user: id) { [weak self] (photos, error) in
self?.photos.value = photos ?? []
self?.refreshing.value = false
}
}
}
view rawViewModelComposition.swift hosted with ❤ by GitHub

正如您所看到的,我们使用UserProfileViewModel,它含有两个视图模型并从它们中获取数据。我们还有一个刷新新的状态,它包含了两个内部视图模型的刷新状态。第二个重要点是在第36行中,ViewModel将数据格式化为所需的表单数据。视图只需要将组件绑定到ViewModel并显示数据。

Conclusion

ViewModel是将表示逻辑层分离到其他层的一种非常简单的方式,它帮助我们避免了重量级的视图控制器,使我们更容易控制和覆盖单元测试。这就是我们的目的,简单而可测试的架构。

Rxswift share

发表于 2018-01-09

原文:https://medium.com/@_achou/rxswift-share-vs-replay-vs-sharereplay-bea99ac42168

RxSwift: share vs replay vc shareReplay

对于使用RxSwift的初学者来说,经常会犯的错误是: 多次订阅同一个observable,却重复执行响应链

例如

1
2
3
4
5
6
let results = query.rx.text
.flatMapLatest { query in
networkRequestAPI(query)
}
results.subscribe(...) // a network request
results.subscribe(...) // another network request

当同一个observable,需要被多次订阅的时候,而我们又不想这个observable对每个订阅者都执行。RxSwift为我们提供了几个操作符share(),replay(),replayAll(),shareReplay(),publish(),and shareReplayLatestWhileConnected来解决.那么我们应该使用哪一个呢?

下面是关于这些操作符的对比

Alt text

Shared subscription: 返回的可观察者共享源observable的一个底层订阅。 所有这些操作符都是如此。

Connectable: 在调用connect()函数之前,返回的observable不会发送事件。这允许多个订阅者在发送任何事件之前订阅。

Reference counting: 返回的observable的引用计数为订阅该observable的个数。当订阅者的个数从0到1时,订阅源观察值。当订阅者从0到1时,取消订阅和处理源观察。注意:每次订阅者的数目从0到1改变的时候,observable都将被重新订阅。
无论如何,同一时间订阅源observable的个数不会超过一个,并且所有并发订阅者将共享相同的订阅源observable。如果底层observable发送了完成或发送、错误的消息,则底层observable可能不会被重新订阅。这是一个灰色区域我建议尽量避免,可以通过确保引用计数下降到0时没有订户来完成。

Replays events: 在这些事件被发送后,操作符将重播事件给订阅源observable的订阅者。对于replay(bufferSize)和shareReplay(bufferSize)操作符,事件的个数最多为bufferSize。 对于shareReplayLatestWhileConnected()来说,最多重播一个事件。当订阅者的引用计数降为零时,重播事件的个数被清空。因此,使引用计数从0到1的订阅者不会收到重播事件。

如果你有Subjects,还可以使用multicast操作符。 但是这里描述的操作符是最流行的,并且能满足大部分使用场景。

fastlane使用指南

发表于 2017-07-31

在这篇教程里面,你讲学习如何使用fastlane将app部署到App Store。

Note:本教程大量使用了命令行,你不需要是一个精通终端者,但要了解一些命令行的基本使用。

学习本教程你需要了解code signing和iTunes Connect。如果你对这些不熟悉,请先阅读这篇文章

开始

下载该教程Demo,放到合适的路径

mZone,

fastlane 运行环境:

  • OS X 10.9 (Mavericks)或者更新
  • Ruby2.0或者更新版本
  • 已付费的苹果开发者账号

因为fastlane是一系列Ruby scripts的集合,所以必须要匹配的Ruby环境。打开终端敲入以下命令可以查看当前Ruby版本

1
ruby -v

在终端里面敲入下面命令可以查看Xcode CLT是否安装

1
xcode-select --install

如果已经安装,你会得到这样的错误:command line tools are already installed, use "Software Update" to install updates.,否者,将为你安装Xcode CLT。

完成上面的操作后,就可以安装fastlane了。继续在终端里面敲入下面命令:

1
sudo gem install -n /usr/local/bin fastlane --verbose

fastlane 工具链

  • produce
    在iTunes Connect和Apple Developer Portal中创建新的iOS应用程序。
  • cert自动创建和维护签名证书。
  • sigh创建,更新,下载和修复配置文件。

设置fastlane

SYPayKit简介

发表于 2017-07-30

问题

一款产品,随着版本的迭代,难免会加入支付功能。而现在的主流支付方式,主要有支付宝、微信、银联支付、苹果银联支付等。
每家支付SDK提供的对外接口不统一,会引起使用起来的混乱,以及维护成本的增加。
比如,在支付成功后由第三方app回调回来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([[url absoluteString] rangeOfString:@"wx"].location != NSNotFound &&
[[url absoluteString] rangeOfString:@"pay"].location != NSNotFound) {
return [WXApi handleOpenURL:url delegate:self];
} else {
/*其他支付方式的处理*/
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
/*处理resultDic*/
}];
return YES;
}
if ([url.host isEqualToString:@"platformapi"]) {//支付宝钱包快登授权返回authCode
[[AlipaySDK defaultService] processAuthResult:url standbyCallback:^(NSDictionary *resultDic) {
/*处理resultDic*/
}];
return YES;
}
}
return YES;
}

接口抽象统一后:

1
2
3
- (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [SYPay handleOpenURL:url sourceApplication:nil];
}

支付流程

  1. 进入支付页面
  2. 选择支付方式
  3. 根据当前支付方式去后台拉取订单,拉取成功后调起当前支付方式对应的app/走网页(如未安装支付宝客户端,银联等)
  4. 在第三方app完成支付后,回调回来,去后台验证订单是否完成支付

NOTE:目前一般的通用做法是支付页面是网页,由网页发起拉取订单的请求,订单部分是后台处理好的订单(web端和native端都不做任何处理),然后web端再把订单通过js交互告诉native端,再由native端调用第三方支付SDK发起支付,支付成功后再回调给web端。

关于支付结果验证部分,目前我们这边是在web端去处理的,native端只负责调用第三方支付SDK,回调支付结果给web端

思路

当去新加入/移除一种支付方式,不会对抽象出来的接口产生影响,也就是说不用做任何修改。

所以就抽象出了一套协议,当新加入一种支付方式时候,只需要实现那套协议即可。

目前的做法是:

  1. 生成某种支付方式的实例

    1
    SYUnionPay *alipay = [[SYUnionPay alloc] init];
  2. 生成订单

    1
    NSDictionary *order = @{kMSPayOrderKey:@"201506221028315777129"};
  3. 发起支付

    1
    2
    3
    [SYPay payment:payment withOrderInfo:order withCompletion:^(SYPayResultStatus status, NSDictionary * _Nullable returnedInfo, NSError * _Nullable error) {
    NSLog(@"success:%d\n,resultDic:%@\n,error:%@", success, returnedInfo, [error localizedDescription]);
    }];

NOTE: 有的做法是把支付方式,放到生成的订单字典里面,由此去区分是哪种支付方式如:

1
NSDictionary *order = @{kMSPayOrderKey:@"201506221028315777129", @"payType": @"alipay"};

使用

1
pod "SYPayKit", :git => 'https://github.com/isandboy/SYPayKit'

默认包含支付宝、微信、银联支付

目前支持以下几种支付方式

Alipay (支付宝)

WXPay(微信)

UnionPay(银联)

1
2
3
pod 'SYPayKit/Alipay'
pod 'SYPayKit/WXPay'
pod 'SYPayKit/UnionPay'

调用部分

1
2
3
4
5
SYUnionPay *alipay = [[SYUnionPay alloc] init];
NSDictionary *order = @{kMSPayOrderKey:@"201506221028315777129"};
[SYPay payment:payment withOrderInfo:nil withCompletion:^(SYPayResultStatus status, NSDictionary * _Nullable returnedInfo, NSError * _Nullable error) {
NSLog(@"success:%d\n,resultDic:%@\n,error:%@", success, returnedInfo, [error localizedDescription]);
}];

接口参数说明:

1
2
3
4
5
6
7
8
/**
* 支付调用接口
*
* @param charge 以kSYPayOrderKey为key的订单,对应的value为格式参考readme说明文档
* @param viewController 银联渠道需要
* @param completionBlock 支付结果回调 Block
*/
+ (void)payment:(SYPayment *)payment withOrderInfo:(NSDictionary *)charge viewController:(nullable UIViewController *)viewController withCompletion:(SYPayResultHandle)handle;

  1. 其中payment是对应的支付方式的实例
  2. charge参数需要和后台约定成以下格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 支付宝支付order
{
kSYPayOrderKey: "partner=\"--------------\"&seller_id=\"-------------\"&out_trade_no=\"-----------\"&subject=\"areyouok\"&body=\"nama\"&total_fee=\"0.01\"&notify_url=\""&service=\"\"&payment_type=\"1\"&_input_charset=\"utf-8\"&it_b_pay=\"30m\"&sign=\"GsSZgPloF1vn52XAItRAldwQAbzIgkDyByCxMfTZG%2FMapRoyrNIJo4U1LUGjHp6gdBZ7U8jA1kljLPqkeGv8MZigd3kH25V0UK3Jc3C94Ngxm5S%2Fz5QsNr6wnqNY9sx%2Bw6DqNdEQnnks7PKvvU0zgsynip50lAhJmflmfHvp%2Bgk%3D\"&sign_type=\"RSA\"&appenv=\"system= ^version=\"&goods_type=\"0\"&rn_check=\"F\""
}
# 微信支付order
{ kSYPayOrderKey: {
"appid": "wx-----------",
"partnerid": "-----------",
"noncestr": "-----------",
"prepayid": "wx-----------",
"packagevalue": "Sign=WXPay",
"timestamp": "-----------",
"sign": "-----------"
}
}
# 银联支付order
{kSYPayOrderKey:@"--------------------" }

更详细的情况可以参考demo里面的SYPayManager类

其他

iOS 9 以上版本如果需要使用支付宝和微信渠道,需要在 Info.plist 添加以下代码:

1
2
3
4
5
6
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
<string>alipay</string>
</array>

如果项目里面还用到了支付宝、微信、银联支付的SDK,请使用

1
2
3
pod 'WechatOpenSDK'
pod 'SYUPPaySDK'
pod 'SYAlipaySDK'

否则会出现和该库冲突的问题。这三个SDK里面分别对应集成了相应的官方SDK,并且支持打包成动态库。

该库可以用于Objective-C、swift工程,并支持打包成动态库。

dynamic framwork

发表于 2017-07-28

问题

在swift工程里面,如果是通过pod去管理公有库,或者团队里面的私有库。需要在Podfile文件里面加入use_frameworks!

但一般的工程里面都要依赖于支付宝、微信等第三方SDK,有些第三方SDK是静态库。
如果是通过Pod 直接引入该静态库,如果只是一级依赖,pod install/update 是可以通过,如果是有多级依赖,执行上面命令就会出现如下错误信息

1
[!] The 'xxxxx' target has transitive dependencies that include static binaries:

正确打包出来的动态库,应该像这样。打开Product/xxxx.app -> Frameworks 文件夹,如下图所示:

Alt text

原理见参考

实战

  1. 通过pod lib create LibraryName 命令创建
  2. 把静态库放到你开发的库里面
  3. 如果当前开发的库里面,除了静态库/动态库(假的)和头文件外,没有其他文件,你需要随便创建个类,否则不行,如果有其他方式一块交流
  4. 关于如果.podspec文件的写法,可以参考:https://github.com/isandboy/SYAlipaySDK

注意

当没有使用到第三方静态库中的相关API时,链接器帮我们链接动态库的时候可能并不会把静态库吸附进来。我们手动在build Setting的other link flags加上-all_load标记

如果是通过 pod lib create 命令去创建模块,可以在.podspec 文件里面加入如下命令

1
spec.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-all_load' }

参考:

组件化-动态库实战

如何查看一个库是静态库还是动态库

1
2
file ***.a
file ***.framework/***

Alt text

Alt text

带dynamically标记的为动态库,有些库虽然也是.framework格式,但实际上是静态库,可以通过上面方式查看

1
2
3
4
5
6
7
8
9
lipo -info ***.a
lipo -info ***.framework/xxx
lipo -thin armv7 xxx.a -output xxx_armv7.a
···
···
···
lipo -create xxx_armv7.a xxx_x86_64.a -output xxx_new.a

Linux 常用命令

发表于 2017-07-28

Linux下查看端口号所使用的进程号:

使用lsof命令: lsof –i:端口号

Linux下查看进程占用端口:

查看程序对应进程号:ps –ef|grep 进程名

iOS多线程

发表于 2017-05-30

多线程

多线程(面试题)汇总

iOS多线程有四套多线程方案:

  • Pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

dispatch_group

NSThread

不常用,主要用到[NSThread currentThread]函数来获取当前线程

GCD (任务和队列)

如果dispatch_group_async里执行的是异步代码dispatch_group_notify会直接触发而不会等待异步任务完成,而dispatch_group_enter、和dispatch_group_leave则不会有这个问题,它们只需要在任务开始前enter结束后leave即可达到线程同步的效果。

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
dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_enter(dispatchGroup);
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
dispatch_async(globalQueue, ^{
sleep(5);
NSLog(@"任务一完成");
dispatch_group_leave(dispatchGroup);
});
});
dispatch_group_enter(dispatchGroup);
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
dispatch_async(globalQueue, ^{
sleep(8);
NSLog(@"任务二完成");
dispatch_group_leave(dispatchGroup);
});
});
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"notify:任务都完成了");
});
2018-08-21 21:38:20.803437+0800 Demo[19819:13724794] 任务一完成
2018-08-21 21:38:23.800334+0800 Demo[19819:13724792] 任务二完成
2018-08-21 21:38:23.800677+0800 Demo[19819:13724748] notify:任务都完成了

如果block块里面执行的是同步代码,则以用dispatch_group_enter dispatch_group_leave和dispatch_group_async实现效果一样

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
方式一:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task A");
sleep(5);
NSLog(@"task a done");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task B");
sleep(3);
NSLog(@"task b done");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
方式二:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"task A");
sleep(5);
NSLog(@"task a done");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task B");
sleep(3);
NSLog(@"task b done");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});

串行队列

  1. 同步串行,不会创建新线程,各个任务在主线程中,依次执行
  2. 异步串行,只创建一次新线程,各个任务依次执行
    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
    // create queue
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queuename", DISPATCH_QUEUE_SERIAL); //DISPATCH_QUEUE_CONCURRENT
    // 串行同步不会创建线程
    dispatch_sync(queue, ^{
    NSLog(@"serial queue sync task1 --%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    NSLog(@"serial queue sync task2 --%@", [NSThread currentThread]);
    });
    // 串行异步会创建线程,当前队列中第一次异步会创建线程之后不会在重新创建
    dispatch_async(queue, ^{
    NSLog(@"serial queue async task1 --%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"serial queue async task2 --%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    NSLog(@"serial queue sync task3 --%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"serial queue async task3 --%@", [NSThread currentThread]);
    });
    serial queue sync task1 --<NSThread: 0x600000069480>{number = 1, name = main}
    serial queue sync task2 --<NSThread: 0x600000069480>{number = 1, name = main}
    serial queue async task1 --<NSThread: 0x60000066cdc0>{number = 6, name = (null)}
    serial queue async task2 --<NSThread: 0x60000066cdc0>{number = 6, name = (null)}
    serial queue sync task3 --<NSThread: 0x600000069480>{number = 1, name = main}
    serial queue async task3 --<NSThread: 0x60000066cdc0>{number = 6, name = (null)}

并发队列

  1. 同步并发,不会创建新线程,各个任务在主线程中,依次执行
  2. 异步并发,每async一次创建一次新线程,任务的执行顺序会根据处理内容和系统状态发生改变

NSOperation & NSOperationQueue

NSOperation是面向对象的。是在GCD基础上封装的,NSOperation和 NSOperationQueue分别对应GCD的任务和队列,可以设置最大并发量,task之间的依赖关系等

一般不直接用NSOperationQueue,用它的几个子类来操作,否者需要自己管理线程

练习

  1. 子线程同时执行ABC三个同步任务、全部执行完成再在子线程执行三个同步任务EDF
    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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    方式一: group
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.emoney.cn", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
    NSLog(@"task A");
    });
    dispatch_group_async(group, queue, ^{
    NSLog(@"task B");
    });
    dispatch_group_async(group, queue, ^{
    NSLog(@"task C");
    });
    dispatch_group_notify(group, queue, ^{
    dispatch_sync(queue, ^{
    NSLog(@"task D, thread:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    NSLog(@"task E");
    });
    dispatch_sync(queue, ^{
    NSLog(@"task F");
    });
    });
    方式二:dispatch_barrier_async
    The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_sync function.
    官方说明大意:在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用
    dispatch_queue_t queue = dispatch_queue_create("com.emoney.cn", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
    NSLog(@"task A, thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"task B, thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"task C, thread:%@", [NSThread currentThread]);
    });
    NSLog(@"ABC");
    dispatch_barrier_async(queue, ^{
    NSLog(@"task ABC Done");
    });
    dispatch_async(queue, ^{
    NSLog(@"task D, thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"task E, thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    NSLog(@"task F, thread:%@", [NSThread currentThread]);
    });
    NSLog(@"DEF");
    方式三:NSOperationQueue dependency
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task A, thread:%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task B, thread:%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task C, thread:%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationD = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task D, thread:%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationE = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task E, thread:%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationF = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task F, thread:%@", [NSThread currentThread]);
    }];
    [operationD addDependency:operationA];
    [operationD addDependency:operationB];
    [operationD addDependency:operationC];
    [operationE addDependency:operationA];
    [operationE addDependency:operationB];
    [operationE addDependency:operationC];
    [operationF addDependency:operationA];
    [operationF addDependency:operationB];
    [operationF addDependency:operationC];
    [queue addOperation:operationA];
    [queue addOperation:operationB];
    [queue addOperation:operationC];
    [queue addOperation:operationD];
    [queue addOperation:operationE];
    [queue addOperation:operationF];

dispatch_semaphore(信号量)的理解及使用

问题描述:

假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢?

或者

我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。

定义:

1、信号量:就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。

其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。

注意,正常的使用顺序是先降低然后再提高,这两个函数通常成对使用。 (具体可参考下面的代码示例) 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});

Cocoapods 使用

发表于 2017-05-01

简介

CocoaPods是Swift和Objective-C项目的依赖管理器,它有超过2.6万个库并被超过160万个app使用, CocoaPods可以帮助您优化扩展项目。
CocoaPods管理的第三方开源库配置在https://github.com/CocoaPods/Specs 库中。

安装

1
2
3
4
//安装最新版本
sudo gem install cocoapods
//安装指定版本
sudo gem install cocoapods -v 1.1.1

NOTE:在这里给大家推荐一款pod版本管理工具podenv

地址:https://github.com/kylef-archive/podenv

卸载

1
sudo gem uninstall cocoapods

NOTE:一般国内用户安装失败,是因为ruby源问题。因为ruby的软件源rubygems.org,使用的是亚马逊的云服务,但被我天朝屏蔽了。

一般的解决方式有两种:

  1. 移除https://rubygems.org 源,添加https://gems.ruby-china.org/ ,操作请看下面关于gem sources 命令介绍
  2. 翻墙,关于如何翻墙问题,网上教程说的很详细,这里就不在累述。

作为一名开发者,推荐使用第二种方式

当cocoapods安装后,可以通过pod --version命令来查看当前安装的cocoapods版本

其他问题可参考:安装CocoaPods遇到的问题及解决办法

使用

  1. 使用terminal切换到你的project目录
  2. 创建一个Podfile文件,你可以使用touch Podfile创建,但是有一种更为优雅的方式,通过在terminal执行pod init命令
  3. 在你的Podfile文件里面为所欲为吧,关于Podfile的配置,请参考cocoapods官方链接:https://guides.cocoapods.org/syntax/podfile.html
  4. 保存Podfile文件,运行pod install
  5. 以后打开你的项目都使用ProjectName.xcworkspace而非ProjectName.xproject

pod install vs pod update

pod install和pod update命令,也许大家只会在第一次使用cocoapods配置project的时候使用一次pod install,甚至一次都没用过,一般都直接使用pod update命令了

但是:pod install和pod update有什么区别呢,分别在什么时候使用呢?

NOTE:

  • install一个新的pods在你的project中,即使在你的project里面已经有一个Podfile文件或者之前已经执行了pod install命令
  • 使用pod update [PODNAME],仅仅当你想要更新pods到一个新的版本的时候

pod update会更新所有Podfile文件里面所指定的依赖到一个新的版本。

pod update [PODNAME]仅仅会更新[PODNAME] 库

pod install

它不仅适用于当你第一次为你的project去检索新的pods的时候,也适用于你每次编辑Podfile文件(如:添加,更新,或者移除一个pod的时候)

但是有以下需要注意:

  • 每次执行pod install命令,会根据Podfile.lock文件已经明确列出的库以及对应的版本去下载并安装pods,
    而不会去检查相应的库是否有新的版本。其中Podfile.lock文件会记录已经引入的pod库和相应的版本。
  • 当你pods没有在Podfile.lock文件列出的时候,它会去根据Podfile文件里面所描述的依赖去下载(如:pod ‘MyPod’, ‘~> 1.2’)

NOTE:

pod outdated

当你使用pod outdated命令时,Cocoapods会列出比Podfile.lock文件中的pod更新的所有pods。这个时候你就可以使用pod update PODNAME1 PODNAME2去更新PODNAME1和PODNAME2的版本(pod update 后面可以跟多个pod)

pod update

当你执行pod udpate PODNAME命令的时候,它将会更新该库到一个满足在Podfile文件里面对该库限制的一个最新版本,而不会关心Podfile.lock文件里面对该库有没有限制

pod install/update [–no-repo-update]

执行pod install/update 命令会首先更新本地的配置库

执行pod install/update --no-repo-update 命令不会更新本地的配置库

本地配置库位置可以通过以下命令查看

1
2
cd ~/.cocoapods/repos
ls

关于Cocoapods的使用更加详细的介绍:可以参考 最新的 CocoaPods 的使用教程 系列文章介绍
http://ios.jobbole.com/90957/

master文件夹是官方的open sources library

gem sources

关于查看当前电脑的ruby源,有如下一些命令需要了解

1
2
3
gem sources -l #查看当前已经制定的源
gem sources -r SOURCE_URI #移除源
gem sources -a SOURCE_URI #添加源

详情请参考:gem sources --help 来查看

Cocoapods创建私有库

发表于 2017-05-01

这篇文章主要是介绍怎么去创建和维护一个Cocoapods

  1. Using Pod Lib Create
  2. Getting setup with Trunk

Getting setup with Trunk

使用Pod创建库,打开终端敲入以下命令:

1
pod lib create MyLirary

NOTE: 如果你想要使用你自己的pod-template去创建Pod库,你可以在上面的命令后面追加--template-url=url参数,其中url是你的git repo库地址。
如果不追加参数默认拉取的模板地址是https://github.com/CocoaPods/pod-template

敲入以上命令回车后,会让你回答几个问题,按照提示填入就行。

创建Project之后会,会自动运行pod install命令,让我们来看看Cocoapods给我们生成的文件目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ tree MyLib -L 2
MyLib
├── .travis.yml
├── _Pods.xcproject
├── Example
│ ├── MyLib
│ ├── MyLib.xcodeproj
│ ├── MyLib.xcworkspace
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ └── Tests
├── LICENSE
├── MyLib.podspec
├── Pod
│ ├── Assets
│ └── Classes
│ └── RemoveMe.[swift/m]
└── README.md
  • .travis.yml - 一个travis-ci配置文件.
  • _Pods.xcproject - 支持Carthage的对你Pod’s Project的链接.
  • LICENSE - 默认是 MIT License.
  • MyLib.podspec - 你的Library的配置文件.
  • README.md - a default README in markdown.
  • RemoveMe.swift/m - 无用文件删除即可.
  • Pod - 放置你的库代码和资源的文件夹
  • Example - Demo,方便你去调试你的库

Getting setup with Trunk

关于怎么发布自己的Pods到Cocoapods trunk,

请参考:

  1. http://www.tuicool.com/articles/6FF7fi
  2. https://guides.cocoapods.org/making/getting-setup-with-trunk.html

说明:关于 .podspec 文件的写法说明也可以参考CocoaPods学习系列4——进阶用法

Private Pods

1. Create a Private Spec Repo
参考:https://guides.cocoapods.org/making/private-cocoapods.html

验证podspec有效性

当开发完私有库之后,把配置库,提交到私有创库,或者cocoapods的公有库仓库,流程如下

1. 本地验证仓库有效性

1
pod lib lint --source --use-libraries --allow-warnings --verbose

其中pod lib lint 之后的参数都是可选参数

  • –source:当开发私有库的时候,
  • –use-libraries: podspec文件里面通过pod dependencies指定的依赖,是否包含静态库,当不加此参数会验证失败。因为swift开发的库,不能包含静态库,所以此参数只针对Objective-C
  • –allow-warnings:是否忽略警告。
  • –verbose:详细log

2. 提交代码

1
2
3
4
git add .
git ci -m"edit commit message"
git tag 0.1.0
git push origin master tags

3. 推送podspec配置文件到私有库

1
pod repo push SYSpecs [MyLib.podspec] --use-libraries --allow-warnings --verbose

其中参数与验证参数一个含义,验证的时候加了哪些可选参数,推送配置文件到私有库的时候也要加

12
Allen

Allen

点点滴滴

13 日志
© 2019 Allen
由 Hexo 强力驱动
主题 - NexT.Mist