1 Access Control
private - only within the class or struct
fileprivate - only within the current source file, including extension
(default)internal - can be access within current module or target
public - can be access in any module or target, but cannot be inherited or overrideed
open - can be inherited and overrided by any module or target
work with extension
- in same file, can access fileprivate or upperlevel members
- in separate file, can access internal or upperlevel members
/// from swift tips
/// 希望在类型之外也能够读取到这个类型,同时为了保证类型的封装和安全,
///只能在类型内部对其进⾏改变和设置。这时,我们可以通过下⾯的写法将读取和设置的控制权限分开:
class MyClass {
private(set) var name: String?
}
2. Stored properties & Type properties
Stored Properties - stored by an instance of class or structure
- cannot be added in Extension
class or structure can define
variable stored properties (introduced by the var keyword)
constant stored properties (introduced by the let keyword)
- must always have a value before initialization completes, and cannot use lazy
Lazy Stored Properties (introduced by the lazy var keyword)
- initial value might not be retrieved until after instance initialization completes
Computed Properties - do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly
- can be added in Extension
- classes, structures, and enumerations can define computed properties
must declare computed properties as var keyword, because their value is not fixed.
Read-Only Computed Properties - with a getter but no setter
ISSUE:
A Swift property does not have a corresponding instance variable, and the backing store for a property is not accessed directly
Instance Properties, Type Properties
Instance Properties - without static
- belong to an instance of a particular type.
- one instance has its own set of property values, separate from any other instance.
Type Properties - static key word
- belong to the type itself, no matter how many instances, just keep one copy
- constant property, all instances can use (like a static constant in C)
- variable property, global to all instances of that type (like a static variable in C)
- cannot support "class var carName"
Type Method, Instance Method
Instance Methods
- normal methods
Type methods
- init()
- designated init()
- convenience init()
- for class/struct/enum static func funcName(parameterList) -> returnType { }
- for class, class func funName(parametersList) -> returnType { } that allow subclasses to override the superclass’s implementation of that method.
For protocol
- static func, means Type methods, for class/struct/enum
- for class, when implement protocol, we treat static/class method as the same
Comparing Classes and Structures
Classes and structures in Swift have many things in common. Both can:
- Define properties, to store values
- Define methods, to provide functionality
- Define subscripts, to provide access to their values using subscript syntax
- Define initializers, to set up their initial state
- Extension, to expand their functionality beyond a default implementation
- Conform to protocols, to provide standard functionality of a certain kind
Classes have additional capabilities that structures do not:
- Inheritance, enables one class to inherit the characteristics of another.
- Type casting, enables you to check and interpret the type of a class instance at runtime.
- Deinitializers, enable an instance of a class to free up any resources it has assigned.
- Reference counting, allows more than one reference to a class instance.
Structures are always copied when they are passed around in your code, and do not use reference counting.
3. Condition compiling
#if os(macOS)
#elseif swift >= 3.0
#else
#endif
Conditions:
os(parameters) : macOS, iOS, tvOS, watchOS, Linux
arch(parameters): x86_64, arm, arm64, i386
swift(parameters): >= version
4. Protocol Extension
- default implementation
/// protocol
protocol MyProtocol {
func method()
}
/// protocol extension
extension MyProtocol {
// provide default implementation
func method() {
print("Called")
}
// provide optional implementation
func methodOptional() {
print("Method Optional")
}
}
/// conform protocol
struct MyStruct: MyProtocol {
// override the default implementation
func method() {
print("Called in struct")
}
}
5. Protocol constraints
swift protocol can be conformed by the Class, Struct, Enum
objc protocol can be only conformed by the Class \(Objective C Object or child class from NSObject\)
add constraints to protocol extensions
6. Enum raw value
A raw value type must: Conform to the Equatable protocol
Be literal convertible from any of the following types:
- Int
- String
- Character
7. UnsafePointer
In order to work with C language,
UnsafePointer 和它的⼀系列变体是对 C 语⾔指针的访问和转换⽅法
对于使⽤ C API 时如果遇到接受内存地址作为参数,或者返回是内存地址的情况,在 Swift ⾥会将它们转为 UnsafePointer<Type> 的类型
Unsafe - because ⽆法对其进⾏⾃动的内存管理。和 Unsafe 类的指针⼯作的时候,我们需要像 ARC 时代之前那样⼿动地来申请和释放内存,以保证程序不会出现泄露或是因为访问已释放内存⽽造成崩溃。
// C method
void method\(const int \*num\) {
printf\\("%d",\\*num\\);
}
// corresponding Swift method
func method\(\_ num: UnsafePointer<CInt>\) {
print\\(num.pointee\\)
}
// C type
int, bool, char
// in swfit
CInt, CBool, CChar
C API Swift API
const Type\* UnsafePointer<Type>
Type\* UnsafeMutablePointer<Type>
8 Property Observing (feature of Swift, hooks)
利⽤属性观察我们可以在当前类型内监视对于属性的设定,并作出⼀些响应。
Swift 中为我们提供了两个属性观察的⽅法,它们分别是 willSet 和 didSet.
only stored property can add property observing, calculate property cannot add property observing, so we cannot see "set willSet didSet" in the same time
// Example
class MyClass {
var date: NSDate {
willSet {
let d = date
print\("即将将⽇期从 \\(d\) 设定⾄ \\(newValue\)"\)
}
didSet {
print\("已经将⽇期从 \\(oldValue\) 设定⾄ \\(date\)"\)
}
}
init\(\) {
date = NSDate\(\)
}
}
let foo = MyClass()
foo.date = foo.date.addingTimeInterval(10086)
// 输出
// 即将将⽇期从 2014-08-23 12:47:36 +0000 设定⾄ 2014-08-23 15:35:42 +0000
// 已经将⽇期从 2014-08-23 12:47:36 +0000 设定⾄ 2014-08-23 15:35:42 +0000
- 如果我们⽆法改动这个类,⼜想要通过属性观察做⼀些事情的话,可能就需要⼦类化这个类,并且重写它的属性了。重写的属性并不知道⽗类属性的具体实现情况,⽽只从⽗类属性中继承名字和类型,因此在⼦类的重载属性中我们是可以对⽗类的属性任意地添加属性观察的,⽽不⽤在意⽗类中到底是存储属性还是计算属性
class A {
var number :Int {
get {
print("get")
return 1
}
set {print("set")}
}
}
class B: A {
override var number: Int {
willSet {print("willSet")}
didSet {print("didSet")}
}
}
// 调⽤ number 的 set ⽅法可以看到⼯作的顺序
let b = B()
b.number = 0
// 输出
// get
// willSet
// set
// didSet
// set 和对应的属性观察的调⽤都在我们的预想之中。这⾥要注意的是 get ⾸先被调⽤了⼀次。
// 这是因为我们实现了 didSet , didSet 中会⽤到 oldValue ,
// ⽽这个值需要在整个 set 动作之前进⾏获取并存储待⽤,否则将⽆法确保正确性。
// 如果我们不实现 didSet 的话,这次 get 操作也将不存在。
9. Delay several seconds to run block
Background thread <--> Main thread
// 创建⽬标队列
let workingQueue = DispatchQueue(label: "my_queue")
// 派发到刚创建的队列中,GCD 会负责进⾏线程调度
workingQueue.async {
// 在 workingQueue 中异步进⾏
print("努⼒⼯作")
Thread.sleep(forTimeInterval: 2)
// 模拟两秒的执⾏时间
DispatchQueue.main.async {
// 返回到主线程更新 UI
print("结束⼯作,更新 UI")
}
}
Main thread
let time: TimeInterval = 2.0
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time) {
print("2 秒后输出")
}
Delay(3)
import Foundation
typealias Task = (_ cancel : Bool) -> Void
func delay(_ time: TimeInterval, task: @escaping ()->()) -> Task? {
func dispatch_later(block: @escaping ()->()) {
let t = DispatchTime.now() + time
DispatchQueue.main.asyncAfter(deadline: t, execute: block)
}
var closure: (()->Void)? = task
var result: Task?
let delayedClosure: Task = {
cancel in
if let internalClosure = closure {
if (cancel == false) {
DispatchQueue.main.async(execute: internalClosure)
}
}
closure = nil
result = nil
}
result = delayedClosure
dispatch_later {
if let delayedClosure = result {
delayedClosure(false)
}
}
return result;
}
func cancel(_ task: Task?) {
task?(true)
}
10. Using KVO in Swift
Description
为了其他不同实例对当前的某个属性 (严格来说是 keypath) 进⾏监听时使⽤的。
其他实例可以充当⼀个订阅者的⾓⾊,当被监听的属性发⽣变化时,订阅者将得到通知。Compared with property observing: 和属性观察不同,KVO 的⽬的并不是为当前类的属性提供⼀个钩⼦⽅法
Cons/Disadvantages - Low Efficiency
在 Swift 中我们也是可以使⽤ KVO 的,但是仅限于在 NSObject 的⼦类中。这是可以理解的,因
为 KVO 是基于 KVC (Key-Value Coding) 以及动态派发技术实现的,⽽这些东⻄都是 Objective-C
运⾏时的概念。另外由于 Swift 为了效率,默认禁⽤了动态派发,因此想⽤ Swift 来实现 KVO,
我们还需要做额外的⼯作,那就是将想要观测的对象标记为 dynamic 。
Use
- class has to be inherited from "NSObject"
- property must be marked by "dynamic"
class MyClass: NSObject {
dynamic var date = Date()
}
private var myContext = 0
class Class: NSObject {
var myObject: MyClass!
override init() {
super.init()
myObject = MyClass()
print("初始化 MyClass,当前⽇期: \(myObject.date)")
myObject.addObserver(self,
forKeyPath: "date",
options: .new,
context: &myContext)
delay(3) { //custom delay function
self.myObject.date = Date()
}
}
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
if let change = change, context == &myContext {
let newDate = change[.newKey]
print("⽇期发⽣变化 \(newDate)")
}
}
}
let obj = Class()
- 有时候我们很可能也⽆法修改想要观察的类的源码。遇到这样的情况的话,⼀个可能可⾏的⽅案是继承这个类并且将需要观察的属性使⽤ dynamic 进⾏重写。
class MyClass: NSObject {
var date = Date()
}
class MyChildClass: MyClass {
dynamic override var date: Date {
get { return super.date }
set { super.date = newValue }
}
}
- if class is not inherited from "NSObject", we cannot use KVO. But we can use Property Observing