Swift進階02: 類、對象、屬性

Swift編譯過程

編譯過程(OC垦搬、Swift的區(qū)別)

  • OC中通過clang編譯器呼寸,編譯成IR,然后再生成可執(zhí)行文件.o(即機器碼)
  • swift中通過swiftc編譯器猴贰,編譯成IR对雪,然后再生成可執(zhí)行文件

iOS開發(fā)語言,不管是OC還是Swift米绕,后端都是通過LLVM進行編譯的瑟捣,如下圖所示

編譯過程

Swift編譯過程

下面是Swift中的編譯流程,其中SIL(Swift Intermediate Language)栅干,是Swift編譯過程中的中間代碼迈套,主要用于進一步分析和優(yōu)化Swift代碼。如下圖所示碱鳞,SIL位于在ASTLLVM IR之間

Swift編譯過程@2x

我們可以通過swiftc -h終端命令桑李,查看swiftc的所有命令

swiftc-h命令@2x

例如:在main.swift文件定義如下代碼

class YCTeacher {
    var age: Int = 18
    var name: String = "teacher"
}

var t = YCTeacher()
  • 查看抽象語法樹:swiftc -dump-ast main.swift
    swift抽象語法樹@2x
  • 生成SIL文件(Swift Intermediate Language):swiftc -emit-sil main.swift >> ./main.sil,其中main的入口函數(shù)如下:
/ main
// `@main`:標(biāo)識當(dāng)前main.swift的`入口函數(shù)`窿给,SIL中的標(biāo)識符名稱以`@`作為前綴
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
// `%0贵白、%1` 在SIL中叫做寄存器,可以理解為開發(fā)中的常量崩泡,一旦賦值就不可修改禁荒,如果還想繼續(xù)使用,就需要不斷的累加數(shù)字(注意:這里的寄存器角撞,與`register read`中的寄存器是有所區(qū)別的呛伴,這里是指`虛擬寄存器`,而`register read`中是`真寄存器`)
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  // `alloc_global`:創(chuàng)建一個`全局變量`谒所,即代碼中的`t`
  alloc_global @$s4main1tAA9YCTeacherCvp          // id: %2
  // `global_addr`:獲取全局變量地址磷蜀,并賦值給寄存器%3
  %3 = global_addr @$s4main1tAA9YCTeacherCvp : $*YCTeacher // user: %7
  // `metatype`獲取`YCTeacher`的`MetaData`賦值給%4
  %4 = metatype $@thick YCTeacher.Type            // user: %6
  // 將`__allocating_init`的函數(shù)地址賦值給 %5
  // function_ref YCTeacher.__allocating_init()
  %5 = function_ref @$s4main9YCTeacherCACycfC : $@convention(method) (@thick YCTeacher.Type) -> @owned YCTeacher // user: %6
  // `apply`調(diào)用 `__allocating_init` 初始化一個變量,賦值給%6
  %6 = apply %5(%4) : $@convention(method) (@thick YCTeacher.Type) -> @owned YCTeacher // user: %7
  // 將%6的值存儲到%3百炬,即全局變量的地址(這里與前面的%3形成一個閉環(huán))
  store %6 to %3 : $*YCTeacher                    // id: %7
  // 構(gòu)建`Int`,并`return`
  %8 = integer_literal $Builtin.Int32, 0          // user: %9
  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
  return %9 : $Int32                              // id: %10
} // end sil function 'main'
  • 從SIL文件中污它,可以看出剖踊,代碼是經(jīng)過混淆的,可以通過xcrun swift-demangle還原
xcrun swift-demangle s4main1tAA9YCTeacherCvp
$s4main1tAA9YCTeacherCvp ---> main.t : main.YCTeacher
  • SIL更多語法信息衫贬,可參考github地址
  • SIL文件中搜索s4main9YCTeacherC3age4nameACSi_SStcfC德澈,其內(nèi)部主要是分配內(nèi)存+初始化變量
// ************* main入口函數(shù)中的代碼 ****************
// function_ref YCTeacher.__allocating_init(age:name:)
  %13 = function_ref @$s4main9YCTeacherC3age4nameACSi_SStcfC : $@convention(method) (Int, @owned String, @thick YCTeacher.Type) -> @owned YCTeacher // user: %14

// s4main9YCTeacherC3age4nameACSi_SStcfC 實際上就是__allocating_init(age:name:)
// YCTeacher.__allocating_init(age:name:)
sil hidden [exact_self_class] @$s4main9YCTeacherC3age4nameACSi_SStcfC : $@convention(method) (Int, @owned String, @thick YCTeacher.Type) -> @owned YCTeacher {
// %0 "age"                                       // user: %5
// %1 "name"                                      // user: %5
// %2 "$metatype"
bb0(%0 : $Int, %1 : $String, %2 : $@thick YCTeacher.Type):
// 堆上分配內(nèi)存空間
  %3 = alloc_ref $YCTeacher                       // user: %5
  // function_ref YCTeacher.init(age:name:) 初始化當(dāng)前變量
  %4 = function_ref @$s4main9YCTeacherC3age4nameACSi_SStcfc : $@convention(method) (Int, @owned String, @owned YCTeacher) -> @owned YCTeacher // user: %5
  %5 = apply %4(%0, %1, %3) : $@convention(method) (Int, @owned String, @owned YCTeacher) -> @owned YCTeacher // user: %6
  return %5 : $YCTeacher                          // id: %6
} // end sil function '$s4main9YCTeacherC3age4nameACSi_SStcfC'

對象的創(chuàng)建過程

符號斷點調(diào)試

  • 在工程中添加__allocating_init符號斷點
__allocating_init符號斷點
  • 發(fā)現(xiàn)其內(nèi)部調(diào)用的是swift_allocObject
image

源碼調(diào)試

下面我們通過swift_allocObject來探索swift中對象的創(chuàng)建過程

  • REPL(Read Eval PrintLoop)swift交互式解釋器中編寫代碼,也可以拷貝固惯,并在HeapObject.cpp文件中搜索swift_allocObject函數(shù)加一個斷點梆造,然后定義一個實例對象t
image
  • 其中requiredSize是分配的實際內(nèi)存大小,40
  • requiredAlignmentMask是swift中的字節(jié)對齊方式,這個和OC是一樣的镇辉,必須是8的倍數(shù)屡穗,不足的會自動補齊,目的是以空間換時間忽肛,來提高內(nèi)存操作效率

swift_allocObject源碼分析

swift_allocObject源碼如下村砂,主要有以下幾個部分

  • 通過swift_slowAlloc分配內(nèi)存,并進行內(nèi)存字節(jié)對齊
  • 通過new (object) HeapObject(metadata);初始化一個實例對象
  • 函數(shù)的返回值是HeapObject類型屹逛,所以當(dāng)前對象的內(nèi)存結(jié)構(gòu)就是HeapObject的內(nèi)存結(jié)構(gòu)
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask)); // 分配內(nèi)存础废、字節(jié)對齊

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata); // 初始化一個實例對象

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}
  • Heap.cpp文件中,進入swift_slowAlloc函數(shù)罕模,其內(nèi)部主要是通過malloc中分配size大小的內(nèi)存空間评腺,并返回內(nèi)存地址p,主要是用來存儲實例變量
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size); // 堆中創(chuàng)建size大小的內(nèi)存空間淑掌,用于存儲實例變量
#endif
  } else {
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}
  • 進入HeapObject初始化方法蒿讥,需要兩個參數(shù)metadatarefCounts
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

#ifndef __swift__
  HeapObject() = default;

  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  • 其中metadata類型是HeapMetadata锋拖,是一個指針類型诈悍,占8字節(jié)大小
  • refCounts(引用計數(shù),類型是InlineRefCounts兽埃,而InlineRefCounts是一個類RefCounts的別名侥钳,占8個字節(jié)),swift采用arc引用計數(shù)
    RefCounts

總結(jié)

  • 對于實例對象t來說柄错,其本質(zhì)是一個HeapObject 結(jié)構(gòu)體舷夺,默認16字節(jié)內(nèi)存大小(metadata 8字節(jié) + refCounts 8字節(jié))售貌,與OC的對比如下
    • OC中實例對象的本質(zhì)是結(jié)構(gòu)體给猾,是以objc_object為模板繼承的,其中有一個isa指針颂跨,占8字節(jié)
    • Swift中實例對象敢伸,默認的比OC中多了一個refCounted引用計數(shù)大小,默認屬性占16字節(jié)
  • Swift中對象的內(nèi)存分配流程是:__allocating_init --> swift_allocObject_ --> _swift_allocObject --> swift_slowAlloc --> malloc
  • init在其中的職責(zé)就是初始化變量恒削,這點與OC中是一致的

針對上面的分析池颈,我們還遺留了兩個問題:metadata是什么,40是怎么計算的钓丰?下面來繼續(xù)探索
在demo中躯砰,我們可以通過Runtime方法獲取類的內(nèi)存大小

class_getInstanceSize

這點與在源碼調(diào)試時左邊local的requiredSize值是相等的,從HeapObject的分析中我們知道了携丁,一個類在沒有任何屬性的情況下琢歇,默認占用16字節(jié)大小
對于IntString類型,進入其底層定義,兩個都是結(jié)構(gòu)體類型李茫,那么是否都是8字節(jié)呢揭保?可以通過打印其內(nèi)存大小來驗證

//********* Int底層定義 *********
@frozen public struct Int : FixedWidthInteger, SignedInteger {...}

//********* String底層定義 *********
@frozen public struct String {...}

//********* 驗證 *********
print(MemoryLayout<Int>.stride)
print(MemoryLayout<String>.stride)

//********* 打印結(jié)果 *********
8
16
  • 從打印的結(jié)果中可以看出,Int類型占8字節(jié)涌矢,String類型占16字節(jié)(后面文章會進行詳細講解)掖举,這點與OC中是有所區(qū)別的
    所以這也解釋了為什么YCTeacher的內(nèi)存大小等于40,即40 = metadata(8字節(jié)) +refCount(8字節(jié))+ Int(8字節(jié))+ String(16字節(jié))

探索Swift中類的結(jié)構(gòu)

  • 在OC中類是從objc_class模板繼承過來的
  • 在Swift中娜庇,類的結(jié)構(gòu)在底層是HeapObject塔次,其中有 metadata + refCounts

HeapMetadata類型分析

  • 進入HeapMetadata定義,是TargetHeapMetaData類型的別名名秀,接收了一個參數(shù)Inprocess
using HeapMetadata = TargetHeapMetaData<Inprocess>;
  • 進入TargetHeapMetaData定義励负,其本質(zhì)是一個模板類型,其中定義了一些所需的數(shù)據(jù)結(jié)構(gòu)匕得。這個結(jié)構(gòu)體中沒有屬性继榆,只有初始化方法,傳入了一個MetadataKind類型的參數(shù)(該結(jié)構(gòu)體沒有汁掠,那么只有在父類中了)這里的kind就是傳入的Inprocess
//模板類型
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  //初始化方法
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};
  • 進入TargetMetaData定義略吨,有一個kind屬性,kind的類型就是之前傳入的Inprocess考阱。從這里可以得出翠忠,對于kind,其類型就是unsigned long乞榨,主要用于區(qū)分是哪種類型的元數(shù)據(jù)
//******** TargetMetaData 定義 ********
struct TargetMetaData{
   using StoredPointer = typename Runtime: StoredPointer;
    ...
    
    StoredPointer kind;
}

//******** Inprocess 定義 ********
struct Inprocess{
    ...
    using StoredPointer = uintptr_t;
    ...
}

//******** uintptr_t 定義 ********
typedef unsigned long uintptr_t;

TargetHeapMetadata秽之、TargetMetaData定義中,均可以看出初始化方法中參數(shù)kind的類型是MetadataKind

  • 進入MetadataKind定義吃既,里面有一個#include "MetadataKind.def"考榨,點擊進入,其中記錄了所有類型的元數(shù)據(jù)鹦倚,所以kind種類總結(jié)如下
name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF
  • 回到TargetMetaData結(jié)構(gòu)體定義中河质,找方法getClassObject,在該方法中去匹配kind返回值是TargetClassMetadata類型, 如果是Class震叙,則直接對this(當(dāng)前指針掀鹅,即metadata)強轉(zhuǎn)為ClassMetadata
 const TargetClassMetadata<Runtime> *getClassObject() const;
 
//******** 具體實現(xiàn) ********
template<> inline const ClassMetadata *
  Metadata::getClassObject() const {
    //匹配kind
    switch (getKind()) {
      //如果kind是class
    case MetadataKind::Class: {
      // Native Swift class metadata is also the class object.
      //將當(dāng)前指針強轉(zhuǎn)為ClassMetadata類型
      return static_cast<const ClassMetadata *>(this);
    }
    case MetadataKind::ObjCClassWrapper: {
      // Objective-C class objects are referenced by their Swift metadata wrapper.
      auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
      return wrapper->Class;
    }
    // Other kinds of types don't have class objects.
    default:
      return nullptr;
    }
  }

這一點,我們可以通過lldb來驗證

  • po metadata->getKind()捐友,得到其kind是Class
  • po metadata->getClassObject()、x/8g 0x0000000110efdc70溃槐,這個地址中存儲的是元數(shù)據(jù)信息!
image

所以匣砖,TargetMetadataTargetClassMetadata 本質(zhì)上是一樣的,因為在內(nèi)存結(jié)構(gòu)中,可以直接進行指針的轉(zhuǎn)換猴鲫,所以可以說对人,我們認為的結(jié)構(gòu)體,其實就是TargetClassMetadata

  • 進入TargetClassMetadata定義拂共,繼承自TargetAnyClassMetadata牺弄,有以下這些屬性,這也是類結(jié)構(gòu)的部分
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
    ...
    //swift特有的標(biāo)志
    ClassFlags Flags;
    //實例對象內(nèi)存大小
    uint32_t InstanceSize;
    //實例對象內(nèi)存對齊方式
    uint16_t InstanceAlignMask;
    //運行時保留字段
    uint16_t Reserved;
    //類的內(nèi)存大小
    uint32_t ClassSize;
    //類的內(nèi)存首地址
    uint32_t ClassAddressPoint;
  ...
}
  • 進入TargetAnyClassMetadata定義宜狐,繼承自TargetHeapMetadata
template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
    ...
    ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;
    TargetPointer<Runtime, void> CacheData[2];
    StoredSize Data;
    ...
}

總結(jié)

綜上所述势告,當(dāng)metadatakind為Class時,有如下繼承鏈:

image

  • 當(dāng)前類返回的實際類型是 TargetClassMetadata,而TargetMetaData中只有一個屬性kind抚恒,TargetAnyClassMetaData中有4個屬性咱台,分別是kindsuperclass俭驮,cacheData回溺、data(圖中未標(biāo)出)
  • 當(dāng)前Class在內(nèi)存中所存放的屬性TargetClassMetadata屬性 + TargetAnyClassMetaData屬性 + TargetMetaData屬性 構(gòu)成,所以得出的metadata的數(shù)據(jù)結(jié)構(gòu)體如下所示
struct swift_class_t: NSObject{
    void *kind;//相當(dāng)于OC中的isa混萝,kind的實際類型是unsigned long
    void *superClass;
    void *cacheData;
    void *data;
    uint32_t flags; //4字節(jié)
    uint32_t instanceAddressOffset;//4字節(jié)
    uint32_t instanceSize;//4字節(jié)
    uint16_t instanceAlignMask;//2字節(jié)
    uint16_t reserved;//2字節(jié)
    
    uint32_t classSize;//4字節(jié)
    uint32_t classAddressOffset;//4字節(jié)
    void *description;
    ...
}

與OC的區(qū)別

  • 實例對象 & 類

    • OC中的實例對象本質(zhì)結(jié)構(gòu)體遗遵,是通過底層的objc_object模板創(chuàng)建,類是繼承自objc_class
    • Swift中的實例對象本質(zhì)也是結(jié)構(gòu)體逸嘀,類型是HeapObject车要,比OC多了一個refCounts
  • 方法列表

    • OC中的方法存儲在objc_class結(jié)構(gòu)體class_rw_tmethodList
    • swift中的方法存儲在 metadata 元數(shù)據(jù)中
  • 引用計數(shù)

    • OC中的ARC維護的是散列表
    • Swift中的ARC是對象內(nèi)部有一個refCounts屬性

Swift屬性

在swift中,屬性主要分為以下幾種

  • 存儲屬性
  • 計算屬性
  • 延遲存儲屬性
  • 類型屬性

存儲屬性

存儲屬性分為兩種:

  • 常量存儲屬性厘熟,用let修飾
  • 變量存儲屬性屯蹦,用var修飾

存儲屬性特征:會占用分配實例對象的內(nèi)存空間

計算屬性

計算屬性是不占用內(nèi)存空間的,本質(zhì)是set/get方法

屬性觀察者(didSet绳姨、willSet)

  • willSet:新值存儲之前調(diào)用 newValue
  • didSet:新值存儲之后調(diào)用 oldValue

問題1:init方法中是否會觸發(fā)屬性觀察者登澜?
以下代碼中,init方法中設(shè)置name飘庄,是否會觸發(fā)屬性觀察者脑蠕?

class YCTeacher{
    var name: String = "測試"{
        //新值存儲之前調(diào)用
        willSet{
            print("willSet newValue \(newValue)")
        }
        //新值存儲之后調(diào)用
        didSet{
            print("didSet oldValue \(oldValue)")
        }
    }
    
    init() {
        self.name = "teacher"
    }
}

運行結(jié)果發(fā)現(xiàn),并沒有走willSet跪削、didSet中的打印方法谴仙,所以有以下結(jié)論:

  • init方法中,如果調(diào)用屬性碾盐,是不會觸發(fā)屬性觀察者的
  • init中主要是初始化當(dāng)前變量晃跺,除了默認的前16個字節(jié),其他屬性會調(diào)用memset清理內(nèi)存空間(因為有可能是臟數(shù)據(jù)毫玖,即被別人用過)掀虎,然后才會賦值

問題2:哪里可以添加屬性觀察者凌盯?
主要有以下三個地方可以添加:

  • 1、類中定義的存儲屬性
  • 2烹玉、通過類繼承的存儲屬性
  • 3驰怎、通過類繼承的計算屬性

問題3:子類和父類的屬性同時存在didset、willset時二打,其調(diào)用順序是什么县忌?

class YCTeacher{
    var age: Int = 18{
        //新值存儲之前調(diào)用
        willSet{
            print("父類 willSet newValue \(newValue)")
        }
        //新值存儲之后調(diào)用
        didSet{
            print("父類 didSet oldValue \(oldValue)")
        }
    }
    
    var age2: Int {
        get{
            return age
        }
        set{
            self.age = newValue
        }
    }
}


class YCMediumTeacher: YCTeacher{
    override var age: Int{
        //新值存儲之前調(diào)用
        willSet{
            print("子類 newValue \(newValue)")
        }
        //新值存儲之后調(diào)用
        didSet{
            print("子類 didSet oldValue \(oldValue)")
        }
    }
    
}

var t = YCMediumTeacher()
t.age = 20

運行結(jié)果如下:


image

結(jié)論:對于同一個屬性,子類和父類都有屬性觀察者继效,其順序是:先子類willset症杏,后父類willset,再父類didset莲趣, 子類的didset鸳慈,即:子父 父子

延遲存儲屬性

  • 使用lazy修飾的存儲屬性
  • 延遲屬性必須有一個默認的初始值
  • 延遲存儲在第一次訪問的時候才被賦值
  • 延遲存儲屬性并不能保證線程安全
  • 延遲存儲屬性對實例對象大小的影響

類型屬性

  • 使用關(guān)鍵字static修飾,且是一個全局變量
  • 類型屬性必須有一個默認的初始值
  • 類型屬性只會被初始化一次喧伞,線程安全

單例的寫法

//****** Swift單例 ******
class YCTeacher{
    //1走芋、使用 static + let 創(chuàng)建聲明一個實例對象
    static let shareInstance = YCTeacher.init()
    //2、給當(dāng)前init添加private訪問權(quán)限
    private init(){ }
}
//使用(只能通過單例潘鲫,不能通過init)
var t = YCTeacher.shareInstance

//****** OC單例 ******
@implementation YCTeacher
+ (instancetype)shareInstance{
    static YCTeacher *shareInstance = nil;
    dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareInstance = [[YCTeacher alloc] init];
    });
    return shareInstance;
}
@end

總結(jié)

  • 存儲屬性會占用實例變量的內(nèi)存空間
  • 計算屬性不會占用內(nèi)存空間翁逞,其本質(zhì)是set/get方法
  • 屬性觀察者
    • willset:新值存儲之前調(diào)用,先通知子類溉仑,再通知父類(因為父類中可能需要做一些額外的操作)挖函,即子父
    • didSet:新值存儲完成后,先告訴父類浊竟,再通知子類(父類的操作優(yōu)先于子類)怨喘,即父子
    • 類中的init方法賦值不會觸發(fā)屬性觀察
    • 屬性可以添加在 類定義的存儲屬性、繼承的存儲屬性振定、繼承的計算屬性
    • 子類調(diào)用父類的init方法必怜,會觸發(fā)觀察屬性
  • 延遲存儲屬性
    • 使用lazy修飾存儲屬性,且必須有一個默認值
    • 只有在第一次被訪問時才會被賦值后频,且是線程不安全
    • 使用lazy和不使用lazy梳庆,會對實例對象的內(nèi)存大小有影響,主要是因為lazy在底層是optional類型卑惜,optional的本質(zhì)是enum膏执,除了存儲屬性本身的內(nèi)存大小,還需要一個字節(jié)用于存儲case
  • 類型屬性
    • 使用static 修飾露久,且必須有一個默認初始值
    • 是一個全局變量更米,只會被初始化一次,是線程安全
    • 用于創(chuàng)建單例對象:
      • 使用static + let創(chuàng)建實例變量
      • init方法的訪問權(quán)限為private
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毫痕,一起剝皮案震驚了整個濱河市征峦,隨后出現(xiàn)的幾起案子纸巷,更是在濱河造成了極大的恐慌,老刑警劉巖眶痰,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梯啤,居然都是意外死亡竖伯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門因宇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來七婴,“玉大人,你說我怎么就攤上這事察滑〈蚶澹” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵贺辰,是天一觀的道長户盯。 經(jīng)常有香客問我,道長饲化,這世上最難降的妖魔是什么莽鸭? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮吃靠,結(jié)果婚禮上硫眨,老公的妹妹穿的比我還像新娘。我一直安慰自己巢块,他們只是感情好礁阁,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著族奢,像睡著了一般姥闭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上歹鱼,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天泣栈,我揣著相機與錄音,去河邊找鬼弥姻。 笑死南片,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庭敦。 我是一名探鬼主播疼进,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秧廉!你這毒婦竟也來了伞广?” 一聲冷哼從身側(cè)響起拣帽,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚼锄,沒想到半個月后减拭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡区丑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年拧粪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沧侥。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡可霎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宴杀,到底是詐尸還是另有隱情癣朗,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布旺罢,位于F島的核電站旷余,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扁达。R本人自食惡果不足惜荣暮,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望罩驻。 院中可真熱鬧穗酥,春花似錦、人聲如沸惠遏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽节吮。三九已至抽高,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間透绩,已是汗流浹背翘骂。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留帚豪,地道東北人碳竟。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像狸臣,于是被迫代替她去往敵國和親莹桅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容