Swift知识点(四)

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

  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个字节

虽然都是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)

ArrayOptional这样,支持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的三大特性:封装、继承、多态

  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Swift 中的闭包是一个自包含的函数代码块,可以在代码中被传递和使用。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。Swift 中的闭包类似于 C 和 Objective-C 中的 blocks、以及其他一些编程语言中的 lambdas。 闭包有以下三种形式: 1. 全局函数,有名字但不能捕获任何值。 2. 嵌套函数,有名字,也能捕获其封闭函数内的值。 3. 闭包表达式,没有名字,使用轻量级语法,可以捕获上下文中的值。 闭包表达式的基本语法如下: ``` { (parameters) -> return type in statements } ``` 其中 `parameters` 为参数列表,可以为空;`return type` 为返回类型,也可以为空;`statements` 为闭包体,包含了要执行的代码。 例如,下面的代码定义了一个接受两个整数参数并返回它们之和的闭包: ``` let sum = { (a: Int, b: Int) -> Int in return a + b } ``` 可以像函数一样调用这个闭包: ``` let result = sum(1, 2) print(result) // 输出 3 ``` 闭包可以作为函数的参数或返回值。例如,下面的代码定义了一个接受一个整型数组和一个闭包参数的函数 `apply`: ``` func apply(_ array: [Int], _ transform: (Int) -> Int) -> [Int] { var result = [Int]() for element in array { result.append(transform(element)) } return result } ``` 可以使用闭包表达式作为 `transform` 参数传递: ``` let numbers = [1, 2, 3, 4, 5] let squared = apply(numbers, { (number) -> Int in return number * number }) print(squared) // 输出 [1, 4, 9, 16, 25] ``` 闭包还支持尾随闭包语法,可以将闭包表达式作为函数的最后一个参数传递,并将其放在圆括号之外。例如,上面的代码也可以写成: ``` let squared = apply(numbers) { (number) -> Int in return number * number } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值