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
,然后给到rsi
,rdi(字符串实际地址)
,rsi(字符串长度)
作为参数传递给Swift.String.init
进行初始化,太细节就不跟进去了,可以简单的当做一个对象的内存分布,前几个字节类似元类信息,这边存储着是字符串标志和长度,然后后面8个字节存储的实际对象的地址,而且偏移值是0x7fffffffffffffe0
。
接着看第一个红框的位置:
1.movq %rax, 0x86b(%rip)
q代表操作八个字节,把rax
的值赋值到后面的地址,register read rax = 0xd00000000000001a
,0x86b + 0x100001a1d = 0x100002288
计算出str3变量的地址值,然后x/2xg 0x100002288
,拿出变量内存开始值的后16个字节内容,可以看到这次不再直接存储字符串,而是前8个字节存储标志位和长度,后八个字节操作0x8000000100001f20 - 0x7fffffffffffffe0 = 0x100001F40
,这就是实际字符串的地址。可以看到字符串长度超过0xf
,就会开辟一个堆空间的字符串对象,前8个字节存储信息,后八个字节存储实际字符串的地址。
案例三
var str1 = "0123456789"
var str2 = "0123456789ABCDEFG"
var str3 = "0123456789ABCDEFMIKEJINGHY"
print(MemoryLayout.stride(ofValue: str1))
汇编可以看到str1
内存地址是0x100002288
,x
一下就可以看到上面案例一的结论。首先ios这边的mach-o
格式对应的VM Address
是0x100000000
,没记错的是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.类型是
private
或fileprivate
,那么成员/嵌套类型就是private
或fileprivate
- 2.类型是
internal
或public
,那么成员/嵌套类型就是internal
- 1.类型是
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 温故而知新笔记系列之第七天
《白帽子讲WEB安全》学习笔记之第5章 点击劫持(clickjacking)
Python之第十五天的努力--生成器,列表推导式,生成器表达式,内置函数1(了解)