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去掉,并且将可选类型解包