SwiftTip
NSHipster - is a journal of the overlooked bits in Objective-C, Swift, and Cocoa.
Flip the image: 翻转图片
Retrive the paths: 检索,重新获得
问题记录
柯里化
什么意思POP
与OOP
的区别Any
与AnyObject
区别rethrows
和throws
有什么区别呢?break
return
continue
fallthough
在语句中的含义(switch、while、for)关键字
public
open
final
static
class
mutating
inout
infix operator
dynamicMemberLookup
where indirect和嵌套
@dynamicCallable
@autoclosure
@escaping
为什么要用
inout
修饰函数的参数才能修改参数?- 什么时候使用convenience?
协议 Protocol
ExpressibleByDictionaryLiteral
Sequence
Collection
CustomStringConvertible
Hashable
Codable
Comparable
RangeReplaceableCollection
以上协议常见应用场景是什么,有什么作用?
iOS初始化核心原则
iOS 的初始化最核心两条的规则:
• 必须至少有一个指定初始化器,在指定初始化器里保证所有非可选类型属性都得到正确的初始化(有值)
• 便利初始化器必须调用其他初始化器,使得最后肯定会调用指定初始化器
示例如下:
1 | public override init(frame: CGRect) { |
在Swift中千万不要用String的count方法计算文本长度,否则当文本中有emoji时,会计算出错。
应当转成NSString再去求length
Swift访问控制权限
Swift的访问权限管理依赖于两个概念:module和文件。module是一个完整的代码单元,
它可以是一个或多个框架(Framework),或者是一个App Bundle,可以被import导入到工程中。
文件指的就是Swift File
,它通常属于一个module。
Swift 为代码中的实体提供了5种不同的访问级别:open、public、internal、fileprivate、private。
Open 为最高级访问级别,private 为最低级访问级别, internal是module中默认权限,private和fileprivate的区别如下:
访问级别 | 定义 |
---|---|
open | 这个属性允许在 app 内或 app 外重写和访问。在开发框架的时候,会应用到这个访问修饰符。 |
public | 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。 |
internal | 默认权限可以访问自己module中源文件里的任何实体 |
fileprivate | 只能在当前源文件中使用。 |
private | 只允许实体在定义的类以及相同源文件内的 extension 中访问 |
public 和 open 的区别在于:
只有被 open 标记的内容才能在别的框架中被继承或者重写。
不希望他们继承或者重写的话,应该将其限定为 public
private 和 fileprivate 的区别在于:
private 让代码只能在当前作用域或者同一文件中同一类型的作用域中被使用
fileprivate 表示代码可以在当前文件中被访问,而不做类型限定。
1、如果希望name
仅在当前文件中可访问,可以使用private
修饰
1 | class Person { |
2 、但是在开发中所面临的更多的情况是我们希望在类型之外也能够读取到这个类型,同时为了保证类型的封装和安全,只能在类型内部对其进行改变和设置。
下面这种写法没有对读取做限制,相当于使用了默认的 internal 权限。
1 | class Person { |
3、如果希望外部可以读取,但不可以修改。这也是为了保证类型的封装和安全,在内部提供可读可写,而外部仅仅可读
- 这种写法相当于把
setter
设置为private
,而getter
仍然是默认的internal
1 | class Person { |
如果既想要外部可读,又想仅内部可写,可以为getter
加上public
:
声明关键字
associatedtype:在协议中,定义一个类型的占位符名称。直到协议被实现,该占位符才会被指定具体的类型。
1 | protocol Entertainment |
class:通用、灵活的结构体,是程序的基础组成部分。与 struct 类似,不同之处在于:
- 允许一个类继承另一个类的特性。
- 类型转换,允许在运行时检查和指定一个类的实际类型。
- 析构方法允许类的实例释放所有资源。
- 引用计数允许多个引用指向一个实例。
struct:通用、灵活的结构体,是程序的基础组成部分,并提供了默认初始化方法。与 class 不同,当 struct 在代码中被传递时,是被拷贝的,并不使用引用计数。除此之外,struct 没有下面的这些功能:
- 使用继承。
- 运行时的类型转换。
- 使用析构方法。
1 | struct Person |
extension:允许给已有的类、结构体、枚举、协议类型,添加新功能。
NSError:code、domin、userInfo
- domain error
- Recoverable error
- Universal error
- Logic error
1 | enum Result<T> { |
try!表示强制执行,如果发生异常程序crash
try?返回可选项,如果发生异常返回nil 不会crash
1 | enum E: Error { |
Swift 5.1 初始化和反初始化
enum包含嵌套方法需要使用indirect
否则报错: Recursive enum "" is not marked 'indirect'
1 | indirect enum LinkedList<Element: Comparable> { |
为什么要用inout
修饰函数的参数才能修改参数?
这是因为函数的参数一般是值类型,比如Int类型就是值类型,当我们想要修改函数内部参数时,我们是不能直接修改默认let修饰的函数参数Int的,我们需要使用
&
符号来修饰参数,这样inout
在函数内部会创建一个新的值,然后在函数return的时候把值赋值给&修改的变量,这和我们常见的class引用类型是不一样的做法1
2
3
4
5
6func makeIncrement(number: Int) -> ((inout Int) -> ()) {
func increamor(v: inout Int) -> () {
v += number
}
return increamor(v:)
}
什么时候使用*convenience?
时间戳
毫秒
1 | let mill = Int64(round(Date().timeIntervalSince1970 * 1000)) |
Swift 单例
1 | class Manager { |
这种写法不仅简洁,而且保证了单例的独一无二。
在初始化类变量的时候,Apple 将会把这个初始化包装在一次 swift_once_block_invoke 中,以保证它的唯一性。
不仅如此,对于所有的全局变量,Apple 都会在底层使用这个类似 dispatch_once 的方式来确保只以 lazy 的方式初始化一次。
另外,我们在这个类型中加入了一个私有的初始化方法,来覆盖默认的公开初始化方法,这让项目中的其他地方不能够通过 init 来生成自己的 MyManager 实例,也保证了类型单例的唯一性。
如果你需要的是类似 default 的形式的单例 (也就是说这个类的使用者可以创建自己的实例) 的话,可以去掉这个私有的 init 方法。
摘录来自: 王巍 (onevcat). “Swifter - Swift 必备 Tips (第四版)。” Apple Books.
Swift 闭包
1 | ///定义一个闭包 |
逃逸闭包
1 | static func getInfo(successHandler: ((Bool) -> Void)? = nil) { |
Precondition预处理
定义
1 | public func precondition(_ condition: @autoclosure () -> Bool, |
使用
1 | precondition(condition: Bool, message: String) |
precondition 在一般的代码中并不多见,因为它是动态的,只会在程序运行时进行检查,适用于哪些无法在编译期确定的风险情况。
如果出现了诸如数据错误的情况,precondition 会提前终止程序,避免因数据错误造成更多的损失。
- 如果条件判断为 true,代码运行会继续进行。
- 如果条件判断为 false,程序将终止。
assert 是单纯地触发断言即停止程序,不会让你有机会将可能出错的设计走过它这一关。???
例如:Swift 数组的下标操作可能造成越界,使用扩展的方式向其中增加一个方法来判断下标是否越界。
extension Array {
func isOutOfBounds(index: Int) {
precondition((0..<endIndex).contains(index), "数组越界")
print("继续执行")
}
}
1 | // 不越界的情况 |
1 | // 越界的情况 |
- 在满足 precondition 条件的时候,程序会继续执行。
- 在不满足 precondition 条件的时候,程序被终止,并且将 precondition 中预设的错误信息打印到了控制台上,precondition 避免了一些无意义的操作。
precondition和assert的区别
闭包
@autoclosure作用:将表达式自动封装成一个闭包
()->Void
1.2 ??的底层实现是用的enum
1.3 “闭包和循环引用”
weak解决循环引用的正确写法:
1 |
|
值类型和引用类型的选择
数组和字典设计为值类型最大的考虑是为了线程安全.
另一个优点,那就是非常高效,因为 “一旦赋值就不太会变化” 这种使用情景在 Cocoa 框架中是占有绝大多数的,这有效减少了内存的分配和回收。
但是在少数情况下,我们显然也可能会在数组或者字典中存储非常多的东西,并且还要对其中的内容进行添加或者删除。”
在需要处理大量数据并且频繁操作 (增减) 其中元素时,选择 NSMutableArray 和 NSMutableDictionary 会更好,
对于容器内条目小而容器本身数目多的情况,应该使用 Swift 语言内建的 Array 和 Dictionary
@escaping的作用?
1 |
|
defer的使用注意点
defer的作用域
以前很单纯地认为 defer 是在函数退出的时候调用,并没有注意其实是当前 scope 退出的时候调用这个事实,造成了这个错误。在 if,guard,for,try 这些语句中使用 defer 时,应该要特别注意这一点。
@discardableResult
Result
Lazy的使用
1 |
|
打印结果:
准备访问结果
准备处理1
处理后的结果:2
准备处理2
处理后的结果:4
准备处理3
处理后的结果:6
done
Swift反射机制Mirror
“通过 Mirror 初始化得到的结果中包含的元素的描述都被集合在 children 属性下,如果你有心可以到 Swift 标准库中查找它的定义,它实际上是一个 Child 的集合,而 Child 则是一对键值的多元组:
示例1
1 |
|
示例2 获取property
1 |
|
@propertyWrapper定义与使用
@propertyWrapper定义
1 | @propertyWrapper |
使用
1 | struct Solution { |
@propertyWrapper属性包裹器 | SwiftLee
1 | @propertyWrapper /// 先告诉编译器 下面这个UserDefault是一个属性包裹器 |
Other usage examples
Property wrappers are used throughout the default Swift APIs as well. The new @State and @Binding keys are an example of this. Another idea could be to create a wrapper for thread-specific writing and reading:
1 | var localPool: MemoryPool |
Or, as some might find handy, a way to define command line actions:
1 | "m", documentation: "Minimum value", defaultValue: 0) (shorthand: |
Related Ideas
- A
@Positive
/@NonNegative
property wrapper that provides the unsigned guarantees to signed integer types. - A
@NonZero
property wrapper that ensures that a number value is either greater than or less than0
. @Validated
or@Whitelisted
/@Blacklisted
property wrappers that restrict which values can be assigned.
FirstVC via UIView
1 | extension UIView { |
TopVC via UIWindow
1 | if let controller = UIWindow.topViewController() { |
1 | extension UIWindow { |
DispatchGroup分组管理异步任务
- enter和leave必须配对出现,如下:
1 | let group = DispatchGroup() |
Swift4.2中的self
- 在 4.2 之前,
self
是全局保留关键字,所以如果在逃逸闭包中如果在闭包中把 self 标记为 weak 后,如果要使用需要使用 ` 包起来:
1 | guard let `self` = self else { return } |
- Swift4.2之后
1 | doSomething(then: { [weak self] in |
- 当然取消了这个限制后也意味着 self 可能不一定是 self 了:
1 | var number: Int? = nil |
给Struct 添加属性
储存属性
1 | ///public struct URLRequest : ReferenceConvertible, Equatable, Hashable {} |
Bool属性
1 | ///public struct URLRequest : ReferenceConvertible, Equatable, Hashable {} |
typealias使用
顾名思义,typealias
是特定类型的别名。类型,例如 Int
、Double
、UIViewController
或一种自定义类型。Int32
和 Int8
是不同的类型。换句话说,类型别名在你的代码库里插入现有类型的另一个名称。例如:
1 | typealias Money = Int |
举例
1 | struct Bank { |
提高可读性
1 | final class Dispatcher { |
该结构体引入了两个闭包,一个用于成功情况,一个用于错误情况。但是,我们还希望提供更方便的函数,调用其中一个处理器即可。在上面的示例中,如果要向成功和错误处理器添加另一个参数(例如 HTTPResponse
),那么需要更改很多代码。在三个地方,((Int) -> Void)?
需要变成 ((Int, HTTPResponse) -> Void)?
。错误处理器也是一样的。通过使用多个类型别名,可以避免这种情况,只需要在一个地方修改类型:
1 | final class Dispatcher { |
组合协议
1 | protocol CanRead {} |
在这里,我们定义了权限层。管理员可以做所有事情,用户可以读写,而消费者只能读。
缺点
1 | func first(action: (Int, Error?) -> Void) {} |
第二个不是立即就能明白的。**Success
是什么类型?如何构造它?你必须在 Xcode 中按住 Option 单击它,以了解它的功能和工作方式。这会带来额外的工作量。如果使用了许多类型别名,则将花费更多的时间。**这没有很好的解决方案,(通常)只能依赖于用例。
callback
1 | var select1Callback: ((String) -> Void)? |
处理cell复用
1、prepareForReuse重置
Apple: If a
UITableViewCell
object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from theUITableView
methoddequeueReusableCell(withIdentifier:)
.For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view’s delegate in
tableView(_:cellForRowAt:)
should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.
1 | override func prepareForReuse() { |