Swift 5.1 温故而知新笔记系列之第五天

Posted Deft_MKJing宓珂璟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 5.1 温故而知新笔记系列之第五天相关的知识,希望对你有一定的参考价值。

1.String

案例一

var str1 = "0123456789"
print(MemoryLayout.stride(ofValue: str1))
// 16

可以看到上面最普通的字符串赋值就是16个字节
在这里插入图片描述
Xcode自带的已经很好的提示了str1变量的地址 0x84b + 0x100001a3d = 0x100002288,因为上面打印出占用16个字节,直接输出x/2xg 0x100002288 可以很明显的看到0x100002288: 0x3736353433323130 0xea00000000003938,此案例中字符串是被存储在变量的内存地址中的,而且都是以ASCII码进行映射。把值直接存储在变量的内存地址中,可以理解为OC的TaggerPoint技术

案例二

var str3 = "0123456789ABCDEFMIKEJINGHY"
print(MemoryLayout.stride(ofValue: str3))

在这里插入图片描述
这里我们能看到最上面右侧Xcode已经提示了字符串,而且可以根据0x551 + 0x1000019ef计算出实际字符串的存储地址,赋值给了rax,然后在给到rdi,我们字符串实际长度是26,因此0x1a给到了rax,然后给到rsirdi(字符串实际地址)rsi(字符串长度)作为参数传递给Swift.String.init进行初始化,太细节就不跟进去了,可以简单的当做一个对象的内存分布,前几个字节类似元类信息,这边存储着是字符串标志和长度,然后后面8个字节存储的实际对象的地址,而且偏移值是0x7fffffffffffffe0

接着看第一个红框的位置:
1.movq %rax, 0x86b(%rip)q代表操作八个字节,把rax的值赋值到后面的地址,register read rax = 0xd00000000000001a0x86b + 0x100001a1d = 0x100002288 计算出str3变量的地址值,然后x/2xg 0x100002288,拿出变量内存开始值的后16个字节内容,可以看到这次不再直接存储字符串,而是前8个字节存储标志位和长度,后八个字节操作0x8000000100001f20 - 0x7fffffffffffffe0 = 0x100001F40,这就是实际字符串的地址。可以看到字符串长度超过0xf,就会开辟一个堆空间的字符串对象,前8个字节存储信息,后八个字节存储实际字符串的地址。

案例三

Mach-O View

var str1 = "0123456789"
var str2 = "0123456789ABCDEFG"
var str3 = "0123456789ABCDEFMIKEJINGHY"

print(MemoryLayout.stride(ofValue: str1))

汇编可以看到str1内存地址是0x100002288x一下就可以看到上面案例一的结论。首先ios这边的mach-o格式对应的VM Address0x100000000,没记错的是Win PE格式是0x400000000
在这里插入图片描述

总结:
可以看到,如果平时自己写的字符串常量,编译后都会存储在cString区域,如果常量长度小于0xf,类似TaggerPointer技术,字符串会被直接赋值到变量的内存区域中,方便快速访问,如果字符串长度大于0xf,被赋值到变量时,会初始化字符串在堆区,类似对象结构,前八个字节存储字符串信息,后八个字节存储字符串实际在cSrting区的内存地址

案例四

var str1 = "0123456789ABCDEF"

str1.append("G")

str1.append("I")

print(MemoryLayout.stride(ofValue: str1))

在这里插入图片描述
可以看到在append之前,变量的16个字节里面,后八个字节指向的是__Text.cString段,很明显看到地址很小,在拼接之后,后八个字节变大很多,猜测是动态分配了堆空间,按照上面的规律,地址值偏移0x7fffffffffffffe0或者0x20,计算出实际的字符串地址0x101837420,可以看到猜测是正确的。不过这个例子是因为正好字符串在常量区存储满了,如果16个字节没有存储满,append还是会继续往里面拼接。

String 总结

字符串长度 <= 0xF

var str = "0123456789"

字符串内容直接存放在str变量的内存中,__TEXT.cString常量区也存在

字符串长度 > 0xF

var str = "0123456789ABCDEFG"

字符串内容存放__TEXT.cString的常量区,字符串地址存放在str变量的后八个字节(有偏移0x20

append操作

如果字符串拼接还是小于0xF,所以拼接后的值依旧存放在变量的内存中。
如果是大于0xF,拼接操作都会再堆区开辟内存。

2.Array

数组变量打印出来是8个字节,很简单就能猜测到,指向的是一块堆内存,看以下案例
在这里插入图片描述
在这里插入图片描述
可以看到当我们数组是1-8八个值的时候,可以看到前八个字节存储的应该是元类的数据,8-16个字节存储着引用计数,16-32个字节存储着数组中实际数量的个数,32-48存储着数组的总容量,后续就存放着实际数字中的值。可以看到当数组个数是八个的时候,总容量是0x10,当数组个数是9的时候,超过最大容量的一般,数组就会扩容,变成了0x20

3.可选项的本质

可选项的本质是枚举

public enum Optional<Wrapped> : ExpressibleByNilLiteral{
    case none
    case some(Wrapped)
    public init(_ some: Wrapped)
}

以下写法都是等价的

var age: Int? = 10
age = 20
age = nil

var age1: Optional<Int> = .some(10)
age1 = .some(20)
age1 = .none

枚举用法

var age: Int? = 10

// 案例一
switch age {
case let v?:
    print(v as Any)
case nil:
    print("nil")
}

// 案例二
//switch age {
//case let .some(v):
//    print(v)
//case .none:
//    print("空值")
//}

4.扩展

Swift中的扩展有点类似于OC里面的Category
扩展可以为枚举,结构体,类,协议添加新功能,可以添加方法,计算属性,下标,便捷初始化器,嵌套类型,协议等
但是不能覆盖原有的功能,不能添加存储属性,不能向已有的属性添加属性观察器,不能添加父类,不能添加指定初始化器,不能添加反初始化器

4.1 扩展计算属性,下标,方法和嵌套类型

var arr: Array<Int> = [10,20,30]


extension Array {
    subscript(nullable idx: Int) -> Element? {
        if (startIndex..<endIndex).contains(idx){
            return self[idx]
        }
        return nil
    }
}


print(arr[nullable: -1] as Any)


extension Int {
    
    // 扩展计算属性
    var dx: Int { self * 100 }
    
    
    // 扩展方法
    func repeats(task: (Int) -> Void){
        for i in 0..<self {
            task(i)
        }
    }
    
    mutating func square() -> Int{
        self = self * self
        return self
    }
    
    // 扩展嵌套属性
    enum Kind {
        case negative, zero, positive
    }
    
    var kind: Kind {
        switch self {
        case 0: return .zero
        case let x where x > 0: return .positive
        default: return .negative
        }
    }
    
    // 扩展下标
    subscript(digitIndex: Int) -> Int {
        var base = 1
        for _ in 0..<digitIndex {base *= 10}
        return (self / base) % 10
    }
}

print(100.dx) // 10000

10.repeats { (idx) in
    print("repeats--\\(idx)")
}

var age = 20
print(age.square()) // 400


print(20.kind) // positive

print(345[0]) // 5

4.2 扩展协议和初始化器

首先明白一点,便捷初始化器和指定初始化器都是类里面的概念,下面就是给类扩展协议和便捷初始化器

class Person {
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}

extension Person : Equatable {
    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.age == lhs.age && rhs.name == rhs.name
    }
    
    convenience init() {
        self.init(age: 100, name: "Mi")
    }
}

以下是结构体的案例

struct Point {
    var x: Int = 0
    var y: Int = 0
}

Point()
Point(x: 20, y: 30)
Point(x:100)
Point(y:200)

上面的写法,会自带四个初始化器,然后添加初始化器在类中

struct Point {
    var x: Int = 0
    var y: Int = 0
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}
Point(x: 20, y: 30)

就只能一个初始化器可以使用,那么只有通过扩展结构体的初始化器,可以完全支持所有的初始化器

struct Point {
    var x: Int = 0
    var y: Int = 0
}

extension Point{
    init(_ point: Point) {
        self.init(x: point.x, y: point.y)
    }
}
var p1 = Point(x: 20, y: 30)
var p2 = Point()
var p3 = Point(x:100)
var p4 = Point(y:200)

var p5 = Point(p4)

扩展协议

extension BinaryInteger{
    func isOdd() -> Bool {
        self % 2 != 0
    }
}
var x: UInt8 = 20
var y = -3

print(x.isOdd()) // false
print(y.isOdd()) // true

扩展可以给协议提供默认实现,也间接实现可选协议的效果

protocol TestProtocol {
    func test() -> Void
}

extension TestProtocol {
    func test() {
        print("默认实现")
    }
}

class TestClass : TestProtocol {}

TestClass().test() // 默认实现

5. 对象打印

class Person : CustomStringConvertible, CustomDebugStringConvertible {
    var description: String{
        return "\\(age)--\\(name)"
    }
    
    var debugDescription: String{
        return "debug--\\(age)--\\(name)"
    }
    
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
}

var p = Person(age: 100, name: "Mikejing")
print(p) // 100--Mikejing

// po p  --> debug--100--Mikejing

6. 访问权限控制

  • open:允许在定义实体的模块,其他模块访问,允许其他模块继承重写(只能用在类,类成员上)
  • public: 允许定义实体的模块,其他模块访问,不允许其他模块继承,重写
  • internal: 只允许在定义的实体模块内访问,不允许在其他模块中访问
  • fileprivate: 只允许在定义的实体源文件中访问
  • private: 只允许在定义实体的封闭声明中访问
    绝大多数实体默认都是internal级别

6.1 一个实体不能被更低访问级别的实体定义

  • 1.变量的类型的访问级别 >= 变量的访问级别
// 变量类型的访问级别 (fileprivate) >= 变量的访问级别 (internal) 不满足,报错
fileprivate class Person {}
internal var p = Person()
  • 2.参数类型,返回类型 >= 函数
  • 3.父类 >= 子类
  • 4.父协议 >= 子协议
  • 5.原始值类型,关联值类型 >= 枚举类型
  • 6.定义类型A时用到的其他类型 >= 类型A
  • 7.元祖类型的访问级别是成员类型最低那个,泛型同理

总结
这里首先明白一个定义,等号右边,函数参数,协议继承,枚举关联值,原始值,都是右边的内容或者说里面的内容必须高于左边被定义的值,不然会导致,变量能被外部访问,定义的内容级别更低的话,就矛盾了。

6.2 成员和嵌套类型

  • 类型的访问级别会影响成员(属性,方法,初始化器,下标)、嵌套类型的默认访问级别
    • 1.类型是privatefileprivate,那么成员/嵌套类型就是privatefileprivate
    • 2.类型是internalpublic,那么成员/嵌套类型就是internal

6.3 协议

  • 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
    • public协议定义的要求也是public
  • 协议实现的访问级别必须 >= 类型的访问级别或者 >= 协议的访 问级别

7. 闭包的循环引用

class Person {
    lazy var fn: (()->()) = {
        [weak self] in
        self?.run()
    }
    
    func run() -> Void {
        print("run")
    }
    deinit {
        print("deinit")
    }
}

func test() {
    let p = Person()
    p.fn()
}

test()

闭包表达式会对用到的对象self对象产生强引用,因此会导致循环引用。

class Person {
    var age: Int = 100
    lazy var x: Int = {
        age
    }()
    
    func run() -> Void {
        print("run")
    }
    deinit {
        print("deinit")
    }
}

func test() {
    let p = Person()
    print(p.x)  // 100
}

test()
// deinit

但是这个案例中,一样强引用了成员变量,原因是这里的闭包没有被引用,而是直接调用,编译器知道这仅仅就是一个Int类型的值而已,因此不需要加self调用,也不会有循环引用

@escaping

逃逸闭包:闭包有可能在函数结束后才调用,闭包调用逃离了函数的作用域,需要通过@escaping声明

import Dispatch

typealias Fn = () -> ()
class Person {
    var fn:Fn?
    init(fn: @escaping Fn) {
        self.fn = fn
    }
    
    func run() -> Void {
        DispatchQueue.global().async {
            [weak self] in
            (self?.fn ?? {})()
        }
    }
    
    deinit {
        print("deinit")
    }
}


func test() {
    let p = Person(fn: {
        print(1)
    })
    p.run()
}

test()

指针

Swift有专门的指针类型,有以下四种类型,而且都是unsafe
UnsafePointer<Pointee> 类似于 const Pointee *
UnsafeMutablePointer<Pointee> 类似于 Pointee *
UnsafeRawPointer 类似于 const void *
UnsafeMutableRawPointer 类似于 void *

案例一

基本的四种类型指针使用

var age = 10

func test1(_ ptr: UnsafePointer<Int>) {
    print(ptr.pointee) // 泛型直接取
}


func test2(_ ptr: UnsafeMutablePointer<Int>){
    ptr.pointee = 20
    print(ptr.pointee)
}

func test3(_ ptr: UnsafeRawPointer) {
    print(ptr.load(as: Int.self)) // void 类型需要load多少字节
}


func test4(_ ptr: UnsafeMutableRawPointer){
    ptr.storeBytes(of: 100, as: Int.self)
    
}
test1(&age)
test2(&age)
print(age)

test3(&age)
test4(&age)
print(age)

案例二

OC中指针变量对应到Swift的案例

import Foundation


// OC BOOL *
// Swift <#UnsafeMutablePointer<ObjCBool>#>
var arr = NSArray(objects: 10,20,30,40)
arr.enumerateObjects { (element, idx, stop) in
    print("\\(element)-\\(idx)")
    if idx == 2{
        stop.pointee = true
    }
}

//for (idx, _) in arr.enumerated(){
//    print(idx, element)
//    if idx == 2 {
//        break
//    }
//}

案例三

获得某个变量的指针

var age = 10
var ptr1 = withUnsafePointer(to: &age) { $0 }
var ptr2 = withUnsafeMutablePointer(to: &age) { $0 }
print(ptr1.pointee)
ptr2.pointee = 20
print(age)

var ptr3 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) }
var ptr4 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0Swift 5.1 温故而知新笔记系列之第七天

python运维开发之第五天

《白帽子讲WEB安全》学习笔记之第5章 点击劫持(clickjacking)

Python之第十五天的努力--生成器,列表推导式,生成器表达式,内置函数1(了解)

21天从Java转向Go之第五天——水滴石穿(复合数据类型)

2012暑期川西旅游之第五天(四姑娘山之双桥沟)_我是亲民_新浪博客