本篇文章代码均在Apple Swift version 5.0.1
,学习Swift
的笔记git
简单枚举,一般这样子写github
enum TestEnum{
case test1 = 1,test2 = 2,test3 = 3
}
复制代码
在c
中,TestEnum
占用空间是1
位[就是8字节],那么咱们在Swift中是怎么样的呢?express
经过测试代码来看枚举占用的空间swift
enum Password {
case number(Int,Int,Int,Int)
case other
}
enum TestEnum {
case test1,test2,test3,test4
}
var testum = TestEnum.test1
var p1 = MemoryLayout.stride(ofValue: testum)
var p2 = MemoryLayout.size(ofValue: testum)
var p3 = MemoryLayout.alignment(ofValue: testum)
print("分配:\(p1) 占用:\(p2) 对齐:\(p3)")
//分配:1 占用:1 对齐:1
var pwd = Password.number(3, 5, 7, 8)//字节 8*4 =32
//pwd = .other//一样的变量仍是32字节
p1 = MemoryLayout.stride(ofValue: pwd)// 40
p2 = MemoryLayout.size(ofValue: pwd)//33
p3 = MemoryLayout.alignment(ofValue: pwd)//8
print("分配:\(p1) 占用:\(p2) 对齐:\(p3)")
复制代码
由代码能够得出:api
简单枚举,case
当默认为数字,占用空间为1
字节,关联枚举则占用的比较多,咱们主要从内存上分析一下为何占用这么多数组
首先在关键代码打断点查看内存布局sass
enum Password {
case number(Int,Int,Int,Int)
case other
}
//此处打断点
var pwd = Password.number(3, 5, 7, 8)//字节 8*4 =32
复制代码
按照咱们正常猜测,4
个int类型的,应该是要占32
字节,为何系统是占用了33
个字节,另一个字节存储了什么呢?bash
DEBUG->DEBUG Workflow ->Always Show Disassembly
能够看到他们的内存地址是0x1000088a0
,或使用lldb
命令查看数据闭包
(lldb) x/9x 0x1000088a0
0x1000088a0: 0x00000003 0x00000000 0x00000005 0x00000000
0x1000088b0: 0x00000007 0x00000000 0x00000008 0x00000000
0x1000088c0: 0x00000000
复制代码
看到了咱们赋值的3/5/7/8
后边是0x0
,而后咱们再测试下边的代码查看内存app
let pwd2 = Password.other//一样的变量仍是33字节
复制代码
查看汇编
movq $0x1,0x4b3b(%rip) //含义是将字面量1,赋值给右边的寄存器 rip是CPU执行下句编码的地址,那么这句含义就是将1赋值给rip+0x4b3b的内存地址。
内存大小不变,变的是最后一位由0x0
变成了0x1
。
将枚举稍微修改一下
enum Password {
case number1(Int,Int,Int,Int)
case number2(Int,Int,Int)
case number3(Int,Int)
case number4(Int)
case other
}
复制代码
再测试下代码
let pwd2 = Password.other//一样的变量仍是33字节
复制代码
let pwd4 = Password.number2(5, 6, 7)//一样的变量仍是33字节
复制代码
let pwd5 = Password.number4(8)//一样的变量仍是33字节
复制代码
switch .case
是调用函数的思路的。
一样能够测试
enum Password {
case number1(Int,Int,Int,Int)
case number2(Int,Int,Int)
case number3(Int,Int)
case number4(Int)
case other
}
复制代码
一样占用了33位,前32位是存储值,第33位存储哪一个类别。
let 和var修饰的究竟是哪些内存?
let s2 = Point(x: 10, y: 12)
s2 = Point(x: 10, y: 10)//报错缘由是let修饰的结构体 内存区域(栈) 因此值和属性都不能改
s2.x = 11//报错
s2.y = 12//报错
let ret = PointClass()
ret.x = 0
ret.y = 0
ret = PointClass()//报错 报错缘由是let修饰的类 指针(栈) 因此指针不能改,可是指针指向的属性能改
复制代码
其实let和const相似,修饰的是直接接触的指针,只是指针不能变,不是指针指向的数据不能变动。
一个函数和它所捕获的常量、变量环境组合起来,成为闭包
typealias Clo = (Int) -> Int
func getFunc () -> Clo {
var num = 5 //局部变量 赋值到了堆空间来保证变量的值。每次调用都访问了堆空间了。
func plus(_ v:Int) ->Int{
num += v
return num//断点B
}
return plus//断点A
}
var f = getFunc()
print(f(1)) //5+1 = 6
print(f(2)) //6 + 2 =8
var f2 = getFunc()//每次调用都会调用新的堆空间的 num,他们是分开没联系的。
print(f2(3))// 5 + 3 = 8
复制代码
每次执行getFunc()
,都会从新申请栈空间来存储num
,f
和f2
的栈空间的独立的。 经过汇编看下栈空间的值的变化,在断点A地方,输出num的占空间地址
x/3xg 0x00000001005735f0
0x1005735f0: 0x0000000100002080 0x0000000000000002
0x100573600: 0x00007fff76760ab9
复制代码
而后运行到断点B,再次打印num的值
(lldb) x/3xg 0x00000001005735f0
0x1005735f0: 0x0000000100002080 0x0000000000000002
0x100573600: 0x0000000000000005
复制代码
而后运行一次,第二次断点到B位置
(lldb) x/3xg 0x00000001005735f0
0x1005735f0: 0x0000000100002080 0x0000000200000002
0x100573600: 0x0000000000000006
复制代码
其实闭包就像一个class(类)
,局部变量像属性(类的成员变量)
,类定义的函数相似闭包的代码。
class CloClass{
var num = 6
func plus(_ v:Int) -> Int {
num += v
return num
}
}
复制代码
类的实例化在堆区,而闭包也是在堆区,类有成员变量,闭包有属性,类能够定义函数,闭包也能够定义函数。。。
闭包原理已经明白了,来两道菜压压惊。
最后来两道题,探究输出结果是什么?
typealias FnClo = (Int) -> (Int,Int)
func getFns() -> (FnClo,FnClo) {
var num1 = 0
var num2 = 0
func plus(_ i:Int) -> (Int,Int){
num1 += i
num2 += i << 1
return (num1,num2)
}
func mnus(_ i:Int) -> (Int,Int){
num1 -= i
num2 -= i << 1
return (num1,num2)
}
return (plus,mnus)
}
let (p,m) = getFns()
print(p(5))
print(m(4))
print(p(3))
print(m(2))
复制代码
结果输出什么?为何?
class Person {
var age = 9
}
typealias Clo = (Int) -> Int
func getFunc () -> Clo {
var p = Person()
func plus(_ v:Int) ->Int{
p.age += v;
return p.age
}
return plus
}
var f = getFunc()
print(f(3))
复制代码
当给inout传递一个变量,直接地址传递进去便可,那么传递进去计算属性,又如何呢?
struct Photo {
var age:Int {
get{
return height/2
}
set{
height = newValue * 2
}
}
var height = 12
}
var p = Photo(height: 10)
func test(_ v: inout Int) {
v = 9
}
test(&p.age)
复制代码
查看汇编看到其实传递的仍是p.age
的地址,不过地址经过getter
以后而后将地址传递到函数内部的。
当设置了是属性观察器则是采用Copy-In-Copy-Cout策略进行赋值和调用will和set函数。
struct Photo {
var age:Int {
willSet{
print("will\(newValue)")
}
didSet{
print("didset old:\(oldValue)")
}
}
}
var p = Photo(age: 5)
func test(_ v: inout Int) {
v = 9
}
test(&p.age)//此处打断点
复制代码
而后查看汇编
inout若是有物理内存地址,且没有属性观察器,直接将内存地址传入函数。 若是是计算属性,或者设置了属性观察器,采起Copy-In-Copy-out作法,调用该函数,先复制实参的值,产生副本,将副本的内存地址传入函数(副本进行引用传递),在函数内部能够修改副本的值,函数返回后,将副本的值覆盖实参的值。
属性分为实例属性和类型属性,实例属性是经过实例能够访问的属性,类型属性是经过类来访问的属性。
咱们定义个实例属性age
和类属性level
struct Propty {
var age = 10
static var level = 11
}
var pro = Propty()
pro.age = 11;
var level = Propty.level;
复制代码
static
修饰的内存只会分配一次,类能够访问,当这个也能够实现单例,本质是执行的dispatch_once_f
。
class fileManger {
public static let manger:fileManger = fileManger()
private init(){//设置私有,外部不可访问
print("init")
}
open func close() {
print("close")
}
public func open() { //mode 可访问
print("open")
}
}
var file = fileManger.manger
file.open()
file.close()
file.init()//error:init' is inaccessible due to 'private' protection level 复制代码
其实inout本质是引用传递(地址传递),分状况分为地址直接传递和副本地址传递。
struct Point {
var x = 0,y = 0
}
class Pointcls {
var p = Point()
subscript (index index:Int) ->Point{
set{
print("set")
p = newValue
}
get{print("get"); return p }
}
}
var p = Pointcls()
p[index: 0] = Point(x: 1, y: 2)
// set
// get
复制代码
其实从代码也能够看出来,下标就是执行的set
和get
方法,这点就不用汇编分析了。
每一个类必定指定一个init
,不写的话,系统默认生成init
。另一个是便捷生成初始化器convenience
,必须指定调用初始化器。
class Size {
var width = 0,height = 0
convenience init(_ w:Int,_ h:Int) {
self.init(width:w,height:h)
//code
}
convenience init(w:Int) {
self.init(width:w,height:0)
//code
}
convenience init(h:Int) {
self.init(width:0,height:h)
//code
}
//私有外部不能访问
private init(width:Int,height:Int) {
self.width = width
self.height = height
}
}
复制代码
Size
设计了一个init
三个便捷初始化器,init
能够作一些必要的配置,另外的便捷初始化能够单独处理,这样子,关键代码不会漏掉。
在OC中有obj.class
,在Swift中则是obj.Self
与之对应的
var t = 1.self
var t2 = type(of: [1])
print(t2,t)//[Int].type Int.type
复制代码
可使用is
关键字判断是不是某个类
var age = 1
if age is Int{
print("age is int")
}else if age is Double{
print("age is int")
}
//age is int
复制代码
self
表明当前类,而不是特定的类。
protocol Run {
func test() -> Self
}
class RunSub: Run {
func test() -> Self {
return RunSub.init()//报错 Cannot convert return expression of type 'RunSub' to return type 'Self'
}
}
复制代码
报错了,由于self指当前类,能够理解泛型的指针,RunSub
可能有子类,不能直接返回RunSub.init()
,这样子至关于类型写死了,一般咱们这样子写
class RunSub: Run {
required init(){
//code
}
func test() -> Self {
let ty = type(of: self)
return ty.init()
}
}
复制代码
required
这样子保证了子类必须实现init
函数,类型也是type(of: self)
的类型。
Swift
中String
也是采用了小数据优化,大数据不优化方案,在OC中叫tag pointer
,其实就是小数据不用指针,大数据使用指针,Swift
中是长度大于15使用指针存储,不大于15直接存储数据。 当不大于15String
是存储在数据段,当进行append()
操做,会将数据段数据复制到栈区,并且使用指针存储数据。 长度小余15在内存布局
//var str2 = "123456789012345"// 0x3837363534333231 0xef35343332313039
//var str2 = "12345678901234" // 0x3837363534333231 0xee00343332313039
//var str2 = "1234567890123" // 0x3837363534333231 0xed00003332313039
复制代码
长度大于15在内存的布局
var str2 = "123456789012345678901234"
//0xd000000000000019 0x80000001000056c0 0x19是字符串长度
复制代码
0x80000001000056c0
须要加上0x20
才是字符串的真正地址,相似OC
的mask
。
//str2真实地址 :0x80000001000056c0 - 0x7fffffffffffffe0 = 0x1000056E0
//0x1000056c0 + 0x20 = 0x1000056E0
/*0x1000056e0: 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 1234567890123456
0x1000056f0: 00 0a 00 20 00 00 00 00 00 00 00 00 00 00 00 00
*/
复制代码
当append()
以后
str2的地址
0xf000000000000019 0x000000010340a490
进行+0x20
0x10340a490+0x20=0x10340a4b0
0x10340a4b0的数据
x/4xg 0x10340a4b0
0x10340a4b0: 0x3837363534333231 0x3635343332313039
0x10340a4c0: 0x3433323130393837 0x0004000000000035
复制代码
指针地址
数组占8字节,指向了存储数据的真实地址。默认数组大小是4,负载超过0.5则进行扩容,扩容系数是2。
var arr = [Int]()
for i in 1...3{
arr.append(i)
}
0x00007fff90381cc0 0x0000000200000002
0x0000000000000003 0x0000000000000008
0x0000000000000001 0x0000000000000002
0x0000000000000003 0x0004003c00000000
for i in 1...9{
arr.append(i)
}
0x00007fff90381cc0 0x0000000200000002 0x0000000000000009
0x0000000000000020 0x0000000000000001 0x0000000000000002
0x0000000000000003 0x0000000000000004 0x0000000000000005
0x0000000000000006 0x0000000000000007 0x0000000000000008
0x0000000000000009 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x00007fff59610004 0x00007fff90381d58
0x0000000000000000 0x0000000000000000
复制代码
可选类型本质上是Optional
类型
var age:Int? = 10
复制代码
至关于
var age1:Optional<Int> = .some(10)
复制代码
?
只是语法糖,更加简单。
高级运算符溢出运算符,在大于最大值+1等于最下值,最小值-1是最大值。
var v:Int8 = Int8.max &+ 1
print(v) // -128
v = Int8.min &- 1
print(v)//127
复制代码
运算符==、-、+、+=、-=、/、*
能够本身实现重载,在OC
中是不支持的。
struct Point:Equatable{
var x = 0,y = 0
//中缀加好
static func +(p1:Point,p2:Point) -> Point{
return Point(x: p1.x+p2.x,y: p1.y+p2.y)
}
//中缀减法
static func -(p1:Point,p2:Point) -> Point{
return Point(x: p1.x-p2.x,y: p1.y - p2.y)
}
//中缀乘法
static func *(p1:Point,p2:Int) -> Point{
return Point(x: p1.x*p2,y: p1.y*p2)
}
//h中缀除法
static func /(p1:Point,p2:Int) -> Point{
if p2 == 0{
return p1
}
return Point(x: p1.x/p2,y: p1.y/p2)
}
//前缀 减号
static prefix func -(p1:Point) -> Point{
return Point(x: -p1.x, y: -p1.y)
}
static postfix func --(p1: inout Point) {
p1.x -= 1
p1.y -= 1
}
static postfix func ++(p1: inout Point) {
p1.x += 1
p1.y += 1
}
static func == (p1:Point,p2:Point)->Bool{
if p1.x == p2.x && p1.y == p2.y{
return true
}
return false
}
static func === (p1:Point,p2:Point)->Bool{
if p1.x == p2.x && p1.y == p2.y {
return true
}
return false
}
}
var p1 = Point(x: 10, y: 10)
var p3 = p1 + p1
print(p1 == p1)
print(p1 === p3)
复制代码
权限控制分为5个层次
open
最高权限,能够被任意模块访问和继承public
次高权限,能够被任意模块访问,不能被其余模块继承重写internal
默认权限,容许在当前模块访问,不容许在其余模块访问fileprivate
容许当前文件中访问private
容许当前定义有效范围访问private
不必定比fileprivate
小,在类中定义属性,fileprivate
访问有效区域大,是整个文件,private
是当前类中,在相同文件全局变量中,private
和fileprivate
有效区域是整个文件。
class test {
// private class testSub{}
// fileprivate class testSub2:testSub{}//报错由于testSub有效区域是test函数,testSub2是真个文件。
}
private class testSub{}
// private 和 fileprivate等价
fileprivate class testSub2:testSub{}
复制代码
当一个属性读权限高,写权限低的时候
class Person {
private(set) var age = 0
fileprivate(set) var weight = 100
}
复制代码
人的年龄不是单独设置,是根据年限变化的,体重是根据吃东西变化的,不是单独能够设置的,因此可读不可写。
@escaping
,通常用到的self
也须要weak
处理//非逃逸闭包
func test (_ fn:()->()){
fn()
}
//逃逸闭包
func test2 (_ fn:@escaping ()->()) -> ()->(){
return fn
}
func test3 (fn:@escaping ()->()) -> Void{
DispatchQueue.global().async {
fn()
}
}
//weak 修饰的逃逸闭包
public class Run {
var age = 0
func test4(_ fn:@escaping ()->()) -> Void {
DispatchQueue.global().async {
fn()
print(self.age)
}
}
}
复制代码
Simultaneous accesses
var step = 1
func plus(_ n:inout Int) {
n += step
}
plus(&step)
复制代码
只须要稍微改动下便可,数值型,copy
是在另一块内存存储step
的值。
var copy = step
plus(©)
复制代码
同一块内存只能同时多度单写,不能读写同时操做
自定义运算符,并和switch混合使用,自定义了几个运算符,而后重载了Int的对比函数。
prefix operator ~=;
prefix operator ~>=;
prefix operator ~<=;
prefix operator ~<;
prefix func ~= (_ v:Int) -> ((Int)->Bool){return{ $0>v}}
prefix func ~>= (_ v:Int) -> ((Int)->Bool){return{ $0 >= v}}
prefix func ~<= (_ v:Int) -> ((Int)->Bool){return{$0 <= v}}
prefix func ~< (_ v:Int) -> ((Int)->Bool){return{$0 < v}}
extension Int{
static func ~=(pattern:(Int)->Bool,value:Int) -> Bool{
return pattern(value)
}
}
var age = 10
switch age {
case ~>=0:
print("~>=0")
case ~<=100:
print("~<=100")
default:
break
}
复制代码
把age当作参数,~>=
是前置运算符,返回一个(Int)->Bool {return{$0 < v}}
闭包,返回值是Bool
类型,根据这个Bool
值进行判断是否进入这个case
。
资料参考