轩辕十四

探索科技与创新的个人博客

  • Authors: Michael Ilseman (compiled through conversations with many others)

Introduction


The Big Picture

One of the top priorities for Swift right now is compatibility across future Swift versions. Compatibility aims at accomplishing two goals:

  1. Source compatibility means that newer compilers can compile code written in an older version of Swift. This aims to reduce the migration pain that Swift developers face when migrating to a newer Swift version. Without source compatibility, projects face version-lock where all source code in a project and its packages must be written in the same version of Swift. With source compatibility, package authors will be able to maintain a single code base across multiple Swift versions while allowing their users to use a newer version of Swift.
阅读全文 »

将程序内部的数据结构序列化为一些可交换的数据格式,以及反过来将通用的数据格式反序列化为内部使用的数据结构,这在编程中是一项非常常见的任务。Swift 将这些操作称为编码(encoding)和解码(decoing)。Swift 4 的一个主要特性就是定义了一套标准的编码和解码数据的方法,所有的自定义类型都能选择使用这套方法。

概览


Codable 系统(以其基本“协议”命名,而这个协议其实是一个类型别名)的设计主要围绕三个核心目标;

  • 普遍性 — 它对结构体,枚举和类都适用。
  • 类型安全 — 像是 JSON 这样的可交换格式通常都是弱类型,而你的代码应该要使用强类型数据。
  • 减少模板代码 — 在让自定义类型加入这套系统时,应该让开发者尽可能少地写重复的“适配代码”。编译器应该为你自动生成这些代码。
阅读全文 »

Judging by the number of talks, articles and discussions related to reactive programming in Swift, it looks like the community has been taken by the storm. It’s not that the concept of reactiveness itself is a new shiny thing. The idea of using it for the development within the Apple ecosystem had been played with for a long time. Frameworks like ReactiveCocoa have existed for years and did an awesome job at bringing the reactive programming to the Objective-C. However, the new and exciting features of Swift make it even more convenient to go full in on the “signals as your apps’ building blocks” model.

Here at Polidea, we’ve also embraced the reactive paradigm, mostly in the form of RxSwift, the port of C#-originated Reactive Extensions. And we couldn’t be happier! It helps us build more expressive and better-architectured apps faster and easier. Unifying various patterns (target-action, completion block, notification) under a universal API that is easy to use, easy to compose and easy to test has so many benefits. Also, introducing new team members is way easier now, when so much logic is written with methods familiar either from sequences (map, filter, zip, flatMap) or from other languages that Reactive Extensions had been ported to.

阅读全文 »

逃逸闭包和非逃逸闭包


逃逸闭包(escaping closure),什么是逃逸闭包?苹果官方给的定义是:当一个闭包作为一个参数传递给函数,但是它是在函数返回之后调用的,这时候,这个闭包就称为逃逸闭包。当你声明一个将闭包作为参数的函数时,你可以在参数的类型之前用 @escaping 来表明这个闭包是允许逃逸的。

闭包可以逃逸的一种方式是存储在函数之外定义的变量中。作为例子,许多启动异步操作的函数都将闭包参数放在异步执行完毕之后的操作中执行(completion handler)。该函数在开始操作后返回,但在操作完成之前不会调用闭包 — 闭包需要逃逸,稍后调用,举个例子:

1
2
3
4
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
阅读全文 »

Swift 中使用通知,在写通知名称的时候,并不能像 Objective-C 那样简单方便,一个字符串搞定。刚开始使用 Swift 通知时,感觉各种不爽,需要这样写:

1
Notification.Name("myNotification")

光取个名字就需要这么长一串代码,真的有悖于 Swift 给我的那种简洁的印象。下面我们就来优化一下。

方法一:简单粗暴的 Notification.Name 扩展


最简单的方式就是对 Notification.Name 进行扩展,代码如下:

1
2
3
extension Notification.Name {
static let myNotification = Notification.Name("myNotification")
}
阅读全文 »

最近项目中遇到了一个崩溃,当点击图片选择保存的时候,并没有出现请求相册权限的对话框,而是直接就崩溃,并且没有任何错误信息。开始以为是权限没有添加,但是 NSPhotoLibraryUsageDescription 是添加了的,搞不懂了。去翻了翻官方的文档,果然找到了解决方式。

从 iOS 11 开始,相册的权限参数发生了变化,适配 iOS 11 还需要添加一个 NSPhotoLibraryAddUsageDescription 的参数。官方的描述如下:

NSPhotoLibraryAddUsageDescription

NSPhotoLibraryAddUsageDescription ( String - iOS) This key lets you describe the reason your app seeks write-only access to the user’s photo library. When the system prompts the user to allow access, this string is displayed as part of the alert.

Important: To protect user privacy, an iOS app linked on or after iOS 10.0, and that accesses the user’s photo library, must statically declare the intent to do so. Include the NSPhotoLibraryAddUsageDescription key (in apps that link on or after iOS 11) or NSPhotoLibraryUsageDescription key in your app’s Info.plist file and provide a purpose string for the key. If your app attempts to access the user’s photo library without a corresponding purpose string, your app exits.

This key is supported in iOS 11.0 and later.

从 important 的最后一句话可以看出,当应用试图访问用户相册但是 Info.plist 中并没有对应权限的参数的话,应用就会退出。

阅读全文 »

在做 iOS 相关的约束动画时,我们一定会用到 layoutIfNeeded() 函数,但是在大多数情况下使用并没有什么问题。当与 UIScrollView 相关的控件结合使用的时候要注意了。

在开发一个 App 时(类似于浏览器的 App),需求是,滑动的时候,需要隐藏掉顶部的 navigation bar (这里我是用 UIView 自定义的一个 navigation bar)。向上滑动,让 navigation bar 移动到可视区域外消失,当然,移动的时候是有动画的,这里我用的是 constraint 相关的动画,所以我需要在最后调用一次 layoutIfNeeded() 函数。代码类似如下:

1
2
3
4
UIView.animate(withDuration: 0.35) {
// 进行更改约束的操作
self.view.layoutIfNeeded()
}
阅读全文 »

在 Swift 中函数是一等公民。


要理解 Swift 中的函数和闭包,需要先明白三件事情,按重要程度进行大致排序如下:

  1. 函数可以像 Int 或者 String 那样被赋值给变量,也可以作为另一个函数的输入参数,或者另一个函数的返回值来使用。
  2. 函数能够捕获存在于其局部作用域之外的变量。
  3. 有两种方法可以创建函数,一种是使用 func 关键字,另一种是 { }。在 Swift 中,后一种被称为闭包表达式
函数可以被赋值给变量,也能够作为函数的输入和输出
1
2
3
4
// 这个函数接受 Int 值并将其打印
func printInt(i: Int) {
print("you passed \(i)")
}

要将函数赋值给一个变量,比如 funVar,我们只需要将函数名字作为值就可以了。注意在函数名后没有括号:

1
let funVar = printInt
阅读全文 »

栈(C 语言描述)


什么是栈(Stack)?这是计算机内存中的一个特殊区域,它存储由每个函数创建的临时变量(包括 main() 函数)。栈是一个“LIFO”(后进先出)的数据结构。它是被 CPU 管理和优化的。每次函数声明一个新变量时,它都被“压入”栈中。然后每次函数退出时,所有由该函数压入栈的变量都被释放(也就是说,它们被删除)。一旦释放栈变量,该区域的内存就可用于其他栈变量。

使用栈来存储变量的优点是内存是自动为你管理的。你无需手动分配内存,或者在你不再需要时释放内存。更重要的是,由于 CPU 如此高效地组织栈内存,读取和写入栈变量的速度非常快。

理解栈的关键是这样一个概念:当一个函数退出时,它的所有变量都从栈中弹出(因此永远丢失)。因此栈变量本质上是本地的。这与我们之前看到的一个概念(称为变量范围)或局部变量或全局变量有关。 C 编程中的一个常见错误是尝试访问某个函数内栈上创建的变量,该函数在该函数之外的某个地方(即该函数退出后)从该程序中创建。

要记住栈的另一个特性是,可以存储在栈上的变量的大小有一个限制(随OS变化)。对于在堆中分配的变量,情况并非如此。

栈的总结:

  • 随着函数压入和弹出局部变量,栈增长和缩小
  • 没有必要自己管理内存,变量内存被自动的分配和释放
  • 栈具有大小限制
  • 栈变量仅在创建它们的函数正在运行时才存在

堆(C 语言描述)


堆是计算机内存中的一个区域,不会自动为你进行内存管理,并且不受 CPU 的严格管理。它是一个更自由的内存区域(并且更大)。要在堆上分配内存,必须使用 malloc()calloc() ,它们是内置的 C 函数。一旦你在堆上分配了内存,当你不再需要它的时候,你就有责任使用 free() 来释放内存。如果你没有做到这一点,你的程序将会有所谓的内存泄漏。也就是说,堆上的内存仍然会被留出(并且不会被其他进程使用)。

阅读全文 »

结构体和类的主要不同点:

  • 结构体 (和枚举) 是值类型,而类是引用类型。在设计结构体时,我们可以要求编译器保证不可变性。而对于类来说,我们就得自己来确保这件事情。

  • 内存的管理方式有所不同。结构体可以被直接持有及访问,但是类的实例只能通过引用来间接地访问。结构体不会被引用,但是会被复制。也就是说,结构体的持有者是唯一的,但是类却能有很多个持有者。

  • 使用类,我们可以通过继承来共享代码。而结构体 (以及枚举) 是不能被继承的。想要在不同的结构体或者枚举之间共享代码,我们需要使用不同的技术,比如像是组合、泛型以及协议扩展等。

值类型


值语义 (value semantics)
* 结构体只有一个持有者。比如,当我们将结构体变量传递给一个函数时,函数将接收到结构体的复制,它也只能改变它自己的这份复制。这叫做值语义 (value semantics),有时候也被叫做复制语义。

引用语义 (reference semantics)
* 对于对象来说,它们是通过传递引用来工作的,因此类对象会拥有很多持有者,这被叫做引用语义 (reference semantics)。

因为结构体只有一个持有者,所以它不可能造成引用循环。而对于类和函数这样的引用类型,我们需要特别小心,避免造成引用循环的问题。

编译器所做的对于值类型的复制优化和值语义类型的写时复制行为并不是一回事儿。写时复制必须由开发者来实现,想要实现写时复制,你需要检测所包含的类是否有共享的引用。和自动移除不必要的值类型复制不同,写时复制是需要自己实现的。不过编译器会移除那些不必要的“无效”浅复制,以及像是数组这样的类型中的代码会执行“智能的”写时复制,两者互为补充,都是对值类型的优化。

阅读全文 »
0%