本篇文章代碼均在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字節
複製代碼
其實枚舉就是的思路和聯合體比較類似,枚舉佔用的空間是其中最大元素的空間+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()
,都會從新申請棧空間來存儲num
,f
和f2
的棧空間的獨立的。 經過彙編看下棧空間的值的變化,在斷點A地方,輸出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傳遞一個變量,直接地址傳遞進去便可,那麼傳遞進去計算屬性,又如何呢?
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
。
資料參考