16. 内存访问冲突、指针
局部作用域
可以使用do实现局部作用域
这样直接写局部变量会报错
加上do,即可实现局部变量
do {
int a = 10
}
内存访问冲突
内存访问冲突会在两个访问满足下列条件时发生:
- 至少一个是写入操作
- 它们访问的是同一块内存
- 它们的访问时间重叠(比如在同一个函数内)
指针
Swift中有专门的指针类型,被定性为“Unsafe”(不安全的),常见的有以下4种类型
UnsafePointer<Pointee>
类似于const Pointee *
UnsafeMutablePointer<Pointee>
类似于Pointee *
UnsafeRawPointer<Pointee>
类似于const void *
UnsafeMutableRawPointer<Pointee>
类似于void *
第一个,只可以访问指针,不可以修改指针
第二个,可以访问和修改指针
<Pointee>
泛型,如果是Int,说明只能访问Int类型的指针
var age = 10
func test1(_ ptr: UnsafeMutablePointer<Int>){
//取数的时候,直接使用ptr.pointee即可(不用*ptr)
//由于是mutable,可以修改
ptr.pointee = 20
print("test1", ptr.pointee)
}
func test2(_ ptr: UnsafePointer<Int>){
print("test2", ptr.pointee)
}
test2(&age)
test1(&age)
print(age)
结果:
test2 10
test1 20
20
17. 字面量协议、模式匹配、条件编译
字面量(Literal)
var age = 10
var isRed = false
var name = "Jack"
上面代码中:10、false、"Jack"就是字面量
可以看到,初始化过程很简单,直接赋值即可
Swift自带的绝大部分类型,都支持直接通过字面量进行初始化
而,当是一个对象的时候,却需要使用下面方法去初始化:
var p = Person()
var p = Person.init()
问:为何自带类型可以直接初始化?而对象却不可以?
是因为Swift自带类型,遵守了对应的 字面量协议
var num: Int = true
当直接写上述代码时,是错误的❌
因为,把bool值类型,赋值给了int类型,类型不匹配
但,当做下列操作,即可变为编译正确
//extension扩展协议
//Int类型遵守ExpressibleByBooleanLiteral协议
extension Int: ExpressibleByBooleanLiteral{
//协议要实现的方法
public init(booleanLiteral value: BooleanLiteralType) {
self = value ? 1 : 0
}
}
var num: Int = true
print(num)
var num2: Int = 100
print(num2)
1
100
在扩展中添加协议成员
我们可以通过扩展来扩充已存在类型( 类,结构体,枚举等)。
扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。
其中:
//ExpressibleByBooleanLiteral是一个协议
public protocol ExpressibleByBooleanLiteral {
associatedtype BooleanLiteralType : _ExpressibleByBuiltinBooleanLiteral
//协议要实现的方法
init(booleanLiteral value: Self.BooleanLiteralType)
}
作业:
var num: Int? = Int("123")
print(num)//123
var num2: Int? = Int("fff")
print(num2)//nil
扩展出:
var num: Int? = "123"
print(num)//123
var num2: Int? = "fff"
print(num2)//nil或0
参考答案:
extension Int: ExpressibleByStringLiteral
{
public init(stringLiteral value: String) {
self = (Int(value) != nil ? Int(value)! : 0)
}
}
var num: Int? = "123"
print(num)
var num2: Int? = "fff"
print(num2)
模式(Pattern)
什么是模式?
模式是用于匹配的规则,比如switch的case、捕捉错误的catch、if\guard\while\for语句 的条件等
let age = 2
if case 0...9 = age {
print("in")
}
相当于switch写法
switch age{
case 0...9:
print("in")
default:break
}
也就是,if case相当于只有一个case的switch
for case let
let points = [(1, 0), (2, 1), (3, 0)]
for case let(x, 0) in points{
print(x)
}
//1
//3
let ages: [Int?] = [nil, 2, 3, nil, 5]
for case let age? in ages {
print(age)
}
等价于:
for item in ages {
if let age = item {
print(age)
}
}
可以看出,可选模式(上面)的这种写法比下面的简单
表达式模式(Expression Pattern)
表达式模式用在case里面
利用这个,可以自定义case的匹配规则
比如:学生里面有score和名字,当做switch比较的时候,就可以只比较score
~=
重写模式
// MARK:标记
// TODO: 将要做
// FIXME: 需要修复
18. 从OC到Swift
Swift调用OC
- 新建一个桥接头文件,文件名格式默认为:
{targetName}-Bridging-Header.h
该文件,是OC暴露给Swift使用的 - 在
{targetName}-Bridging-Header.h
文件中# import
OC需要暴露给Swift的内容
如果C语言暴露给Swift的函数名,跟Swift中其他函数名冲突了
可以在Swift中使用@_silgen_name
修改C函数名
例如:
OC调用Swift
Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是:{targetName-Swift.h}
不同于上面那种直接在.h文件中手动写导入#import 方法
OC调用Swift,不需要自己手动导入{targetName-Swift.h}
而是暴露出来,即前面加上特定关键词,系统会自动加入到{targetName-Swift.h}
文件中去
swift中的class,需要暴露出来,才能被OC引用。
swift中的class继承NSOjbect,即是暴露操作
问1:为什么要暴露给OC的 Swift中的类 一定要继承NSObject?
因为是在OC里面调用,走的是runtime那一套,也就是需要isa指针,因此,在Swift中,必须使其继承NSObject
问2:OC中的方法,在Swift中去调用,比如person.run()(run方法定义在OC中),那么,此时的底层调用是OC的runtime机制?还是Swift中虚表机制?同理,在OC中调用Swift方法,比如[car run];(run方法定义在Swift中),底层调用又是如何调用的?
通过汇编打断点,可以看出
在Swift中调用OC的方法,还是使用的runtime那一套
在OC中调用Swift的方法函数,由于已经是继承NSObject,因此,还是走的runtime那一套
问3:在Swift中,class已经被暴露出去,那么,此时再调用已经被暴露出去的函数方法,底层又是如何呢?
(如果没暴露,调用函数方法,必定是走Swift调用那一套)
仅仅是暴露,还是在Swift中调用的话,没有走runtime那一套,走的是Swift自己虚表那一套
如果实在是想让方法走runtime那一套的话,可以在方法前加上dynamic关键字,则就走的是runtime那一套
以上图片方法是指:
方法一:使用@objc给每一个需要暴露的属性添加修饰
方法二:使用@objcMembers修饰类,里面所有的属性,都可以访问(不需要一个一个加)
同样,防止两个属性、类同名,也可以对Swift的调用修改名字:
@objc(name)
OC调用Swift中的#selector(方法名1)
方法名1,前面需要加@objc修饰,将其暴露出去
因为,本身#selector方法在OC中,就是runtime那些,所以在swift中需要暴露
String
- Swift中的String与OC中的NSString可以互相桥接转换
- String不能 桥接转换成 NSMutableString
- NSMutableString继承NSString,因此可以 桥接转换成String
其他Siwft、OC桥接转换:
19. 从OC到Swift、函数式编程
问:下列p对象,占多少个字节?
class Person{
var age = 10
var weight = 20
}
var p = Person()
//8 8 8 8
//metadata指针 引用计数相关 age weight
8个字节,是metadata指针
8个字节,是 引用计数相关
8个字节,是age
8个字节,是weight
因此,一共占32个字节
问:下列p对象继承NSObject,占多少个字节?
class Person: NSObject{
var age = 10
var weight = 20
}
var p = Person()
8个字节,是isa指针
8个字节,是age
8个字节,是weight
字节对齐(需要是16的倍数),需要8个字节
因此,一共占32个字节
虽然都是32个字节,但是存储内容不一样
只能被class继承的协议
某个协议,只允许被class继承,不允许被struct继承,如何操作?
//定义一个协议
protocol Runnable {
}
//struct遵守协议
struct Person: Runnable {}
//class遵守协议
class Student: Runnable {}
当,协议被这样修改的时候,就可以实现想要的效果:
protocol Runnable: AnyObject {}
protocol Runnable: class {}
@objc protocol Runnable {}
被@objc修饰的协议,还可以暴露给OC去遵守实现
即:OC可以调用swift中的协议方法
dynamic
被@objc dynamic修饰的内容,会具有动态性,比如走Runtime那一套流程
KVC\KVO
swift支持KVC\KVO
有几个要求:
- 属性所在的类、监听器需要最终继承NSObject
- 属性前加:@objc dynamic修饰
关联对象(Associated Object)
在swift中,只有Class可以使用关联对象
默认情况下,extension(扩展)是不可以添加存储属性的(可以扩展计算属性)
此时,使用关联对象,就可以实现类似:类实现存储属性的效果
class Person {
}
extension Person {
//定义一个key
private static var AGE_KEY: Bool = false
var age: Int {
get{
//&Person.AGE_KEY 或者 &Self.AGE_KEY
// Self就相当于当前类
(objc_getAssociatedObject(self, &Person.AGE_KEY) as? Int) ?? 0
}
set{
//newValue,把传进来的值存进去(10)
objc_setAssociatedObject(self, &Person.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
}
var p = Person()
p.age = 10
print(p.age)
资源名管理
直接赋值图片名不太好,因为有一张图片,100处在用,如果现在要修改图片,则需要全局修改
可以做类似安卓的图片名赋值方式,写一个全局的名称,用的时候直接用全局字符串名,修改的时候,只需要修改一处地方即可。
多线程开发
DispatchQueue.main.async {
print("主队列,异步执行")
}
DispatchQueue.global().async {
print(Thread.current, "全局并发队列")
}
延迟执行
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 10) {
print("do something")
}
只执行一次
dispatch_once在swift中已经被废弃
可以使用lazy代替
//fileprivate只访问当前文件
//全局变量的初始化,默认也是lazy的
fileprivate var initTask: Void = {
print("----init-----")
}()
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let _ = initTask
let _ = initTask
//只会打印一次
}
}
Array的常见操作
- map
- filter
- reduce
- flatMap
- compactMap
map
map的作用:会将array里面的元素全部拿出来遍历,然后处理元素,处理完毕后再组成一个新数组array2
var array = [1, 2, 3, 4]
//map的作用:会将array里面的元素全部拿出来遍历,然后处理元素,处理完毕后再组成一个新数组array2
var array2 = array.map { i in
return i*2
}
//或者简写
//var array2 = array.map{ $0 * 2}
print(array2)
//[2, 4, 6, 8]
映射一遍,可以改变输出元素的类型,比如Int变成String
filter
filter也会遍历数组的每一个元素,但,它会有过滤的效果
array.filter(isIncluded: (Int) throws -> Bool>)
里面的返回值是Bool类型
如果是true,则放入到新数组里
如果是false,则不要
找出数组里面元素为偶数的元素,组成新数组:
var array3 = array.filter { i in
return i % 2 == 0
}
print(array3)
//[2, 4]
reduce
reduce也会遍历array里面所有的元素
然后对元素做有关联 的操作
有关联:下次用到的数据,与上次运行的结果有关
//0是初始值,第一次遍历的时候,partialResult = 0
//当第二次遍历的时候,partialResult就是partialResult + i(初始值已经没用了)
//i就是遍历的元素
var array4 = array.reduce(0) { partialResult, i in
return partialResult + i
}
print(array4)
//10
大致过程是:
0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
或者是:
((((0 + 1) + 2) + 3) + 4) = 10
reduce还可以简写:
var array4 = array.reduce(0) { $0 + $1 }
reduce就是解决:遍历数组,对里面所有元素进行有关联操作的问题
flatMap
首先,了解下Array.init(repeating: 2, count: 3)
代表,创建一个数组,数组3个元素,每个元素的值都是2
var array = [1, 2, 3]
var array2 = array.map { Array.init(repeating: $0, count: $0)}
var array3 = array.flatMap { Array.init(repeating: $0, count: $0)}
print(array)
print(array2)
print(array3)
打印结果:
[1, 2, 3]
[[1], [2, 2], [3, 3, 3]]
[1, 2, 2, 3, 3, 3]
也就是,flatMap会将数组里面的元素,放在新的数组里面
compactMap
var array = ["123", "test", "jack", "-30"]
var array2 = array.map{Int($0)}
var array3 = array.compactMap{Int($0)}
print(array)
print(array2)
print(array3)
打印结果:
["123", "test", "jack", "-30"]
[Optional(123), nil, nil, Optional(-30)]
[123, -30]
当使用map的时候,里面转换,有可能转换成功,也可能转换失败,因此,数组里面存放的是可选类型或nil
当使用compactMap的时候,返回结果里面会将nil去掉,并且将可选类型解包
OC中,结构体使用并|
运算符,在Swift中直接使用数组即可
20. 函数式编程(Function Programming)
函数式编程
函数式编程,简称FP,是一种编程范式,也就是如何编写程序的方法论
主要思想:把计算过程尽量分解成一系列可复用函数的调用
主要特征:函数是第一等公民
假如要实现:((num + 3) * 5 - 1) % 10 / 2
方法一:
直接写:
let num = 1
let result = ((num + 3) * 5 - 1) % 10 / 2
print(result)//4
方法二:
将每一个计算操作写成函数:
let num = 1
//加法+
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2}
//减法-
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2}
//乘法*
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2}
//除法/
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2}
//取余%
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2}
let result = divide(mod(sub(multiple(add(num, 3), 5), 1) , 10), 2)
print(result)//4
可读性有些差,容易写错
如何将一个接收两个参数的函数,变为接收一个参数的函数?
将第二个参数改成一个函数2,该函数2接收原来的参数2,然后在函数2里面做操作,返回值即可
let num = 1
//看第一个->,第一个箭头后面的所有内容,都是返回值
func add(_ v: Int) -> (Int) -> Int {
//return后面的{},是一个闭包表达式
return {
//$0代表返回函数里面的第一个参数
$0 + v
}
}
//得到一个函数:参数Int类型,返回值Int类型
let fn = add(3)
//调用函数fn,返回Int类型
let sum = fn(num)
//或者合并到一起:
//先调用add函数,获取到一个结果,是函数类型,再继续调用新函数,参数是num
let sum2 = add(3)(num)
print(sum, sum2)//4 4
上述add也可以简写:
func add(_ v: Int) -> (Int) -> Int { {$0 + v} }
return后面只有一个表达式,return可以省略。注意,闭包的{}不能省略
同理,可以替换以上5种运算符:
//加法
func add(_ v: Int) -> (Int) -> Int { {$0 + v} }
//减法-
func sub(_ v: Int) -> (Int) -> Int { {$0 - v} }
//乘法*
func multiple(_ v: Int) -> (Int) -> Int { {$0 * v} }
//除法/
func divide(_ v: Int) -> (Int) -> Int { {$0 / v} }
//取余%
func mod(_ v: Int) -> (Int) -> Int { {$0 % v} }
然后,实现上述结果:
方法三:
//fn1是一个函数
let fn1 = add(3)
let fn2 = multiple(5)
let fn3 = sub(1)
let fn4 = mod(10)
let fn5 = divide(2)
let result = fn5(fn4(fn3(fn2(fn1(num)))))
print(result)//4
比第二种方法好些,但依然可读性差一些
方法四:
可以使用函数合成
先来看简答的,如何将两个函数,合成一个函数
定义一个函数composite,其参数是两个函数,返回值也是一个函数
//函数合成
func composite(_ f1: @escaping (Int) -> Int, _ f2: @escaping (Int) -> Int) -> (Int) -> Int
{
return {
//$0是指:composite函数的返回值(是个函数)里面的的入参
//f2调用f1
f2(f1($0))
}
}
//tempFun是一个函数,即composite的返回值
let tempFun = composite(fn1, fn2)
//tempFun(num)函数的调用,参数是num
print(tempFun(num))//20 解释:(3+1)*5
可以不使用composite ,而自定义运算符,比如>>>
///自定义运算符 >>>
infix operator >>> : AdditionPrecedence
func >>> (_ f1: @escaping (Int) -> Int,
_ f2: @escaping (Int) -> Int)
-> (Int) -> Int{ { f2(f1($0)) }}
//tempFun是一个函数,即composite的返回值
let tempFun = fn1 >>> fn2
//tempFun(num)函数的调用,参数是num
print(tempFun(num))
同理,可扩展其他,最后得到:
let tempFun = fn1 >>> fn2 >>> fn3 >>> fn4 >>> fn5
print(tempFun(num))//4
或者直接使用fn1等号后面的内容,而不定义fn1
let tempFun = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
print(tempFun(num))//4
num传递给 >>> 函数返回值里面的入参,然后给到调用里面的&0,因为是f1调用,所有又给到f1的入参
f1的参数是入口,f2的返回值是出口
这也就要求:f1的返回值,与f2的入参类型相等,才可以进行合并
入口和出口的类型可以不一样
确定上述关系,就可以抽出泛型(而不需要写Float的时候,再写一个)
func >>> <A, B, C> (_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C)
-> (A) -> C{ { f2(f1($0)) }}
高阶函数(Highter-Order-Function)
高阶函数,是至少满足下列一个条件的函数:
- 接收一个或多个函数作为输入|参数(map、filter、reduce)
- 返回值是一个函数
因此,FP里面到处都是高阶函数
柯里化(Currying)
Currying:将一个接收多参数的函数,变换为一系列只接收单个参数的函数
函数:多个参数->单个参数的过程
//两个参数
func add1(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
//两个参数,转化为一个参数:
func add1(_ v: Int) -> (Int) -> Int{ { $0 + v} }
let temp1 = add1(10, 20)
let temp2 = add1(20)(10)
print(temp1, temp2)//30 30
//三个参数
func add2(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
///三个参数,转化为一个参数
func add2(_ v3: Int) -> (Int) -> (Int) -> Int {
//v2 == 20
return { v2 in
//v1 == 10
return { v1 in
return v1 + v2 + v3
}
}
}
let temp3 = add2(10, 20, 30)
let temp4 = add2(30)(20)(10)
print(temp3, temp4)//60 60
加上泛型:
func add1(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func add2(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
func currying<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {
return { b in
return { a in
return fn(a, b)
}
}
}
print(currying(add1)(20)(10))//30
简化:
prefix func ~<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {
{ b in { a in fn(a, b) } }
}
print((~add1)(20)(10))
同理可扩充为三个参数的
函子(Functor)
像Array
、Optional
这样,支持map运算的类型,称为函子(Functor)
func map<T>(_ fn: (Inner) -> T) -> Type<T>
这种类型的,称为函子
而,Array和Optional的定义:
func map<T>(_ fn: (Element) -> T) -> Array<T>
func map<T>(_ fn: (Wrapped) -> T) -> Optional<T>
刚好符合上面的类型,因此,它们两个是函子
单子(Monad)
对任意一个类型F,如果能支持以下运算,那么就可以称为是一个单子(Monad)
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>
Array、Optional是单子
面向协议编程
面向协议编程(Protocol Oriented Programming,简称POP
)
是Swift的一种编程范式,在Swift的标准库中,有大量的POP影子
同时,Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP
)
在Swift开发中,OOP和POP是相辅相成的,任何一方并不能取代另一方
OOP的三大特性:封装、继承、多态