protocol
protocol 的方法声明为mutating,是为了防止struct 或者 enum 实现协议的时候 无法修改里面的方法
当使用class来实现协议的时候 是不需要在协议方法前面加mutating的 因为class可以随意修改自己的成员变量 mutating修饰对于class而言 完全是透明的 可以当做不存在
@autoclosure
@autoclosure把一句表达式自动地封装成一个闭包
func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
switch optional {
case .Some(let value): return value
case .None:
return defaultValue()
}
}
如果我们直接使用 T ,那么就意味着在 ?? 操作符真正取值之前,我们就必须准备好一 个默认值传入到这个方法中,一般来说这不会有很大问题,但是如果这个默认值是通过一系列复杂计算得到的话,可能会成为浪费
-- 因为其实如果 optional 不是 nil 的话,我们实际上是完全 没有用到这个默认值,而会直接返回 optional解包后的值的。这样的开销是完全可以避免的,方法就是将默认值的计算推迟到 optional 判定为 nil 之后
@autoclosure 只支持()-> T形式的简化
@escaping
当没有使用@escaping修饰闭包的时候 闭包不是逃逸闭包 那么闭包的作用域就不会超出函数本身 那么不需要在闭包中持有self
但是 对于逃逸闭包而言 需要确保闭包中的成员依然有效 如果再闭包内引用了self以及其成员的话 就要强制明确的写出self
如果在协议或者父类中定义了一个接受 @escaping 为参数的方法,那么在实现协议和类型或者是这个父类的子类中,对应的方法也必须被声明为 @escaping ,否则两个方法会被认为拥有不同的函数签名
运算符重载
对于对像除了+ - * 这样的操作符进行重载的时候 需要先进行声明
precedencegroup DotProductPrecedence {
associativity: none
higherThan: MultiplicationPrecedence
}
infix operator +*: DotProductPrecedence
precedencegroup
---定义了一个操作符优先级别。操作符优先级的定义和类型声明有些相似,一个操作符比需 要属于某个特定的优先级。Swift
标准库中已经定义了一些常用的运算优先级组,比如加法 优先级 ( AdditionPrecedence ) 和乘法优先级 ( MultiplicationPrecedence ) 等,你可以在这里找
到完整的列表。如果没有适合你的运算符的优先级组,你就需要像我们在例子中做得这 样,自己指定结合律方式和优先级顺序了。
associativity
---定义了结合律,即如果多个同类的操作符顺序出现的计算顺序。比如常⻅的加法和减法都 是 left ,就是说多个加法同时出现时按照从左往右的顺序计算
(因为加法满足交换律,所 以这个顺序无所谓,但是减法的话计算顺序就很重要了)。点乘的结果是一个 Double ,不 再会和其他点乘结合使用,所以这里是 none ;
higherThan
---运算的优先级,点积运算是优先于乘法运算的。除了 higherThan ,也支持使用 lowerThan 来指定优先级低于某个其他组。
infix
---表示要定义的是一个中位操作符,即前后都是输入;其他的修饰子还包括prefix 和 postfix ,不再赘述;
Swift 的操作符是不能定义在局部域中的,因为至少会希望在能在全局范围使用你的操作符,否则操作符也就失去意义
函数
1.func的参数修饰不写明的情况下 是let的 也就是不可更改的
2.使用inout修饰函数内部的参数相当于在函数内部创建了一个新值 然后在函数返回的时候将这个值赋值给&修饰的变量
3.数的修饰具有传递限制 对于跨越层级的调用 需要保证同一参数的修饰是统一的
func makeIncrementor(addNumber: Int) -> ((inout Int) -> ()) {
func incrementor(variable: inout Int) -> () {
variable += addNumber;
}
return incrementor;
}
命名空间
1.OC没有命名空间 OC中所有的代码和引用的静态库最终都会被编译到同一个域和二进制中
2.Swift的命名空间是基于module 而不是在代码中显式地指明 每个module代表了Swift的一个命名空间 同一个target里面的类型名称不能相同
如果出现冲突 如何解决
- 如果可能出现冲突的时候 需要在类型名称前面加上module的名称 MyFramework.MyClass.hello()
- 使用类型嵌套的方法来指定访问的范围 常见的办法是将名字重复的类型定义到不同的struct中 以此避免冲突
struct MyClassContainer1 {
class MyClass {
class func hello() {
print("hello from MyClassContainer1")
} }
}
struct MyClassContainer2 {
class MyClass {
class func hello() {
print("hello from MyClassContainer2")
} }
}
typealias
1.typealias是为已经存在的类型重新定义名字 通过名字 可以使代码变得更加清晰
2.typealias是单一的 必须指定将某个特定的类型通过typealias赋值为新名字 而不能将整个泛型类型进行重命名 但是 如果在别名中也引入了泛型 可以进行对应
class Person<T> {}
typealias Worker<T> = Person<T>
3.某个类型同时实现多个协议的组合时。我们可以使用 & 符号连接几个协议,然后给它们一个新的更符合上下文的名字,来增强代码可读性
protocol Cat { ... }
protocol Dog { ... }
typealias Pat = Cat & Dog
associatedtype
associatedtype 类型的占位符
在一个协议中加入了像是associatedtype或者Self的约束 它将只能被用为泛型约束 而不能作为独立类型的占位使用 失去了动态派发的特性
protocol Animal {
associatedtype F: Food
func eat(_ food: F)
}
struct Tiger: Animal {
func eat(_ food: Meat) {
print("eat \(meat)")
}
}
struct Sheep: Animal {
func eat(_ food: Grass) {
print("eat \(food)")
}
}
func isDangerous<T: Animal>(animal: T) -> Bool { if animal is Tiger {
return true
} else {
return false
}
}
isDangerous(animal: Tiger()) // true
isDangerous(animal: Sheep()) // false
可变参数函数
可变参数函数指的是可以接受任意多个参数的函数
func sum(input: Int...) -> Int { //...
}
- 对于Swift 可变参数无需放在最后一个
func myFunc(numbers: Int..., string: String) {
numbers.forEach {
for i in 0..<$0 {
print("\(i + 1): \(string)")
}
}
}
对于同一个方法 只能存在一个可变参数
可变参数都必须是同一种类型
对于这条可以进行变通
extension NSString {
convenience init(format: NSString, _ args: CVarArgType...) //...
}
let name = "Tom"
let date = NSDate()
let string = NSString(format: "Hello %@. Date: %@", name, date)
初始化方法顺序
Swift的初始化方法需要保证类型的所有属性都要被初始化
需要保证在当前子类实例的成员初始化完成后才能调用父类的初始化方法
class Cat {
var name: String
init() {
name = "cat"
}
}
class Tiger: Cat {
let power: Int
override init() {
power = 10
// 如果我们不需要打改变 name 的话,
// 虽然我们没有显式地对 super.init() 进行调用
// 不过由于这是初始化的最后了,Swift 替我们自动完成了
}
}
Designated,Convenience 和 Required
所有不加修饰的init方法都需要在方法中保证所有非Optional的实例变量被赋值初始化,而在子类中也强制(显示或者隐式的)调用super版本的designated初始化
class ClassA {
let numA: Int
init(num: Int) {
numA = num
}
}
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
convenience的初始化方法是不能被子类重写或者从子类中以super的方式被调用的
但是,当子类中实现冲洗了父类convenience方法所需要的init方法的话 那么子类中也可以调用父类的convenience初始化方法
class ClassA {
let numA: Int
init(num: Int) {
numA = num
}
convenience init(bigNum: Bool) {
self.init(num: bigNum ? 10000 : 1)
}
}
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
1.初始化路径必须保证对象完全初始化 可以通过调用本类型的designated初始化方法得到保证
2.子类的designated初始化法方法必须调用父类的designated方法 以保证父类也完成初始化法
对于希望子类中一定实现的designated初始化方法 可以添加required关键字限制 强制子类对这个方法重写实现 最大的好处是可以保证依赖于某个designated初始化法方法的 convenience 一直可以被使用
class ClassA {
let numA: Int
required init(num: Int) {
numA = num
}
convenience init(bigNum: Bool) {
self.init(num: bigNum ? 10000 : 1)
}
}
class ClassB: ClassA {
let numB: Int
required init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
对于 convenience 的初始化方法,也可以加上 required 以确保子类对其进行实现。这在要求子类不直接使用父类中的 convenience 初始化方法时有用
初始化返回 nil
对于可能初始化失败的情况,我们应该始终使用可返回 nil 的初始化方法,而不是类型工厂方法
extension Int {
init?(fromString: String) {
self = 0
var digit = fromString.count - 1 for c in fromString {
var number = 0
if let n = Int(String(c)) {
number = n
} else {
switch c {
case "一":
case "二":
case "三":
case "四":
case "五":
case "六":
case "七":
case "八":
case "九":
case "零":
default: return nil }
number = 1 number = 2 number = 3 number = 4 number = 5 number = 6 number = 7 number = 8 number = 9 number = 0
self = self + number * Int(pow(10, Double(digit)))
}
}
}
}
static 和 class
在非class的类型上下文中, 统一使用static来描述类型作用域
struct Point {
let x: Double
let y: Double
// 存储属性
static let zero = Point(x: 0,y: 0)
// 计算属性
static var ones: [Point] {
return [Point(x: 1, y: 1),
Point(x: -1, y: 1),
Point(x: 1, y: -1),
Point(x: -1, y: -1)]
}
// 类型方法
static func add(p1: Point, p2: Point) -> Point {
return Point(x: p1.x + p2.x, y: p1.y + p2.y)
}
}
class 专门用在class类型的上下文中 可以用来修饰类方法和类的计算属性
class不能修饰class里面的存储属性
对于一个可以让class struct enum都能实现的protocol 定义方法或者计算属性的时候 用static
protocol MyProtocol {
static func foo() -> String
}
struct MyStruct: MyProtocol {
static func foo() -> String {
return "MyStruct"
}
}
enum MyEnum: MyProtocol {
static func foo() -> String {
return "MyEnum"
}
}
class MyClass: MyProtocol {
// 在 class 中可以使用class
class func foo() -> String {
return "MyClass.foo()"
}
// 也可以使用 static
static func bar() -> String {
return "MyClass.bar()"
}
}
多类型和容器
Array 、 Dictionay 和 Set 泛型的 在一个集合中只能放同一个类型的元素
AnyClass,元类型和 .self
在Swift中 Any AnyObject AnyClass 代表任意
typealias AnyClass = AnyObject.Type
.Type 表示的是某个类型的元类型 协议的使用.Protocol 来获取
协议和类方法中的 Self
protocol IntervalType { //...
/// Return `rhs` clamped to `self`. The bounds of the result, even /// if it is empty, are always within the bounds of `self`
func clamp(intervalToClamp: Self) -> Self
//...
}
这么定义是因为协议其实本身是没有自己的上下文类型信息的,在声明协议的时候,我们并不知道最后究竟会是什么样的类型来实现这个协议,Swift 中也不能在协议中定义泛型进行限制。而在声明协议时,我们希望在协议中使用的类型就是实现这个协议本身的类型的话,就需要使用 Self 进行指代
class MyClass: Copyable {
var num = 1
func copy() -> Self {
let result = type(of: self).init()
result.num = num
return result
}
required init() {
}
}
let object = MyClass()
object.num = 100
let newObject = object.copy()
object.num = 1
print(object.num)//1
print(newObject.num) // 100
动态类型和多方法
虽说我们可以通过dynamicType来获取一个对象的动态类型 (也就是运行时的实际类型,而 非代码指定或编译器看到的类型)但是,swift目前不支持多方法
属性观察
属性分为存储属性和计算属性,其中,存储属性将会在内存中实际分配地址对属性进行存储,而计算属性不包括背后的存储 只是提供了set和get两种方法
在一个属性的定义中,同时出现set和willSet或者didSet是不可能的
当无法改到一个类 还想通过属性观察做相关事情,那么就需要子类化这个类,重写他的属性 只是从父类属性中继承名字和类型 不关注父类属性的具体实现情况。因此,在子类的重载属性中可以对父类的属性任意的添加属性观察 不用在意父类中到底是存储属性或者计算属性
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")
}
}
}
didSet 中会用到 oldValue ,而这个值需要在整个 set 动作之前进 行获取并存储待用 否则将无法确保正确性
final
final 用在class func或者var前面进行修饰 表示不允许对该内容进行继承或者重写操作
- 权限控制
给一段代码加上final就意味着编译器想你做出保证 这段代码不会再被修改
1.类或者方法的功能确实已经完备了
比如很多辅助类的工具类或者方法 可能考虑加上final
2.子类继承和修改是一件危险的事情
3.为了父类中某些代码一定会被执行
class Parent {
final func method() {
print("开始配置") // ..必要的代码
methodImpl()
// ..必要的代码
print("结束配置")
}
func methodImpl() {
fatalError("子类必须实现这个方法") // 或者也可以给出默认实现
}
}
class Child: Parent {
override func methodImpl() {
//..子类的业务逻辑 }
}
}
无论如何我们如何使用 method ,都可以保证需要的代码一定被运行过,而同时又给了子类 继承和重写自定义具体实现的机会
- 性能考虑
lazy 修饰符和 lazy 方法
class ClassA {
lazy var str: String = {
let str = "Hello" print("只在首次访问输出")
return str
}()
}
打印只在第一次被调用的时候出现
let data = 1...3
let result = data.lazy.map {
(i: Int) -> Int in print("正在处理 \(i)")
return i * 2
}
print("准备访问结果")
for i in result {
print("操作后结果为 \(i)")
}
print("操作完毕")
// 准备访问结果
// 正在处理 1
// 操作后结果为 2
// 正在处理 2
// 操作后结果为 4
// 正在处理 3
// 操作后结果为 6
// 操作完毕
Reflection 和 Mirror
Reflection反射 可以有机会在运行的时候通过某些条件实时的决定调用的方法 或者向某个类型动态的设置甚至加入属性及方法
struct Person {
let name: String
let age: Int
}
let xiaoMing = Person(name: "XiaoMing", age: 16)
let r = Mirror(reflecting: xiaoMing)
print("xiaoMing 是\(r.displayStyle!)")
print("属性个数:\(r.children.count)")
for child in r.children {
print("属性名:\(String(describing: child.label)),值:\(child.value)")
}
image.png
可以通过dump(对象)方法获取一个对象的镜像并进行标准输出的方式将其输出出来
func valueFrom(_ object: Any, key: String) -> Any? {
let mirror = Mirror(reflecting: object)
for child in mirror.children {
let (targetKey, targetMirror) = (child.label, child.value)
if key == targetKey {
return targetMirror
}
}
return nil
}
多重 Optional
遇到了多重 Optional时,可以使用 fr v -R 命令来打印出变量的未加工过时的信息
Protocol Extension
对于已有的protocol进行扩展 扩展中实现的方法将作为实现扩展的类型的默认实现
规则
- 如果类型推断得到的是实际的类型
那么类型中的实现将被调用 如果类型中没有实现 那么协议扩展中的默认实现将被调用 - 如果类型推断得到的是协议 而不是实际类型
1.并且方法在协议中进行了定义 那么类型中的实现将被调用 如果类型中没有实现 那么协议扩展在的默认实习将被使用
2.方法没用在协议中定义,扩展中的默认实现将被调用
where 和模式匹配
let name = ["王小二","张三","李四","王二小"]
name.forEach {
switch $0 {
case let x where x.hasPrefix("王"):
print("\(x)是笔者本家")
default:
print("你好,\($0)")
}
}
let num: [Int?] = [48, 99, nil]
let n = num.flatMap {$0}
for score in n where score > 60 {
print("及格啦 - \(score)")
}
网友评论