f22x


  • 首页

  • 归档

textField响应

发表于 2016-01-04   |   分类于 objective-c

UITextField是我们常用的控件,它是如何响应用户事件的呢,看了一下它的视图层次如下:

这是一个简单的textField,当点击它的时候,生成了UIFieldEditor(如果没有设置clearButton的话它的frame等于textField),
UIFieldEditor上有一个_UIFieldEditorContentView, _UIFieldEditorContentView负责展示文字并包含一个UITextSelectionView(光标)。这些由textField生成的编辑系列视图以textField为父视图并总是处于父视图的最顶层,如果直接在textField上添加控件的话,这些控件将不能够被系统响应。

Facebook的BFTask简介

发表于 2015-10-26   |   分类于 objective-c

要创建一个真正具有响应式的iOS应用程序,你必须保持长时间的运行以便操作UI线程,并注意避免任何延迟事件阻塞UI线程。这意味着你将需要在后台执行各种操作。为方便起见,我们增加了一个类BFTask。一个可以表示异步操作结果的任务(task)。通常情况下,一个BFTask从异步函数返回,并给出一个继续处理任务结果的能力。当一个任务从一个函数返回它已经开始做的工作。一个任务是不依赖于特定的线程模型:它代表正在做的工作,而不是在那里执行。task比异步编程的其它方法,诸如回调有更多优点。 BFTask是不能代替NSOperation或GCD的。事实上,他们发挥得很好。但是BFTask填补了它们中的一些空白,以及这些技术没有解决​​的一些问题。

BFTask负责管理依赖。与使用NSOperation的依赖管理相比,你没有必要开始BFTask之前声明的所有依赖关系。例如,假设你需要保存一组对象,每个人可能或不可能需要保存子对象。在NSOperation,您通常需要提前创建操作为每个子操作节省时间。但是,你总是不知道你何时开始工作,是否它是必要的。这让管理依赖关系的NSOperation非常痛苦。即使在最好的情况下,必须依赖于它们的操作,导致出现了比它执行不同的命令代码之前创建的依赖。使用BFTask,您可以在操作的过程中决定是否将子任务返回,在一个很短的时间内。

BFTasks释放依赖关系。NSOperation强引用了这些依赖项,所以如果你有队列顺序地执行这些依赖项,你的应用将会产生泄漏,因为每次的操作永远会被retain。BFTasks会释放它们的回调,当这些操作在运行中的时候,所以清理BFTask后,它持有的一切东西都将被清空。这可以减少内存使用,并简化内存管理。

BFTasks跟踪完成任务的状态:它跟踪是否有返回值,任务被取消,或者如果发生错误。它还具有方便的方法来返回错误信息。在NSOperation里,你自己必须建立所有这些东西。

BFTasks不依赖于任何特定的线程模型。所以它可以简单地使用一个操作队列执行几个任务,当其他的操作使用block或者GCD的时候。这些任务可以互相依赖并且无缝连接。

在连续执行多个任务时无需创建嵌套的“金字塔”的代码,你会只使用回调。

BFTasks是完全可组合的,允许您执行分支,并行和复杂的错误处理,而无需命名很多回调的意大利面条式(意指包含复杂庞大控制结构,杂乱无章的代码,特别是大量使用goto语句的代码等等)代码。

你可以安排基于任务的代码的顺序来执行,而不必使用分散的回调函数分开你的逻辑。

对于本文档中的例子,假设有异步版本的一些常用的解析方法,称为saveAsync:和findAsync:它返回一个Task。在后面的章节中,我们将介绍如何将自己定义这些功能。

The continueWithBlock Method

每个BFTask都有一个方法continueWithBlock: 包含有一个延时的block。当任务完成后,这个block将会执行。然后,您可以检查任务是否成功,并获得其结果。

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

// Objective-C
[[self saveAsync:obj] continueWithBlock:^id(BFTask *task) {
if (task.isCancelled) {
// the save was cancelled.
} else if (task.error) {
// the save failed.
} else {
// the object was saved successfully.
PFObject *object = task.result;
}
return nil;
}];

// Swift
self.saveAsync(obj).continueWithBlock {
(task: BFTask!) -> BFTask in
if task.isCancelled() {
// the save was cancelled.
} else if task.error() {
// the save failed.
} else {
// the object was saved successfully.
var object = task.result() as PFObject
}
}

BFTasks使用Objective-C的block,所以语法应该是非常简单的。让我们来看看一个例子。

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

// Objective-C
//异步获取一个NSString。
- (BFTask *)getStringAsync {
//我们假设getNumberAsync返回BFTask,其结果是一个NSNumber。
return [[self getNumberAsync] continueWithBlock:^id(BFTask *task) {
//这个延续块返回的NSNumber BFTask作为输入,
//并提供一个NSString作为输出。
NSNumber *number = task.result;
return [NSString stringWithFormat:@"%@", number];
)];
}

// Swift
//Gets an NSString asynchronously.
func getStringAsync() -> BFTask {
//Let's suppose getNumberAsync returns a BFTask whose result is an NSNumber.
return self.getNumberAsync().continueWithBlock {
(task: BFTask!) -> NSString in
// This continuation block takes the NSNumber BFTask as input,
// and provides an NSString as output.
let number = task.result() as NSNumber
return NSString(format:"%@", number)
}
}

在很多情况下,你希望做更多的工作,如果之前的任务并没有传递任何错误或取消操作,那么它将会走成功的回调。要做到这一点,使用continueWithSuccessBlock:方法,而不是continueWithBlock :

1
2
3
4
5
6
7
8
9
10
11
12
13
14

// Objective-C
[[self saveAsync:obj] continueWithSuccessBlock:^id(BFTask *task) {
// the object was saved successfully.
return nil;
}];

// Swift
self.saveAsync(obj).continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// the object was saved successfully.
return nil
}

Chaining Tasks Together

BFTasks是有点不可思议,因为它们允许你把它们链接起来而无需嵌套。如果从continueWithBlock返回BFTask : 然后continueWithBlock返回的任务将不被视为完成,直到新任务的新的连续的block返回之后。这使您可以避免金字塔式代码,你将获得回调与执行多个操作。同样,你可以从continueWithSuccessBlock返回BFTask:。因此,返回的BFTask将做更多的异步工作。

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

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Student"];
[query orderByDescending:@"gpa"];
[[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
NSArray *students = task.result;
PFObject *valedictorian = [students objectAtIndex:0];
[valedictorian setObject:@YES forKey:@"valedictorian"];
return [self saveAsync:valedictorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
PFObject *valedictorian = task.result;
return [self findAsync:query];
}] continueWithSuccessBlock:^id(BFTask *task) {
NSArray *students = task.result;
PFObject *salutatorian = [students objectAtIndex:1];
[salutatorian setObject:@YES forKey:@"salutatorian"];
return [self saveAsync:salutatorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
// Everything is done!
return nil;
}];

// Swift
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var valedictorian = students.objectAtIndex(0) as PFObject
valedictorian["valedictorian"] = true
return self.saveAsync(valedictorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
var valedictorian = task.result() as PFObject
return self.findAsync(query)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var salutatorian = students.objectAtIndex(1) as PFObject
salutatorian["salutatorian"] = true
return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// Everything is done!
return nil
}

Error Handling(错误处理)

通过仔细选择是否调用continueWithBlock:或continueWithSuccessBlock :,可以控制应用程序中的错误传播。使用continueWithBlock:您可以通过他们来处理错误信息。你可以把失败的任务当作抛出异常。事实上,如果你在continue回调时抛出(返回)一个异常,由此产生的后续任务将会产生故障与异常。

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

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Student"];
[query orderByDescending:@"gpa"];
[[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
NSArray *students = task.result;
PFObject *valedictorian = [students objectAtIndex:0];
[valedictorian setObject:@YES forKey:@"valedictorian"];
//强制此回调失败。
return [BFTask taskWithError:[NSError errorWithDomain:@"example.com"
code:-1
userInfo:nil]];
}] continueWithSuccessBlock:^id(BFTask *task) {
//现在,这个continue将被跳过。
PFQuery *valedictorian = task.result;
return [self findAsync:query];
}] continueWithBlock:^id(BFTask *task) {
if (task.error) {
//这个错误处理程序将被调用。
// 该错误将是上面返回的error.
//让我们通过返回一个新值处理错误。
//该任务将以nil作为其值返回。
return nil;
}
//这里也将被跳过。
NSArray *students = task.result;
PFObject *salutatorian = [students objectAtIndex:1];
[salutatorian setObject:@YES forKey:@"salutatorian"];
return [self saveAsync:salutatorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
//一切都完成了!这里被调用。
//任务的结果是nil
return nil;
}];

// Swift
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var valedictorian = students.objectAtIndex(0) as PFObject
valedictorian["valedictorian"] = true
//Force this callback to fail.
return BFTask(error:NSError(domain:"example.com",
code:-1, userInfo: nil))
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
//Now this continuation will be skipped.
var valedictorian = task.result() as PFObject
return self.findAsync(query)
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
if task.error() {
// This error handler WILL be called.
// The error will be the NSError returned above.
// Let's handle the error by returning a new value.
// The task will be completed with nil as its value.
return nil
}
// This will also be skipped.
let students = task.result() as NSArray
var salutatorian = students.objectAtIndex(1) as PFObject
salutatorian["salutatorian"] = true
return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// Everything is done! This gets called.
// The tasks result is nil.
return nil
}


这是很方便的,在最后始终保持成功的一个回调链,而且只有唯一一个错误处理程序。

Creating Tasks(创建任务)

当你入门之后,你可以使用来自像findAsync方法返回的任务:或saveAsync : 然而,对于更高级的方案,你可能想使用自己的任务。要做到这一点,您可以创建一个BFTaskCompletionSource。这个对象可以让你创建一个新的BFTask,并控制其是否被标记为已完成或取消。创建BFTask后,您将需要调用的setResult : setError:或取消触发它的continuations。有了这些工具,你会很容易使自己的异步函数返回任务。例如,你可以使用fetchAsync的基于任务的版本:

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

- (BFTask *) fetchAsync:(PFObject *)object {
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
[object fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
[task setResult:object];
} else {
[task setError:error];
}
}];
return task.task;
}

// Swift
func fetchAsync(object: PFObject) -> BFTask {
var task = BFTaskCompletionSource()
object.fetchInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error == nil {
task.setResult(object)
} else {
task.setError(error)
}
}
return task.task
}

同样地,很容易创建saveAsync : findAsync:或deleteAsync :

Tasks in Series(系列任务)

BFTasks很方便,当你想要执行一个串行的任务,每个任务都会等待上一个任务完成。例如,假设你要删除你的博客上所有的评论。

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

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
[query whereKey:@"post" equalTo:@123];

[[[self findAsync:query] continueWithBlock:^id(BFTask *task) {
NSArray *results = task.result;

/ /创建一个简单的完成任务的基本情况。
BFTask *task = [BFTask taskWithResult:nil];
for (PFObject *result in results) {
//对于每个对象,继续任务与函数删除的项目。
task = [task continueWithBlock:^id(BFTask *task) {
//返回一个已完成的被标记的任务,当删除完成的时候。

return [self deleteAsync:result];
}];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
//每个评论都被删除。

return nil;
}];

// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)
findAsync(query).continueWithBlock {
(task: BFTask!) -> BFTask in
let results = task.result() as NSArray

// Create a trivial completed task as a base case.
let task = BFTask(result:nil)
for result : PFObject in results {
// For each item, extend the task with a function to delete the item.
task = task.continueWithBlock {
(task: BFTask!) -> BFTask in
return self.deleteAsync(result)
}
}
return task
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
// Every comment was deleted.
return nil
}

// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)
findAsync(query).continueWithBlock {
(task: BFTask!) -> BFTask in
let results = task.result() as NSArray

// Create a trivial completed task as a base case.
let task = BFTask(result:nil)
for result : PFObject in results {
// For each item, extend the task with a function to delete the item.
task = task.continueWithBlock {
(task: BFTask!) -> BFTask in
return self.deleteAsync(result)
}
}
return task
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
// Every comment was deleted.
}

Tasks in Parallel(并行任务)

您还可以同时执行多个任务,使用taskForCompletionOfAllTask​​s:方法。您可以一次启动多个作业,并且,使用taskForCompletionOfAllTask​​s:将会创建一个新的被标记完成的任务,当所有的任务都完成时。只有当所有传入的任务取得成功,新的任务才算是成功。并行执行的操作会比串行时更快,但可能会消耗更多的系统资源和带宽。

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

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
[query whereKey:@"post" equalTo:@123];

[[[self findAsync:query] continueWithBlock:^id(BFTask *results) {
//收集执行删除任务的Task到一个数组。
NSMutableArray *tasks = [NSMutableArray array];
for (PFObject *result in results) {
//开始立即删除,并添加到任务列表。
[tasks addObject:[self deleteAsync:result]];
}
//返回将被标记为完成时,当所有的删除动作完成时.
return [BFTask taskForCompletionOfAllTasks:tasks];
}] continueWithBlock:^id(BFTask *task) {
// 每个评论都被删除
return nil;
}];

// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)

findAsync(query).continueWithBlock {
(task: BFTask!) -> BFTask in
// Collect one task for each delete into an array.
var tasks = NSMutableArray.array()
var results = task.result() as NSArray
for result : PFObject! in results {
// Start this delete immediately and add its task to the list.
tasks.addObject(self.deleteAsync(result))
}
// Return a new task that will be marked as completed when all of the deletes
// are finished.
return BFTask(forCompletionOfAllTasks:tasks)
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
// Every comment was deleted.
return nil
}

Task Executors(任务执行器)

无论continueWithBlock:和continueWithSuccessBlock:这些方法都有另一种形式,采用BFExecutor的一个实例。这些都是continueWithExecutor:withBlock:和continueWithExecutor:withSuccessBlock : 这些方法允许你控制延续事件是如何执行的。默认的执行器将会使用GCD,但你可以提供自己的执行器分派工作到不同的线程。例如,如果你想继续在UI线程上工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//创建一个使用主线程BFExecutor。
BFExecutor *myExecutor = [BFExecutor executorWithBlock:^void(void(^block)()) {
dispatch_async(dispatch_get_main_queue(), block);
}];

//而使用这样的主线程执行程序。执行程序仅适用于新的延续事件,通过continueWithBlock。
[[self fetchAsync:object] continueWithExecutor:myExecutor withBlock:^id(BFTask *task) {
myTextView.text = [object objectForKey:@"name"];
}];

一般的情况下,如在主线程上调用,我们提供BFExecutor的默认实现。这些实现包括defaultExecutor,immediateExecutor,mainThreadExecutor,executorWithDispatchQueue:和executorWithOperationQueue : 例如:

//继续在主线程,采用了内置的执行者。
[[self fetchAsync:object] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
myTextView.text = [object objectForKey:@"name"];
}];

Task Cancellation(取消任务)

这不是一个好的设计:用来跟踪BFTaskCompletionSource的注销与否。一个更好的模型是建立一个“取消标记”的顶层,并传递给你想做相同的“取消操作”的一部分,为每个异步函数。然后,在你的continuation blocks中,你可以检查取消标记是否已被取消,如果取消则返回[BFTask cancelledTask].例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

- (void)doSomethingComplicatedAsync:(MYCancellationToken *)cancellationToken {
[[self doSomethingAsync:cancellationToken] continueWithBlock:^{
if (cancellationToken.isCancelled) {
return [BFTask cancelledTask];
}
// Do something that takes a while.
return result;
}];
}

// Somewhere else.
MYCancellationToken *cancellationToken = [[MYCancellationToken alloc] init];
[obj doSomethingComplicatedAsync:cancellationToken];

//When you get bored...
[cancellationToken cancel];


注:取消标记实现应该是线程安全的。我们很可能会…像这样加入一些概念到Bolts在将来的某个时候。

swift思维 第四部分:map all the things!

发表于 2015-10-16   |   分类于 swift

原文地址

Array vs. Optional

所以,作为一个提醒,我们在前面的文章中了解到,Array的函数map()和flatmap:

map( transform: T -> U ) -> Array
flatMap( transform: T -> Array ) -> Array

这意味着,给定一个变换:T-> U的用户可将T的数组转换为U的数组,只需使用(变换:T-> U)在你的Array中,它会返回一个数组Array

好了,这并不奇怪,map()和flatmap的返回值(Optional)是如此的相似:

map( transform: T -> U  ) -> Optional<U>
flatMap( transform: T -> Optional<U> ) -> Optional<U>

map() on Optionals

那么,map方式是如何返回Optional类型的(T?)?

嗯,这很简单 :与Array同样的方法,它内部是Optional的,使用transform:T->U来进行转换,并且把结果包装起来形成新的Optional

你想想看,这非常像Array.map所做的:它可以转换每个内在值(resp.Optional)并且返回一个新的Array(U)(resp.Optional)包含着转换过的新值。

回到我们的例子

那么,怎样才能应用这一切在代码中呢?

在上个版本的代码中,我们有一个itemDesc[“icon”]做了隐式转换(String?),并且我们想要把它最终转化成为UIImage;但是UIImage(named:)使用String作为参数,而不是String?(Optional)类型,所以我们需要使用一个方法的内部值String,并且仅仅当可选变量不为nil的情况。

一个解决办法是使用Optional绑定来做到这一点:

1
2
3
4
5
6
7
8

let icon: UIImage?
if let iconName = itemDesc["icon"] as? String {
icon = UIImage(named: iconName)
} else {
icon = nil
}

但是,对于这样一个简单的操作,我们写了太多行。

在前面的例子中,我们使用了(非常不优雅)替代解决方案,采用了nil,合并运算符??:

1
2
3
4

let iconName = itemDesc["icon"] as? String
let icon = UIImage(named: iconName ?? "")

上面的代码能够工作,仅因为如果iconName是nil,我们将欺骗的UIImage的初始化和使用UIImage(name:” “),这确实会返回一个nil的图像。但是,这样并不能让人感觉干净整洁,我们有点滥用初始化,使其在这里工作。

让我们使用map

那么,为什么不使用map?的确,我们想要解包我们的Optional如果它不为空的话,转换内部值为UIImage并且返回UIIamge,那么究竟怎么使用呢?

我们试一试:

1
2
3
4

let iconName = itemDesc["icon"] as? String
item.icon = iconName.map { imageName in UIImage(named: imageName) }

等等,上面的代码不能编译。你能猜出是为什么吗?

发生了什么?

问题出在UIImage(name:…)方法返回了一个可选类型:如果name没有值,那么它将无法创建UIImage,这样就使得这个初始化是失败的,在这种情况下将返回nil。

因此,这里的问题是,我们给闭包映射一个String,并返回……一个UIImage?类型 –初始化失败并且返回nil的类型。并且如果你再次检查map内部,他将要形成T(imageName) -> U(UIImage(name: ImageName))的闭包,并且返回U?。所以在这种情况下,U相当于UIImage?并且如果整个map表达式返回U?那么最终将返回的将是UIImage??,是的,两个Optional,oh boy!

flatMap()来救援

flatMap与map相似,但是它使用T->U?转换(而不是T->U),并且它是”flattens”的它的结果仅有一级的optional。这正是我们期望的!

1
2
3
4

let iconName = itemDesc["icon"] as? String
item.icon = iconName.flatMap { imageName in UIImage(named: imageName) }

因此,这里就是flatMap实际所做的:

1、如果iconName为nil,flatMap将直接返回nil(但是类型为UIImage?)
2、如果iconName不为nil,flatMap适用于转换内部值,因此试图使用String创建UIImage,并且返回结果–如果已经是UIImage?因此其可以是nil,如果UIImage初始化失败的话。

总之,item.icon只会有一个非空值,如果 itemDesc[“icon”]as? String 为非空并且UIImage(named:imageName)初始化成功。

这样感觉好多了,比欺骗初始化的??更加符合习惯。

使用 init闭包

说远一点,上面也可以使用一个更紧凑的编码方式,如Xcode中7现在通过类型的.init属性公开这些类的构造函数。

这意味着,UIImage.init实际上已经是一个函数,它接受一个字符串,返回一个UIImage?因此,我们可以直接使用它作为参数传递给我们的flatMap,没必要把它放在闭包里!

1
2
3
4

let iconName = itemDesc["icon"] as? String
item.icon = iconName.flatMap(UIImage.init)

好吧,我觉得这是难以阅读和个人更喜欢使用一个明确的闭包在这里,为了使代码更清晰,更加明确。但是,这是个人喜好的问题而已,这是一件好事,你知道这是可能的!

我们最终的swift代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

struct ListItem {
var icon: UIImage?
var title: String
var url: NSURL

static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
guard let jsonData = jsonData,
let json = try? NSJSONSerialization.JSONObjectWithData(jsonData, options: []),
let jsonItems = json as? Array<NSDictionary> else { return [] }

return jsonItems.flatMap { (itemDesc: NSDictionary) -> ListItem? in
guard let title = itemDesc["title"] as? String,
let urlString = itemDesc["url"] as? String,
let url = NSURL(string: urlString)
else { return nil }
let iconName = itemDesc["icon"] as? String
let icon = iconName.flatMap { UIImage(named: $0) }
return ListItem(icon: icon, title: title, url: url)
}
}
}

回头看看我们的ObjC代码

花一点时间来与ObjC代码比较,我们的WIFT代码。我们从那里适应了相当多的东西!

如果你仔细观察ObjC与swift的代码,你就会意识到,swift的代码行数不会明显地缩短,但它的方式更安全。

特别是,关于swift,我们学会了,guard?,try?和as?它迫使我们必须检查一切不确定的类型,那些ObjC代码不会操心的和会导致崩溃的潜在问题。因此,也许它们的代码一样大小,但ObjC代码的更加危险!

结论

有了这个系列文章,我希望你明白,你不应该尝试把ObjC代码直接转化为swift。相反,尝试重新思考你的代码,重构它。通常更好的方式是从一个clean的状态开始重写你的代码时考虑到swift的特性,而不是试图直接翻译ObjC代码。

我不是说这很容易。改变你的思维方式,当你已经习惯了ObjC,并使用其模式和方法来编写代码还可能需要一些时间。但swift肯定更好。

swift思维 第三部分:struct

发表于 2015-10-12   |   分类于 swift

这次让我们使用struct来简化我们的代码

在本系列的上一篇文章中,我们学习了如何使用array的map和flatMap方法,从而避免出现有状态的中间变量,并且使用一些函数式编程。
翻译自

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

class ListItem {
var icon: UIImage?
var title: String = ""
var url: NSURL!

static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
guard let nonNilJsonData = jsonData,
let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []),
let jsonItems = json as? Array<NSDictionary>
else {
return []
}

return jsonItems.flatMap { (itemDesc: NSDictionary) -> ListItem? in
guard let title = itemDesc["title"] as? String,
let urlString = itemDesc["url"] as? String,
let url = NSURL(string: urlString)
else { return nil }
let li = ListItem()
if let icon = itemDesc["icon"] as? String {
li.icon = UIImage(named: icon)
}
li.title = title
li.url = url
return li
}
}
}

今天,我们要做一个非常简单的变化,它会使我们的代码更轻量,看起来更像swifteer。

struct vs class

我们刚刚使用swift的时候通常使用class,这不难理解,因为在oc中class无处不在。使用class并没有什么错误,你当然可以在swift中继续使用它们。但是在swift中,struct拥有更强大的功能相对于c语言来说。它们不再局
限于一些拥有值的字段。

相对的,swift的struct拥有class相同的能力,除了继承,它还拥有值类型(所以当你使用其他的变量时,都会复制它们,就像Int)而class是引用类型,通过引用用而不是复制,在objective-c中(它们遍布了每个地方)下一个是转贴,请参见watch this excellent talk from Andy Matuschak on that subject关于两者的解释。

转换我们的class为struct

在这种情况下,struct似乎更适合,因为它承载值,并且不会被某些意外所改变(复制胜于引用)。假如我们要使用它们作为数据源例如一个菜单,并且它们并非旨在一定要创建中间量,所以这种情况是有意义的。

此外,struct的优势在于他们有一个隐含的构造函数在默认的情况下,如果你不定义:我们可以使用它的默认构造函数列表项ListItem(icon:…,titile:…,url:…)。

最后但是并非最不重要的,因为我们现在还不能创建一个伪造的列表项,因为我们消除了上面的数据损坏的问题,我们可以消除tittle的默认值,但更重要的是,我们可以save that last pony ??通过将NSURL!转换为NSURL。

所以接下来的代码像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

struct ListItem {
var icon: UIImage?
var title: String
var url: NSURL

static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
guard let nonNilJsonData = jsonData,
let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []),
let jsonItems = json as? Array<NSDictionary> else { return [] }

return jsonItems.flatMap { (itemDesc: NSDictionary) -> ListItem? in
guard let title = itemDesc["title"] as? String,
let urlString = itemDesc["url"] as? String,
let url = NSURL(string: urlString)
else { return nil }
let iconName = itemDesc["icon"] as? String
let icon = UIImage(named: iconName ?? "")
return ListItem(icon: icon, title: title, url: url)
}
}
}

现在,我们使用一步来创建ListItem,因为struct为一个默认的init()内包含所有的参数,如果你不提供任何自己的初始化参数。我们可用class做相同的事,但是一旦有class我们就不得不初始化自己。

合并运算符

在上面的例子中,我还使用了新的技巧,使用?操作符得到一个默认情况下iconName为nil的情况。

这里的??操作符类似于Objective-c的opt?:val号运算符,这意味着,如果opt是类型T的?VAL必须是T型的。

所以在这里iconName??””让我们用一个nil的情况下的iconName的映射,我们知道会导致nil的UIImage(icon=nil)的这种情况。

⚠️注意⚠️:这不是处理一个空的iconName或者有一个nil的UIImage最好的和干净的方式。事实上,它甚至显得有点丑陋,以及使用一个假的“”符号创建一个空的图像。但是,这是一个机会,向您展示??的用法……哎,我们也保留了一些不错的东西,作为本作的下一部分。

有问题请回复至

swift思维 第二部分:array的map方法

发表于 2015-09-28   |   分类于 swift

翻译自 今天这篇文章主要讲解map和flatmap在Arrays中的应用

以前的代码

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

class ListItem {
var icon: UIImage?
var title: String = ""
var url: NSURL!

static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
guard let nonNilJsonData = jsonData,
let class ListItem {
var icon: UIImage?
var title: String = ""
var url: NSURL!

static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
guard let nonNilJsonData = jsonData,
let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []),
let jsonItems = json as? Array<NSDictionary>
else {
// If we failed to unserialize the JSON or that JSON wasn't an NSArray,
// then bail early with an empty array
return []
}

var items = [ListItem]()
for itemDesc in jsonItems {
let item = ListItem()
if let icon = itemDesc["icon"] as? String {
item.icon = UIImage(named: icon)
}
if let title = itemDesc["title"] as? String {
item.title = title
}
if let urlString = itemDesc["url"] as? String, let url = NSURL(string: urlString) {
item.url = url
}
items.append(item)
}
return items
}
}

map()介绍

map()是Array中的一个方法,它包含一个函数来作为参数,这就解释了怎么样把数组的每个元素转换成新的。这个方法仅通过解释如何转换X->Y来允许转换一个数组[x]为[Y],而不需要创建一个临时的可变数组。(关于这一段,需要了解函数式编程的相关内容,简单来说,函数作为处理”值”而存在,我们输入一个值,会得到另一个值。而数据类型作为对值的封装,不仅包含值,还包含相关的属性和方法。这里把array这个数据类型而不是值通过map()函数进行处理,我们通过这个过程,拿到中间状态NSDictionary来自己”组装”函数,最后,函数将帮我们返回另一种数据类型ListItem。详情见这篇博文以及原文)

因此,在我们的例子中,我们利用这个特性代替for循环,我们可以使用jsonItems(包含几组NSDictionary的JSON数组)的map方法–将每个字典替换成ListItem实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

return jsonItems.map {(itemDesc: NSDictionary) -> ListItem in
let item = ListItem()
if let icon = itemDesc["icon"] as? String {
item.icon = UIImage(named: icon)
}
if let title = itemDesc["title"] as? String {
item.title = title
}
if let urlString = itemDesc["url"] as? String, let url = NSURL(string: urlString) {
item.url = url
}
return item

这可能看起来像一个简单的变化,但它使我们能够专注于“如何将一个NSDictionary转化成ListItem”的问题 - 这是我们的问题的核心,并且 - 更重要的是避免了需要建立一个中间可变数组(像我们在ObjC做的那样)。如果可能的话,我们要始终避免可变(mutable)状态。

数据损坏

另一个需要解决的问题是,目前我们所使用的代码仍然会创建一个ListItem(并将它包含在最后的数组中)即使我们输入了不正确的数据。因此,如果其中的一些NSDictionary是无效的,我们的数组最终还会包含一些空的ListItem()对象,实际上它们在真正的意义上是不存在的。

更重要的是,我们仍然 killing some ponies 🐴 因为我们还在使用NSURL!并且我们的代码还是允许我们创建一个没有NSURL的ListItem(item.url不会受到影响,如果我们没有一个有效的“URL”键),如果我们试图访问这些无效的NSURL! 我们的代码将崩溃。

为了解决这个问题,我们可以返回一个nil的ListItem,如果输入的参数是无效的。这比一个会引起错误的或者空的ListItem更合适。

1
2
3
4
5
6
7
8

return jsonItems.map { (itemDesc: NSDictionary) -> ListItem? in
guard …/* condition for valid data */… else { return nil }
let realValidItem = ListItem()
… /* fill the ListItem with the values */
return realValidItem
}

但是,如果我们要使用新的NSDictionary - >ListItem?与jsonItems.map变换,它会产生一个[ListItem?],其中可能包含包含nil,我们仍然输入了无效的NSDictionary项。虽然这样做比以前好,但仍然不是非常实用的处理方法。

使用flatMap()

该flatMap()派上用场了。

flatMap()类似map(),但它采用的是T-> U?转换,而不是T-> U,如果该转换的返回值是nil,那么不加此项到输出数组。

语义上,flatMap和map很像,就像map消除nil值的扁平化结果。

运用这一原理,我们给出了下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

return jsonItems.flatMap { (itemDesc: NSDictionary) -> ListItem? in
guard let title = itemDesc["title"] as? String,
let urlString = itemDesc["url"] as? String,
let url = NSURL(string: urlString)
else { return nil }
let li = ListItem()
if let icon = itemDesc["icon"] as? String {
li.icon = UIImage(named: icon)
}
li.title = title
li.url = url
return li
}

现在我们只返回一个真正的ListItem如果所有的键是我们所需要的和有效的(包括NSURL,我们保证它不是空的)。否则(使用guard声明),我们将返回nil,告诉flatMap不要添加无效的元素到返回的数组。

这是更好的而且更安全的,对吗?我们消除了数据损坏和错误数据带来的风险,以及无效的ListItem元素带来的风险。

结论

我们仍然有很多工作要做,但是今天,这将是所有(让我们保留一些东西到本系列的下一篇文章!)

因此,在这一篇里,我们学会了如何更换一个for循环到map或flatMap,我们要使我们的代码保证一点,避免不一致的输出信号的产生当我们的输入数据是无效的。这是一个相当不错的改进了。

在下篇文章中,我们将看到如何将ListItem转化为struct,并且探索map和flatMap的其他用途 - 尤其是Optionals。

在此期间,我们需要时间来探索map()和flatMap()的效率。我知道他们在第一次使用时是可怕的或复杂的,但一旦你掌握它,你将会想在任何地方使用它们!

f22x

f22x

5 日志
2 分类
GitHub twitter
© 2019 f22x
由 Hexo 强力驱动
主题 - NexT.Mist