轩辕十四

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

逃逸闭包和非逃逸闭包


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

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

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

最近项目中遇到了一个崩溃,当点击图片选择保存的时候,并没有出现请求相册权限的对话框,而是直接就崩溃,并且没有任何错误信息。开始以为是权限没有添加,但是 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)。

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

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

阅读全文 »

Swift 中可选值的定义如下

1
2
3
4
enum Optional<Wrapped> {
case none
case some(wrapped)
}

因为 Optional 是枚举类型,所以有的时候可以用模式匹配来进行一些巧妙的操作:

  • 使用 if case 来进行模式匹配,对非 nil 的值做 for 循环
1
2
3
4
5
let ary = [1, 2, nil, 4, nil, 44]
for case let i? in ary {
print(i)
}
// 1, 2, 4, 44
阅读全文 »

序列(Sequence)


Sequence定义:

1
2
3
4
protocol Sequence {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
}

要实现一个Sequence,首先需要提供一个返回迭代器(iterator)makeIterator() 方法。
对于迭代器,它是一个满足 IteratorProtocol 协议的类型。

IteratorProtocol协议的定义:

1
2
3
4
protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
阅读全文 »

数组和可变性


在Swift中,数组是值类型。并且Swift中,Swift标准库中的所有集合类型都使用了“写时复制”这一技术,避免了大量副本导致性能下降。值类型的好处,不仅是性能有提高,其次就是能保证数组的不可变。

在Objective-C中,声明一个不可变的数组 NSArray 并不能保证数组的不可变。例如:

1
2
3
4
5
6
let mutableAry = NSMutableArray(array: [1, 2, 3])
/// 我们不想让 ary 被改变,声明为不可变数组
let ary: NSArray = mutableAry
/// 但是事实上它依然能够被 a 影响并改变
mutableAry.add(5)
ary // [1, 2, 3, 5]

正确的做法是,在赋值的时候,先进行拷贝:

1
2
3
4
5
let nsAry = mutableAry.copy() as! NSArray
mutableAry.add(10)

mutableAry // [1, 2, 3, 5, 10]
nsAry // [1, 2, 3, 5]
阅读全文 »

之前我一直以为,Swift是一门弱类型的语言。

判断依据:

Swift 变量不强制的指定类型,而是用 varlet 表示可变与不可变。所以,Swift是一门弱类型的语言。

但是,看了一下强弱类型的时候,才恍然大悟。其实 varlet 的特性,只是说明Swift是一门动态类型的语言。并不能指出是否是强或弱类型。

一些定义:

  • 静态类型语言:是一种编译器无法自动检测类型的语言。
  • 动态类型语言:是一种编译器能够自动检测类型的语言。
  • 强类型语言:强类型语言也称为强类型定义语言。是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。
  • 弱类型语言:弱类型语言也称为弱类型定义语言。与强类型定义相反。像vb,php,js等就属于弱类型语言。
阅读全文 »
0%