Swift彙編看枚舉、類、閉包

本篇文章代碼均在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

這種方式在oc中能夠正常使用,可是在Swift中不行。

咱們尋求其餘方式來查看變量內存地址, mj大哥的小工具 使用很簡單或者設置 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字節
複製代碼

其實枚舉就是的思路和聯合體比較類似,枚舉佔用的空間是其中最大元素的空間+1,就是這個枚舉佔用的空間。 利用最後一位來分辯是哪一個類型,不存在 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(),都會從新申請棧空間來存儲numff2的棧空間的獨立的。 經過彙編看下棧空間的值的變化,在斷點A地方,輸出num的佔空間地址

獲得了存儲num的地址。首先在賦值以前查看內存

x/3xg 0x00000001005735f0
0x1005735f0: 0x0000000100002080 0x0000000000000002
0x100573600: 0x00007fff76760ab9
複製代碼

而後運行到斷點B,再次打印num的值

(lldb) x/3xg 0x00000001005735f0
0x1005735f0: 0x0000000100002080 0x0000000000000002
0x100573600: 0x0000000000000005
複製代碼

能夠看到寄存器的值和num值一致。

而後運行一次,第二次斷點到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

當給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本質是引用傳遞(地址傳遞),分狀況分爲地址直接傳遞和副本地址傳遞。

其餘

下標Subscript

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
複製代碼

其實從代碼也能夠看出來,下標就是執行的setget方法,這點就不用匯編分析了。

初始化器

每一個類必定指定一個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能夠作一些必要的配置,另外的便捷初始化能夠單獨處理,這樣子,關鍵代碼不會漏掉。

X.self type(of:X)

在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)的類型。

String

SwiftString也是採用了小數據優化,大數據不優化方案,在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纔是字符串的真正地址,相似OCmask

//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
複製代碼

指針地址

array

數組佔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是當前類中,在相同文件全局變量中,privatefileprivate有效區域是整個文件。

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 )

Simultaneous accesses 

var step = 1
func plus(_ n:inout Int)  {
	 n += step
}
plus(&step)
複製代碼

只須要稍微改動下便可,數值型,copy是在另一塊內存存儲step的值。

var copy = step
plus(&copy)
複製代碼

同一塊內存只能同時多度單寫,不能讀寫同時操做

匹配模式

自定義運算符,並和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

資料參考

相關文章
相關標籤/搜索