0
点赞
收藏
分享

微信扫一扫

Swift知识点(二)


17. 字面量协议、模式匹配、条件编译

字面量(Literal)

var age = 10
var isRed = false
var name = "Jack"

上面代码中:10、false、"Jack"就是字面量

可以看到,初始化过程很简单,直接赋值即可

而,当是一个对象的时候,却需要使用下面方法去初始化:

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

  1. 新建一个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h
    该文件,是OC暴露给Swift使用的
  2. {targetName}-Bridging-Header.h文件中# importOC需要暴露给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个字节

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

举报

相关推荐

0 条评论