Swift進(jìn)階-類與結(jié)構(gòu)體

Swift進(jìn)階-類與結(jié)構(gòu)體
Swift-函數(shù)派發(fā)
Swift進(jìn)階-屬性
Swift進(jìn)階-指針
Swift進(jìn)階-內(nèi)存管理
Swift進(jìn)階-TargetClassMetadata和TargetStructMetadata數(shù)據(jù)結(jié)構(gòu)源碼分析
Swift進(jìn)階-Mirror解析
Swift進(jìn)階-閉包
Swift進(jìn)階-協(xié)議
Swift進(jìn)階-泛型
Swift進(jìn)階-String源碼解析
Swift進(jìn)階-Array源碼解析

類與結(jié)構(gòu)體的異同點(diǎn)

// 定義一個(gè)類或結(jié)構(gòu)體
class/struct Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
  
    deinit { // class
    }
}
相同點(diǎn):
  • 定義存儲(chǔ)的屬性
  • 定義方法
  • 定義下標(biāo)析苫,使用下標(biāo)語(yǔ)法(subscript)提供對(duì)其值的訪問(wèn)
  • 定義初始化器(init)
  • 使用extension來(lái)拓展功能
  • 遵循Protocol來(lái)提供某種功能
不同點(diǎn):
  • class有繼承的特性,struct沒(méi)有繼承特性
  • 類型轉(zhuǎn)換使您能在運(yùn)行時(shí)檢查和解釋class的實(shí)例對(duì)象的類型
  • class有析構(gòu)函數(shù)用來(lái)釋放其占用的資源
  • 引用計(jì)數(shù)允許對(duì)一個(gè)class實(shí)例有多個(gè)引用
  • class是引用類型,struct是值類型
  • 一般情況下蟀架,class存儲(chǔ)在堆區(qū)耐量;struct存儲(chǔ)在棧區(qū)
引用類型
class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}
image.png

這里我們借助兩個(gè)指令來(lái)查看當(dāng)前變量的內(nèi)存結(jié)構(gòu):

p / po 的區(qū)別在于:
po 只會(huì)輸出對(duì)應(yīng)的值;
p 則會(huì)返回值的類型以及命令結(jié)果的引用名财忽。
x/8g (16進(jìn)制地址): 讀取內(nèi)存中的值(8g: 8字節(jié)格式輸出)

引用類型特征圖

看到p1p2變量都引用了同一個(gè)Person的實(shí)例地址猪钮。
所以引用類型存儲(chǔ)的是實(shí)例內(nèi)存地址的引用党饮。

p1p2兩個(gè)變量本身的地址是不一樣的,而他倆內(nèi)存是挨著的友酱,剛好相差8個(gè)字節(jié)晴音,這兩變量的內(nèi)存地址存儲(chǔ)的就是當(dāng)前實(shí)例對(duì)象的內(nèi)存地址:

image.png
值類型
struct Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}
image.png

看到輸出的內(nèi)容就和class的不一樣了。
值類型存儲(chǔ)的就是具體實(shí)例(或者說(shuō)具體的值)缔杉。

值類型特征圖

引用類型 和 值類型 的存儲(chǔ)位置

一般情況下锤躁,值類型存儲(chǔ)在棧上,引用類型存儲(chǔ)在堆上或详。
先來(lái)了解一下我們的內(nèi)存模型:

內(nèi)存分區(qū)

我們把系統(tǒng)分配給app的可操作性的內(nèi)存空間人為地分成五大區(qū):指令區(qū)系羞、常量區(qū)郭计、全局(靜態(tài))區(qū)、堆區(qū)椒振、棧區(qū)

棧區(qū)(stack): 局部變量和函數(shù)運(yùn)行過(guò)程中的上下文
堆區(qū)(Heap): 存儲(chǔ)所有對(duì)象
Global: 存儲(chǔ)全局變量昭伸;常量;代碼區(qū)

Segment & Section: Mach-O 文件有多個(gè)段( Segment )杠人,每個(gè)段有不同的功能勋乾。然后每 個(gè)段又分為很多小的 Section

name value
TEXT.text 機(jī)器碼
TEXT.cstring 硬編碼的字符串
TEXT.const 初始化過(guò)的常量
DATA.data 初始化過(guò)的可變的(靜態(tài)/全局)數(shù)據(jù)
DATA.const 沒(méi)有初始化過(guò)的常量
DATA.bss 沒(méi)有初始化的(靜態(tài)/全局)變量
DATA.common 沒(méi)有初始化過(guò)的符號(hào)聲明
LLDB調(diào)試內(nèi)存分布
frame variable -L xxx

結(jié)構(gòu)體的內(nèi)存分布:

struct Person {
    var age = 18
    var name = "a"
}
frame variable -L 指令調(diào)試

struct是值類型,所以存放第一個(gè)首地址是指向age的嗡善,并且很明顯struct存儲(chǔ)在棧區(qū),因?yàn)榈刂肥沁B續(xù)的学歧,age到name剛好中間相差8個(gè)字節(jié)罩引。

當(dāng)前結(jié)構(gòu)體在內(nèi)存中分布示意圖

如果struct的成員包含了一個(gè)引用類型呢?

struct Person {
    var age: Int = 18
    var name: String = "a"
    var t = Teacher()
}

class Teacher {
    var age = 10
    var name = "tearcher"
}
image.png

age枝笨、name和t在棧區(qū)這沒(méi)有問(wèn)題袁铐,而Teacher開辟的地址0x0000600000ec1080在堆區(qū)。

類的內(nèi)存分布:

class Person {
    var age = 18
    var name = "a"
}
frame variable -L 指令調(diào)試

我們知道p1存儲(chǔ)在方法棧上横浑,Person實(shí)例的地址是0x00006000024a9b90剔桨,仍然要在堆區(qū)中開辟內(nèi)存空間。

當(dāng)前類的實(shí)例在內(nèi)存中分布示意圖

類在內(nèi)存分配的時(shí)候徙融,會(huì)在堆空間上找到合適的內(nèi)存區(qū)域洒缀,找到和內(nèi)存區(qū)域后就會(huì)把這個(gè)內(nèi)存地址拷貝到堆,然后棧區(qū)的內(nèi)存地址指向這個(gè)當(dāng)前的堆區(qū)欺冀。
離開作用域的時(shí)候树绩,勢(shì)必要回收內(nèi)存空間,這個(gè)時(shí)候先查找類的內(nèi)存空間隐轩,并把內(nèi)存塊歸重新插入到堆空間中饺饭,棧區(qū)地址不再指向堆區(qū)。

對(duì)于引用類型來(lái)說(shuō)职车,創(chuàng)建和銷毀都必須有一個(gè)查找的過(guò)程瘫俊,會(huì)有時(shí)間和速度上的損耗,并且對(duì)于引用計(jì)數(shù)的計(jì)算也是消耗性能的

舉例:
一個(gè)聊天室創(chuàng)建一個(gè)聊天氣泡(makeBalloon)悴灵,通過(guò)Color扛芽、Orientation、Tail來(lái)作為字符串的key從緩存中取出氣泡img

enum Color { case blue, green, gray}
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }

var cache = [String: UIImage]()

func makeBalloon(_ color: Color, _ orientation: Orientation, _ tail: Tail) {
    let key = "\(color):\(orientation):\(tail)"
    if let image = cache[key] {
        return image
    }
    ...
}

上面這段代碼雖然我們做了image的緩存称勋,但是key是一個(gè)字符串(是一個(gè)表型為值類型的引用類型)存儲(chǔ)在堆區(qū)胸哥,在每次調(diào)用makeBalloon時(shí)候,仍然要從堆空間中不斷地分配/銷毀內(nèi)存赡鲜。

優(yōu)化后的代碼:

enum Color { case blue, green, gray}
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }

struct Ballon: Hashable {
    var color: Color
    var orientation: Orientation
    var tail: Tail
}

func makeBalloon(_ ballon: Ballon) {
    if let image = cache[ballon] {
        return image
    }
    ...
}

在我們實(shí)際開發(fā)當(dāng)中盡可能地使用struct代替class空厌,如果諸如繼承這些關(guān)系庐船,那可以選用class。

初始化器

結(jié)構(gòu)體不需要聲明初始化器嘲更,系統(tǒng)默認(rèn)自動(dòng)提供成員變量初始化器筐钟。

struct Person {
    var age: Int
    var name: String
}

類在聲明的時(shí)候必須給予一個(gè)指定初始化器,同時(shí)我們也可以提供便捷初始化器赋朦、可失敗初始化器必要初始化器

class Person {
     var  age: Int
     var name: String 
     init(_ age: Int, _ name: String) {
          self.age = age
          self.name = name
     }

     convenience init(_ age: Int) {
        self.init(age, "名稱")
     }
}

class Son: Person {
      var subName: String
      init(_ subName: String) {
          self.subName = subName
          super.init(18, "wj")
      }
}

可失敗初始化器:

class Person {
     var  age: Int
     var name: String 
     init?(_ age: Int, _ name: String) {
          if age < 18 {return nil}
          self.age = age
          self.name = name
     }

     convenience init?(_ age: Int) {
        self.init(age, "名稱")
     }
}

必要初始化器(繼承下去的子類必須實(shí)現(xiàn)該初始化器):

class Person {
     var  age: Int
     var name: String 
     required init(_ age: Int, _ name: String) {
          self.age = age
          self.name = name
     }

     convenience init(_ age: Int) {
        self.init(age, "名稱")
     }
}

類的生命周期

iOS開發(fā)的語(yǔ)言不管是OC還是Swift后端都是通過(guò)LLVM進(jìn)行編譯的篓冲,如下圖所示:

image.png

OC 通過(guò) clang 編譯器編譯成 IR,然后再生成可執(zhí)行文件 .o(這里也就是我們的機(jī)器碼);
Swift 則是通過(guò) Swift 編譯器編譯成 IR宠哄,然后在生成可執(zhí)行文件壹将。

Swift編譯過(guò)程
// 語(yǔ)法分析分析輸出AST(抽象語(yǔ)法樹)
swiftc main.swift -dump-parse 

// 語(yǔ)義分析并且檢查類型輸出AST
swiftc main.swift -dump-ast

// 生成swift中間體語(yǔ)言(SIL)未優(yōu)化
swiftc main.swift -emit-silgen

// 生成swift中間體語(yǔ)言(SIL)已優(yōu)化
swiftc main.swift -emit-sil

// 生成LLVM中間體語(yǔ)言 (.ll文件)
swiftc main.swift -emit-ir

// 生成LLVM中間體語(yǔ)言 (.bc文件)
swiftc main.swift -emit-bc

// 生成匯編
swiftc main.swift -emit-assembly

// 編譯生成可執(zhí)行.out文件 (x86、arm64....)
swiftc -o main.o main.swift

可以通過(guò)上面的命令自行嘗試編譯過(guò)程毛嫉。

// 還原類名
xcrun swift-demangle xxx   // xxx是經(jīng)過(guò)混寫規(guī)則的類名
class Person{
    var age = 18
    var name = "LGMan"
}

var p = Person()

Person()創(chuàng)建的時(shí)候打個(gè)斷點(diǎn)調(diào)試诽俯,Debug->Debug Workflow -> Always Show Disassembly

SwiftTest.Person.__allocating_init()

Person是純swift類,在創(chuàng)建實(shí)例的時(shí)候承粤,會(huì)調(diào)用SwiftTest.Person.__allocating_init()暴区; 底層會(huì)調(diào)用swift_allocObjectSwiftTest.Person.init()

Person是繼承NSObject的類辛臊,在創(chuàng)建實(shí)例的時(shí)候仙粱,會(huì)調(diào)用SwiftTest.Person.__allocating_init();底層會(huì)調(diào)用objc_allocWithZoneobjc_msgSend 發(fā)送init消息彻舰。

來(lái)看swift源碼 swift_allocObject 底層調(diào)用分配內(nèi)存

全局搜索swift_allocObject伐割,在HeapObject.cpp文件找到swift_allocObject,它會(huì)調(diào)用_swift_allocObject_函數(shù):

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));

  // 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;
}

里面調(diào)用了swift_slowAlloc函數(shù)返回了一個(gè) HeapObject 泛型對(duì)象淹遵,全局搜索這個(gè)函數(shù)口猜,來(lái)到Heap.cpp里的swift_slowAlloc

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__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);
#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;
}

看到這行熟悉代碼 p = malloc(size); 進(jìn)行了內(nèi)存分配,最后并返回了p

Swift 對(duì)象內(nèi)存分配__allocating_init -> swift_allocObject -> _swift_allocObject_ -> swift_slowAlloc -> malloc

再回來(lái) _swift_allocObject_ 函數(shù)看接下來(lái)的邏輯

_swift_allocObject_

對(duì)object進(jìn)行初始化透揣,可以來(lái)看看HeapObject的結(jié)構(gòu):

  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

Swift 對(duì)象的內(nèi)存結(jié)構(gòu): HeapObject (OC objc_object) 济炎,有兩個(gè)屬性各占8字節(jié): MetadataRefCount,默認(rèn)占用 16 字節(jié)大小辐真。

RefCount是一個(gè)64位的引用計(jì)數(shù)须尚,那么HeapMetadata到底是什么呢?

源碼分析class的數(shù)據(jù)結(jié)構(gòu)

HeapMetadata

HeapMetadata

HeapMetadata 起了別名 TargetHeapMetadata

Metadata.h

所以swift類的 HeapMetadata / TargetHeapMetadata 是通過(guò) kind進(jìn)行初始化的侍咱。

MetadataKind的定義:

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里面有一個(gè)函數(shù):

image.png

這里的this就是TargetMetadata實(shí)例耐床,可以看成objc里的isa,而
TargetClassMetadata是swift所有類型元類的最終基類(OC objc_class)楔脯。

/// The structure of all class metadata.  This structure is embedded
/// directly within the class's heap metadata structure and therefore
/// cannot be extended without an ABI break.
///
/// Note that the layout of this type is compatible with the layout of
/// an Objective-C class.
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;

  TargetClassMetadata() = default;
  constexpr TargetClassMetadata(const TargetAnyClassMetadata<Runtime> &base,
             ClassFlags flags,
             ClassIVarDestroyer *ivarDestroyer,
             StoredPointer size, StoredPointer addressPoint,
             StoredPointer alignMask,
             StoredPointer classSize, StoredPointer classAddressPoint)
    : TargetAnyClassMetadata<Runtime>(base),
      Flags(flags), InstanceAddressPoint(addressPoint),
      InstanceSize(size), InstanceAlignMask(alignMask),
      Reserved(0), ClassSize(classSize), ClassAddressPoint(classAddressPoint),
      Description(nullptr), IVarDestroyer(ivarDestroyer) {}

  // The remaining fields are valid only when isTypeMetadata().
  // The Objective-C runtime knows the offsets to some of these fields.
  // Be careful when accessing them.

  /// Swift-specific class flags.
  ClassFlags Flags;

  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;

  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;

  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;

  /// Reserved for runtime use.
  uint16_t Reserved;

  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;

  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;

  // Description is by far the most likely field for a client to try
  // to access directly, so we force access to go through accessors.
private:
  /// An out-of-line Swift-specific description of the type, or null
  /// if this is an artificial subclass.  We currently provide no
  /// supported mechanism for making a non-artificial subclass
  /// dynamically.
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;

public:
  /// A function for destroying instance variables, used to clean up after an
  /// early return from a constructor. If null, no clean up will be performed
  /// and all ivars must be trivial.
  TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;

  // After this come the class members, laid out as follows:
  //   - class members for the superclass (recursively)
  //   - metadata reference for the parent, if applicable
  //   - generic parameters for this class
  //   - class variables (if we choose to support these)
  //   - "tabulated" virtual methods

  using TargetAnyClassMetadata<Runtime>::isTypeMetadata;

  ConstTargetMetadataPointer<Runtime, TargetClassDescriptor>
  getDescription() const {
    assert(isTypeMetadata());
    return Description;
  }

  typename Runtime::StoredSignedPointer
  getDescriptionAsSignedPointer() const {
    assert(isTypeMetadata());
    return Description;
  }

  void setDescription(const TargetClassDescriptor<Runtime> *description) {
    Description = description;
  }

  // [NOTE: Dynamic-subclass-KVO]
  //
  // Using Objective-C runtime, KVO can modify object behavior without needing
  // to modify the object's code. This is done by dynamically creating an
  // artificial subclass of the the object's type.
  //
  // The isa pointer of the observed object is swapped out to point to
  // the artificial subclass, which has the following properties:
  // - Setters for observed keys are overridden to additionally post
  // notifications.
  // - The `-class` method is overridden to return the original class type
  // instead of the artificial subclass type.
  //
  // For more details, see:
  // https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html

  /// Is this class an artificial subclass, such as one dynamically
  /// created for various dynamic purposes like KVO?
  /// See [NOTE: Dynamic-subclass-KVO]
  bool isArtificialSubclass() const {
    assert(isTypeMetadata());
    return Description == nullptr;
  }
  void setArtificialSubclass() {
    assert(isTypeMetadata());
    Description = nullptr;
  }

  ClassFlags getFlags() const {
    assert(isTypeMetadata());
    return Flags;
  }
  void setFlags(ClassFlags flags) {
    assert(isTypeMetadata());
    Flags = flags;
  }

  StoredSize getInstanceSize() const {
    assert(isTypeMetadata());
    return InstanceSize;
  }
  void setInstanceSize(StoredSize size) {
    assert(isTypeMetadata());
    InstanceSize = size;
  }

  StoredPointer getInstanceAddressPoint() const {
    assert(isTypeMetadata());
    return InstanceAddressPoint;
  }
  void setInstanceAddressPoint(StoredSize size) {
    assert(isTypeMetadata());
    InstanceAddressPoint = size;
  }

  StoredPointer getInstanceAlignMask() const {
    assert(isTypeMetadata());
    return InstanceAlignMask;
  }
  void setInstanceAlignMask(StoredSize mask) {
    assert(isTypeMetadata());
    InstanceAlignMask = mask;
  }

  StoredPointer getClassSize() const {
    assert(isTypeMetadata());
    return ClassSize;
  }
  void setClassSize(StoredSize size) {
    assert(isTypeMetadata());
    ClassSize = size;
  }

  StoredPointer getClassAddressPoint() const {
    assert(isTypeMetadata());
    return ClassAddressPoint;
  }
  void setClassAddressPoint(StoredSize offset) {
    assert(isTypeMetadata());
    ClassAddressPoint = offset;
  }

  uint16_t getRuntimeReservedData() const {
    assert(isTypeMetadata());
    return Reserved;
  }
  void setRuntimeReservedData(uint16_t data) {
    assert(isTypeMetadata());
    Reserved = data;
  }

  /// Get a pointer to the field offset vector, if present, or null.
  const StoredPointer *getFieldOffsets() const {
    assert(isTypeMetadata());
    auto offset = getDescription()->getFieldOffsetVectorOffset();
    if (offset == 0)
      return nullptr;
    auto asWords = reinterpret_cast<const void * const*>(this);
    return reinterpret_cast<const StoredPointer *>(asWords + offset);
  }

  uint32_t getSizeInWords() const {
    assert(isTypeMetadata());
    uint32_t size = getClassSize() - getClassAddressPoint();
    assert(size % sizeof(StoredPointer) == 0);
    return size / sizeof(StoredPointer);
  }

  /// Given that this class is serving as the superclass of a Swift class,
  /// return its bounds as metadata.
  ///
  /// Note that the ImmediateMembersOffset member will not be meaningful.
  TargetClassMetadataBounds<Runtime>
  getClassBoundsAsSwiftSuperclass() const {
    using Bounds = TargetClassMetadataBounds<Runtime>;

    auto rootBounds = Bounds::forSwiftRootClass();

    // If the class is not type metadata, just use the root-class bounds.
    if (!isTypeMetadata())
      return rootBounds;

    // Otherwise, pull out the bounds from the metadata.
    auto bounds = Bounds::forAddressPointAndSize(getClassAddressPoint(),
                                                 getClassSize());

    // Round the bounds up to the required dimensions.
    if (bounds.NegativeSizeInWords < rootBounds.NegativeSizeInWords)
      bounds.NegativeSizeInWords = rootBounds.NegativeSizeInWords;
    if (bounds.PositiveSizeInWords < rootBounds.PositiveSizeInWords)
      bounds.PositiveSizeInWords = rootBounds.PositiveSizeInWords;

    return bounds;
  }

#if SWIFT_OBJC_INTEROP
  /// Given a statically-emitted metadata template, this sets the correct
  /// "is Swift" bit for the current runtime. Depending on the deployment
  /// target a binary was compiled for, statically emitted metadata templates
  /// may have a different bit set from the one that this runtime canonically
  /// considers the "is Swift" bit.
  void setAsTypeMetadata() {
    // If the wrong "is Swift" bit is set, set the correct one.
    //
    // Note that the only time we should see the "new" bit set while
    // expecting the "old" one is when running a binary built for a
    // new OS on an old OS, which is not supported, however we do
    // have tests that exercise this scenario.
    auto otherSwiftBit = (3ULL - SWIFT_CLASS_IS_SWIFT_MASK);
    assert(otherSwiftBit == 1ULL || otherSwiftBit == 2ULL);

    if ((this->Data & 3) == otherSwiftBit) {
      this->Data ^= 3;
    }

    // Otherwise there should be nothing to do, since only the old "is
    // Swift" bit is used for backward-deployed runtimes.
    
    assert(isTypeMetadata());
  }
#endif

  bool isStaticallySpecializedGenericMetadata() const {
    auto *description = getDescription();
    if (!description->isGeneric())
      return false;

    return this->Flags & ClassFlags::IsStaticSpecialization;
  }

  bool isCanonicalStaticallySpecializedGenericMetadata() const {
    auto *description = getDescription();
    if (!description->isGeneric())
      return false;

    return this->Flags & ClassFlags::IsCanonicalStaticSpecialization;
  }

  static bool classof(const TargetMetadata<Runtime> *metadata) {
    return metadata->getKind() == MetadataKind::Class;
  }
};
using ClassMetadata = TargetClassMetadata<InProcess>;

TargetClassMetadata的父類TargetAnyClassMetadata就有熟悉的感覺(jué)了:

/// The portion of a class metadata object that is compatible with
/// all classes, even non-Swift ones.
template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;

#if SWIFT_OBJC_INTEROP
  constexpr TargetAnyClassMetadata(TargetAnyClassMetadata<Runtime> *isa,
                                   TargetClassMetadata<Runtime> *superclass)
    : TargetHeapMetadata<Runtime>(isa),
      Superclass(superclass),
      CacheData{nullptr, nullptr},
      Data(SWIFT_CLASS_IS_SWIFT_MASK) {}
#endif

  constexpr TargetAnyClassMetadata(TargetClassMetadata<Runtime> *superclass)
    : TargetHeapMetadata<Runtime>(MetadataKind::Class),
      Superclass(superclass)
#if SWIFT_OBJC_INTEROP
      , CacheData{nullptr, nullptr},
      Data(SWIFT_CLASS_IS_SWIFT_MASK)
#endif
      {}

#if SWIFT_OBJC_INTEROP
  // Allow setting the metadata kind to a class ISA on class metadata.
  using TargetMetadata<Runtime>::getClassISA;
  using TargetMetadata<Runtime>::setClassISA;
#endif

  // Note that ObjC classes do not have a metadata header.

  /// The metadata for the superclass.  This is null for the root class.
  TargetSignedPointer<Runtime, const TargetClassMetadata<Runtime> *
                                   __ptrauth_swift_objc_superclass>
      Superclass;

#if SWIFT_OBJC_INTEROP
  /// The cache data is used for certain dynamic lookups; it is owned
  /// by the runtime and generally needs to interoperate with
  /// Objective-C's use.
  TargetPointer<Runtime, void> CacheData[2];

  /// The data pointer is used for out-of-line metadata and is
  /// generally opaque, except that the compiler sets the low bit in
  /// order to indicate that this is a Swift metatype and therefore
  /// that the type metadata header is present.
  StoredSize Data;
  
  static constexpr StoredPointer offsetToData() {
    return offsetof(TargetAnyClassMetadata, Data);
  }
#endif

  /// Is this object a valid swift type metadata?  That is, can it be
  /// safely downcast to ClassMetadata?
  bool isTypeMetadata() const {
#if SWIFT_OBJC_INTEROP
    return (Data & SWIFT_CLASS_IS_SWIFT_MASK);
#else
    return true;
#endif
  }
  /// A different perspective on the same bit
  bool isPureObjC() const {
    return !isTypeMetadata();
  }
};
using AnyClassMetadata =
  TargetAnyClassMetadata<InProcess>;

using ClassIVarDestroyer =
  SWIFT_CC(swift) void(SWIFT_CONTEXT HeapObject *);

TargetAnyClassMetadata的數(shù)據(jù)結(jié)構(gòu)里有我們所熟悉的 Superclass撩轰、ClassISACacheDataData

這里面的數(shù)據(jù)結(jié)構(gòu)就是我們的最終答案
經(jīng)過(guò)源碼分析我們不難得出 swift 類的數(shù)據(jù)結(jié)構(gòu)

struct Metadata { 
      var kind: Int 
      var superClass: Any.Type 
      var cacheData: (Int, Int) 
      var data: Int 
      var classFlags: Int32 
      var instanceAddressPoint: UInt32 
      var instanceSize: UInt32 
      var instanceAlignmentMask: UInt16 
      var reserved: UInt16 
      var classSize: UInt32 
      var classAddressPoint: UInt32 
      var typeDescriptor: UnsafeMutableRawPointer 
      var iVarDestroyer: UnsafeRawPointer
 }

異變方法

swift中class和struct都能定義方法func堪嫂。但是區(qū)別在于:
默認(rèn)情況下偎箫,值類型的屬性不能被自身的實(shí)例方法修改的
來(lái)看看下面這個(gè)案例:

值類型改變自身錯(cuò)誤演示

此時(shí)self就是結(jié)構(gòu)體自己皆串,指代x, y淹办。當(dāng)調(diào)用moveBy方法時(shí)候,就相當(dāng)于p在修改自己恶复,此時(shí)是不被允許的怜森。

改變自身的實(shí)例方法前面需要添加mutating修飾,此時(shí)才能編譯成功:

struct Point {
    var x = 0.0, y = 0.0  
    mutating func moveBy(x: Double, y: Double) {
        self.x += x
        self.y += y
    }
}

一個(gè)用mutating修飾谤牡,一個(gè)沒(méi)有的情況副硅,編譯成swift中間代碼 sil 來(lái)看一下:

struct Point {
    var x = 0.0, y = 0.0
    
    func test() {
        let tmp = self.x
    }
    
    mutating func moveBy(x: Double, y: Double) {
        self.x += x
        self.y += y
    }
}

上面已有編譯命令,自行使用翅萤。下面這個(gè)是輸出sil的Point結(jié)構(gòu)體:

Point結(jié)構(gòu)體 sil
sil的Point的關(guān)注的方法

swift中函數(shù)的參數(shù)默認(rèn)在最后是傳遞self的想许,而objective-c是在方法列表前面默認(rèn)傳遞selfcmd

找到testmoveBy方法断序,可以看出moveBy方法前加了mutating修飾其參數(shù)后面加了個(gè)@inout

SIL 文檔的解釋 @inout
An @inout parameter is indirect. The address must be of an initialized object.(當(dāng)前參數(shù) 類型是間接的糜烹,傳遞的是已經(jīng)初始化過(guò)的地址

也就是說(shuō)moveBy的默認(rèn)參數(shù) @inout Point 其實(shí)是一個(gè)地址违诗,而test的默認(rèn)參數(shù)就是一個(gè)結(jié)構(gòu)體的值。

再來(lái)關(guān)注這兩句代碼
test:

  debug_value %0 : $Point, let, name "self", argno 1 // id: %1

相當(dāng)于是偽代碼:let self = Point疮蹦,let在swift中是不可修改的诸迟,并且self取的是一個(gè)值。

moveBy:

  debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5

相當(dāng)于是偽代碼:var self = &Point愕乎,var在swift中是可修改的阵苇,并且self取的地址。

區(qū)別舉例:

區(qū)別舉例

值類型的實(shí)例方法前面添加了mutating關(guān)鍵字感论,其默認(rèn)參數(shù)會(huì)增加一個(gè)@inout绅项,而這個(gè)@inout修飾的參數(shù)相當(dāng)于傳遞一個(gè)地址。

案例一:

var age = 10

func modify(_ age: inout Int) {
    var tmp = age 
    tmp += 1
}

modify(&age)
print(age)  // 10

我們傳遞了age地址進(jìn)去了比肄,此時(shí)的age打印的還是10快耿,為什么?

方法體里的age是一個(gè)值芳绩,這個(gè)值取的是外部變量age的地址的值掀亥。所以 var tmp = age 是值類型的賦值,并不會(huì)外部變量age那個(gè)地址存儲(chǔ)的值妥色。

編譯后的sil

偽代碼:
var age = &age
var tmp = (withUnsafePoint(to: &age) {return $0}).pointee

案例二:

func modify(_ age: inout Int) {
    age += 1
}

modify(&age)
print(age) // 11

此時(shí)age += 1就變得好使了搪花,age是11,因?yàn)閮?nèi)部age取的是外部age的地址,可以改變地址的值撮竿。

方法調(diào)度


類的方法調(diào)度

objective-c的方法調(diào)度是以消息發(fā)送的方式吮便。swift的方法調(diào)度是以什么方式?

首先我們來(lái)了解一下常見匯編指令倚聚,然后再匯編下調(diào)試方法調(diào)度的過(guò)程线衫。

mov: 將某一寄存器的值復(fù)制到另一寄存器(只能用于寄存器與寄存器或者寄存器
與常量之間傳值,不能用于內(nèi)存地址)惑折,如:
mov x1, x0  // 將寄存器x0的值賦值到寄存器x1中

add: 將某一寄存器的值和另一寄存器的值 相加 并將結(jié)果保存在另一寄存器中授账,如:
add x0, x1, x2 // 將寄存器x1和x2的值相加后,保存到x0中

sub: 將某一寄存器的值和另一寄存器的值 相減 并將結(jié)果保存在另一寄存器中:
sub x0, x1, x2 // 將寄存器x1和x2的 值相減后惨驶,保存到x0中

and: 將某一寄存器的值和另一寄存器的值 按位與 并將結(jié)果保存到另一寄存器中白热,如:
and x0, x0, #0x1  // 將寄存器x0的值和常量1 按位 與 之后保存到寄存器x0中

orr: 將某一寄存器的值和另一寄存器的值 按位或 并將結(jié)果保存到另一寄存器中,如:
orr x0, x0, #0x1 // 將寄存器x0的值和常量1 按位 或 之后保存到集成器x0中

str : 將寄存器中的值寫入到內(nèi)存中,如:
str x0, [x0, x8]  // 將寄存器x0的值保存到棧內(nèi)存 [x0 + x8]處

ldr: 將內(nèi)存中的值讀取到寄存器中粗卜,如:
ldr x0, [x1, x2] 將寄存器x1和x2的值相加作為地址屋确,取該內(nèi)存地址的值,放入寄存器x0中

cbz: 和 0 比較续扔,如果結(jié)果為零就轉(zhuǎn)移(只能跳到后面的指令)
cbnz: 和非 0 比較攻臀,如果結(jié)果非零就轉(zhuǎn)移(只能跳到后面的指令)
cmp: 比較指令
blr: (branch)跳轉(zhuǎn)到某地址(無(wú)返回)
bl: 跳轉(zhuǎn)到某地址(有返回)
ret: 子程序(函數(shù)調(diào)用)返回指令,返回地址已默認(rèn)保存在寄存器 lr (x30) 中

小tip:在看方法調(diào)用的時(shí)候纱昧,關(guān)注bl和blr刨啸。

新建一個(gè)工程,在ViewController.swift

// ViewController.swift
import UIKit
class Teacher {
    func teach() {
        print("teach")
    }
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        let t = LGTeacher()
        t.teach()
    }
}

設(shè)置匯編調(diào)試识脆,然后在真機(jī)上運(yùn)行代碼(arm64的匯編):

設(shè)置匯編調(diào)試

斷點(diǎn)打在 t.teach() 來(lái)看看匯編

匯編

__allocating_init()swift_release之間的 blr x8 就是teach方法的調(diào)用设联,然后我們來(lái)看看blr x8的調(diào)用里面是啥,推測(cè)對(duì)不對(duì):

teach匯編內(nèi)部調(diào)用

那么swift在方法調(diào)用的時(shí)候是怎么調(diào)用的呢灼捂,我這里給Teacher擴(kuò)充兩個(gè)方法:

class Teacher{
    func teach() {
        print("teach")
    }
    func teach1(){
        print("teach1")
    }
    func teach2(){
        print("teach2")
    }
}

在viewDidLoad都去調(diào)用運(yùn)行离例,看匯編找到這三個(gè)方法調(diào)用

image.png
image.png

可以看到三個(gè)函數(shù)的內(nèi)存是連續(xù)的,并且都相差8個(gè)字節(jié)悉稠。
分析第一個(gè)teach:

分析第一個(gè)teach

__allocating_init 的返回值放在x0寄存器里宫蛆,它現(xiàn)在存的是實(shí)例對(duì)象

mov x20, x0  // 將x0的值賦值給x20
str x20, [sp, #0x8] // #0x8入棧,將x20的值保存到棧內(nèi)存
str x20, [sp, #0x10] // #0x10入棧偎球,將x20的值保存到棧內(nèi)存
ldr x8, [x20]  // 取x20地址的值給到 x8寄存器洒扎,這里[x20]取地址就是對(duì)象的前8個(gè)字節(jié):metadata
ldr x8, [x8, #0x50] // 寄存器x8(metadata address)和地址#0x50的值相加,取地址存放到x8

ldr x8, [x20] 這里[x20]取地址就是對(duì)象的前8個(gè)字節(jié):metadata衰絮。
執(zhí)行后 x8到底是不是metadata:

image.png

驗(yàn)證結(jié)果是metadata 接著再執(zhí)行 ldr x8, [x8, #0x50] 相當(dāng)于是 (metadata address value) + (0x50 value) = teach袍冷。最后就是執(zhí)行teach了。
ps:0x50是編譯的時(shí)候猫牡,系統(tǒng)就確定了的胡诗。
而三個(gè)teach函數(shù)在方法棧的內(nèi)存中是連續(xù)內(nèi)存空間,并且剛好相差了8個(gè)字節(jié)(函數(shù)指針的大小):0x50、0x58煌恢、0x60

所以teach方法的調(diào)用過(guò)程:找到Metadata尝苇,確定函數(shù)地址(metadata + 偏移量)枢贿,執(zhí)行函數(shù)担钮。

驗(yàn)證函數(shù)表調(diào)度

上面可以看出举户,swift其中的一種方法調(diào)用方式:函數(shù)表調(diào)度
把上面那個(gè)ViewController.swift編譯成sil文件二汛,打開并拖拽文件最后:

ViewController.sil

這個(gè)sil_vtable就是class自己的函數(shù)表婿崭。不相信?好吧肴颊,繼續(xù)驗(yàn)證氓栈。
上面分析出Metadata的數(shù)據(jù)結(jié)構(gòu)是這樣的:

struct Metadata { 
      var kind: Int 
      var superClass: Any.Type 
      var cacheData: (Int, Int) 
      var data: Int 
      var classFlags: Int32 
      var instanceAddressPoint: UInt32 
      var instanceSize: UInt32 
      var instanceAlignmentMask: UInt16 
      var reserved: UInt16 
      var classSize: UInt32 
      var classAddressPoint: UInt32 
      var typeDescriptor: UnsafeMutableRawPointer 
      var iVarDestroyer: UnsafeRawPointer
 }

其中需要關(guān)注typeDescriptor,不管是class/struct/enum都有自己的Descriptor婿着,它就是對(duì)類的一個(gè)詳細(xì)描述授瘦。

找到swift源碼 TargetClassMetadata

image.png

找到 Description 成員變量

image.png

TargetClassDescriptor就是上面說(shuō)的描述,經(jīng)過(guò)分析得出其數(shù)據(jù)結(jié)構(gòu):

struct TargetClassDescriptor{ 
      var flags: UInt32 
      var parent: UInt32 
      var name: Int32   // class/struct/enum 的名稱
      var accessFunctionPointer: Int32 
      var fieldDescriptor: Int32 
      var superClassType: Int32 
      var metadataNegativeSizeInWords: UInt32 
      var metadataPositiveSizeInWords: UInt32 
      var numImmediateMembers: UInt32 
      var numFields: UInt32 
      var fieldOffsetVectorOffset: UInt32 
      var Offset: UInt32 
      // var size: UInt32 
      // V-Table  (methods) 
}

TargetClassDescriptor本身的結(jié)構(gòu)是沒(méi)有 V-Table 的竟宋,進(jìn)而我從源碼里面找推測(cè)出來(lái)的提完,下面就開始推測(cè)流程:
繼續(xù)找全局搜TargetClassDescriptor唄:

using ClassDescriptor = TargetClassDescriptor<InProcess>;

再全局搜這個(gè)別名ClassDescriptor,定位到一個(gè)類描述生成器里面:

ClassContextDescriptorBuilder

ClassContextDescriptorBuilder這個(gè)類是用來(lái)創(chuàng)建當(dāng)前的Matedata和Descriptor用的丘侠。然后找到layout函數(shù):

layout

首先來(lái)看看 super::layout() 做了啥:

super::layout()

這里的各種add是不是與上面的TargetClassDescriptor數(shù)據(jù)結(jié)構(gòu)有點(diǎn)類似了氯葬,這個(gè)layout就是在創(chuàng)建Descriptor進(jìn)行賦值操作!

再回來(lái)看看ClassContextDescriptorBuilderlayout函數(shù):
addVTable(); 添加虛函數(shù)表

addVTable();

addOverrideTable(); 添加重載虛函數(shù)表

addOverrideTable();

此時(shí)此刻在源碼中剖析的TargetClassDescriptor數(shù)據(jù)結(jié)構(gòu)里有V-Table也只是猜測(cè)婉陷,接下來(lái)我從Match-O文件進(jìn)行驗(yàn)證。

Mach-O介紹

Mach-O 其實(shí)是Mach Object文件格式的縮寫官研,是 mac 以及 iOS 上可執(zhí)行文件的格式秽澳。常見的 .o,.a .dylib Framework戏羽,dyld .dsym担神。

Mach-O文件格式
  • 文件頭Header,表明該文件是 Mach-O 格式始花,指定目標(biāo)架構(gòu)妄讯,還有一些其他的文件屬性信息,文件頭信息影響后續(xù)的文件結(jié)構(gòu)安排酷宵。
  • Load commands是一張包含很多內(nèi)容的表亥贸。內(nèi)容包括區(qū)域的位置、符號(hào)表浇垦、動(dòng)態(tài)符號(hào)表 等炕置。
Load commands
  • Data 區(qū)主要就是負(fù)責(zé)代碼和數(shù)據(jù)記錄的。Mach-O 是以 Segment 這種結(jié)構(gòu)來(lái)組織數(shù)據(jù)的,一個(gè) Segment 可以包含 0 個(gè)或多個(gè)Section朴摊。根據(jù) Segment 是映射的哪一個(gè) Load Command默垄,Segmentsection 就可以被解讀為是是代碼,常量或者一些其他的數(shù)據(jù)類型甚纲。在裝載在內(nèi)存中時(shí)口锭,也是根據(jù) Segment 做內(nèi)存映射的。

拿到Mach-O文件的步驟:
1.編譯工程介杆,找到Products目錄里

image.png

2.找到應(yīng)用程序鹃操,右鍵顯示包內(nèi)容,找到可執(zhí)行文件exec

image.png

3.打開軟件 MachOView 將可執(zhí)行文件拖拽進(jìn)去后

MachOView
從Match-O驗(yàn)證TargetClassDescriptor結(jié)構(gòu)里有V-Table

案例代碼 ViewController.swift:

class Teacher{
    func teach() {
        print("teach")
    }
    func teach1(){
        print("teach1")
    }
    func teach2(){
        print("teach2")
    }
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        let t = Teacher()
        t.teach()
        t.teach1()
        t.teach2()
        //metadata + offset
    }
}

編譯后把可執(zhí)行文件拖拽到MachOView

image.png

在Mach-O的data區(qū)里的__TEXT,__swift5_types就是存放 所有的struct/enum/類的Descriptor的地址信息这溅;以每4個(gè)字節(jié)來(lái)做區(qū)分组民。第一個(gè)4字節(jié)就是Teacher的Descriptor

Teacher的Descriptor在mach-o上的偏移量

掏出計(jì)算器,所以Teacher的Descriptor在mach-o上的地址:

0xFFFFFBF0 + 0xBC58 = 0x10000B848 // Descriptor

而0x100000000是Mach-O文件叫虛擬內(nèi)存的基地址悲靴,在Mach-O中也能找到:

虛擬內(nèi)存的基地址

所以Descriptor在mach-o的data區(qū)的偏移量:

0x10000B848 - 0x100000000 = 0xB848

然后再data區(qū)找到 __TEXT,__const里邊臭胜,去找0xB848的位置:

image.png

在0xB848后面開始算起就是Teacher的Descriptor的內(nèi)容(到哪里結(jié)束先不關(guān)心),再回來(lái)看Descriptor的數(shù)據(jù)結(jié)構(gòu):

struct TargetClassDescriptor{ 
      var flags: UInt32 
      var parent: UInt32 
      var name: Int32   // class/struct/enum 的名稱
      var accessFunctionPointer: Int32 
      var fieldDescriptor: Int32 
      var superClassType: Int32 
      var metadataNegativeSizeInWords: UInt32 
      var metadataPositiveSizeInWords: UInt32 
      var numImmediateMembers: UInt32 
      var numFields: UInt32 
      var fieldOffsetVectorOffset: UInt32 
      var Offset: UInt32 
      // var size: UInt32 
      // V-Table  (methods) 
}

可以看出它的成員已有12個(gè)癞尚,所以我們?cè)傧蚝笃?2個(gè)4字節(jié)耸三,再往后的4個(gè)字節(jié)里的內(nèi)容就是size

image.png

所以size后面的8個(gè)就是teach()方法的內(nèi)容,再往后8個(gè)就是teach1()方法的內(nèi)容浇揩,再往后8個(gè)就是teach2()的內(nèi)容:

image.png

來(lái)驗(yàn)證一下紅色畫線的地方就是teach()方法的內(nèi)容仪壮,而我們紅色畫線的開始位置是0xB87C,就是我們程序運(yùn)行時(shí)在內(nèi)存時(shí)的偏移量胳徽,它需要加上程序運(yùn)行的基地址积锅。

獲取程序運(yùn)行的基地址:

程序運(yùn)行的基地址

0x000000010297c000 是我們當(dāng)前的程序運(yùn)行時(shí)候的基地址。
而上線紅色畫線的teach方法的內(nèi)容在內(nèi)存中的地址:

0x000000010297c000 + 0xB87C = 0x10298787C

0x10298787C就是teach()方法的內(nèi)容TargetMethodDescriptor养盗,那么方法里的內(nèi)容有什么呢缚陷?
來(lái)看看swift源碼里方法在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)

方法在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)

flags是標(biāo)識(shí)這個(gè)方法是一個(gè)什么方法,是初始化方法還是getter還是什么方法往核,它占用4個(gè)字節(jié)箫爷。
另外一個(gè)是imp的指針,如果我們要找到imp指針那還需要偏移聂儒;
首先把 0x10298787C 偏移flags那4個(gè)字節(jié)

0x10298787C + 0x4 = 0x102987880

注意imp指針其實(shí)是一個(gè)相對(duì)指針虎锚,它存儲(chǔ)的其實(shí)是offset,所以我們還需要用 0x102987880加上offset衩婚,得到的就是teach的實(shí)際的方法實(shí)現(xiàn)4芑ぁ!
那這個(gè)offset是什么呢:

image.png
0x102987880 + 0xFFFFB9D8 = 0x202983258

注意0x202983258 還需減去 Mach-O的基地址:

0x202983258 - 0x100000000 = 0x102983258

0x102983258就是teach()方法的imp在內(nèi)存中的地址非春。
繼續(xù)回到我們的工程柄慰,輸出teach()的內(nèi)存地址鳍悠,如果他倆匹配,所以上面的猜測(cè)驗(yàn)證成功坐搔!

從剛才的工程運(yùn)行的斷點(diǎn)藏研,我們進(jìn)入?yún)R編調(diào)試Alaways Show Disassembly進(jìn)入到匯編,找到__allocating_init()后面的第一個(gè)blr概行,打印那個(gè)寄存器的地址 register read x8 一起見證時(shí)刻

image.png

所以驗(yàn)證了我們的猜想:
**TargetClassDescriptor數(shù)據(jù)結(jié)構(gòu)里有 sizeV-Table **

來(lái)swift源碼里看看V-Table是怎么創(chuàng)建的:

創(chuàng)建V-Table

通過(guò)Metadata來(lái)獲取當(dāng)前的描述Descriptor蠢挡,把Descriptormethod加載到了對(duì)應(yīng)的位置。這個(gè)vtableOffset是程序編譯的時(shí)候就決定了凳忙。

swift里類的默認(rèn)調(diào)度方式是函數(shù)表調(diào)度
函數(shù)表調(diào)度的實(shí)質(zhì):就是Metadata + offset

但是swift里類在extension里的派發(fā)方式是直接派發(fā)

個(gè)人理解(官方?jīng)]有聲明):因?yàn)楫?dāng)前的類已經(jīng)生成的VTable业踏,此時(shí)如果要從extension里的方法添加到VTable的話,需要通過(guò)大量的計(jì)算offset涧卵,這樣會(huì)大量浪費(fèi)cpu資料勤家,沒(méi)有這個(gè)必要,所以蘋果自動(dòng)給優(yōu)化成了extension的方法調(diào)度是直接派發(fā)柳恐。


結(jié)構(gòu)體的方法調(diào)度

struct Teacher{
    func teach() {
        print("teach")
    }
    func teach1(){
        print("teach1")
    }
    func teach2(){
        print("teach2")
    }
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        let t = Teacher()
        t.teach()
        t.teach1()
        t.teach2()
    }
}

在 t.teach()處斷點(diǎn)進(jìn)入?yún)R編調(diào)試伐脖,運(yùn)行程序,看看和class的有什么不一樣的

struct匯編調(diào)試

struct在方法調(diào)用的時(shí)候直接拿到方法地址直接調(diào)用了乐设。

結(jié)構(gòu)體的方法調(diào)用方式是直接派發(fā)

默認(rèn)方法調(diào)度方式總結(jié):

Swift默認(rèn)派發(fā)
影響函數(shù)派發(fā)的方式
  • final:允許類里面的函數(shù)使用直接派發(fā)讼庇,這個(gè)修飾符會(huì)讓函數(shù)失去動(dòng)態(tài)性。任何函數(shù)都可以使用這個(gè)修飾符近尚,就算是 extension 里本來(lái)就是直接派發(fā)的函數(shù)蠕啄。這也會(huì)讓 Objective-C 的運(yùn)行時(shí)獲取不到這個(gè)函數(shù),不會(huì)生成相應(yīng)的 selector戈锻。

  • dynamic:可以讓類里面的函數(shù)使用消息機(jī)制派發(fā)歼跟。使用 dynamic必須導(dǎo)入 Foundation 框架,里面包括了 NSObject 和 Objective-C 的運(yùn)行時(shí)格遭。dynamic 可以讓聲明在 extension 里面的函數(shù)能夠被 override嘹承。dynamic 可以用在所有 NSObject 的子類和 Swift 的原聲類。

  • @objc 或 @nonobjc:都可以顯式地聲明了一個(gè)函數(shù)是否能被 Objective-C 的運(yùn)行時(shí)捕獲到如庭。但使用 @objc 的典型例子就是給 selector 一個(gè)命名空間 @objc(abc_methodName),讓這個(gè)函數(shù)可以被 Objective-C 的運(yùn)行時(shí)調(diào)用撼港。@nonobjc會(huì)改變派發(fā)的方式坪它,可以用來(lái)禁止消息機(jī)制派發(fā)這個(gè)函數(shù),不讓這個(gè)函數(shù)注冊(cè)到 Objective-C 的運(yùn)行時(shí)里帝牡。我不確定這跟 final 有什么區(qū)別往毡,因?yàn)閺氖褂脠?chǎng)景來(lái)說(shuō)也幾乎一樣。我個(gè)人來(lái)說(shuō)更喜歡 final靶溜,因?yàn)橐鈭D更加明顯开瞭。

  • final 與 @objc同時(shí)使用:可以在標(biāo)記為 final 的同時(shí)懒震,也使用 @objc 來(lái)讓函數(shù)可以使用消息機(jī)制派發(fā)。這么做的結(jié)果就是嗤详,調(diào)用函數(shù)的時(shí)候會(huì)使用直接派發(fā)个扰,但也會(huì)在 Objective-C 的運(yùn)行時(shí)里注冊(cè)響應(yīng)的 selector。函數(shù)可以響應(yīng) perform(selector:) 以及別的 Objective-C 特性葱色,但在直接調(diào)用時(shí)又可以有直接派發(fā)的性能递宅。

  • @inline:Swift 也支持 @inline,告訴編譯器可以使用直接派發(fā)苍狰。有趣的是办龄,dynamic @inline(__always) func dynamicOrDirect() {} 也可以通過(guò)編譯!但這也只是告訴了編譯器而已淋昭,實(shí)際上這個(gè)函數(shù)還是會(huì)使用消息機(jī)制派發(fā)俐填。這樣的寫法看起來(lái)像是一個(gè)未定義的行為,應(yīng)該避免這么做翔忽。

將確保有時(shí)內(nèi)聯(lián)函數(shù)英融。這是默認(rèn)行為,我們無(wú)需執(zhí)行任何操作. Swift 編譯器可能會(huì)自動(dòng)內(nèi) 聯(lián)函數(shù)作為優(yōu)化呀打。
always - 將確保始終內(nèi)聯(lián)函數(shù)矢赁。通過(guò)在函數(shù)前添加@inline(__always) 來(lái)實(shí)現(xiàn)此行為
never - 將確保永遠(yuǎn)不會(huì)內(nèi)聯(lián)函數(shù)。這可以通過(guò)在函數(shù)前添加@inline(never) 來(lái)實(shí)現(xiàn)贬丛。
如果函數(shù)很長(zhǎng)并且想避免增加代碼段大小撩银,請(qǐng)使用@inline(never)

如果對(duì)象只在聲明的文件中可見,可以用 private 或 fileprivate 進(jìn)行修飾豺憔。編譯器會(huì)對(duì) privatefileprivate 對(duì)象進(jìn)行檢查额获,確保沒(méi)有其他繼承關(guān)系的情形下,自動(dòng)打上 final 標(biāo)記恭应,進(jìn)而使得 對(duì)象獲得靜態(tài)派發(fā)的特性
(fileprivate: 只允許在定義的源文件中訪問(wèn)抄邀,private : 定義的聲明 中訪問(wèn))

關(guān)鍵字影響函數(shù)派發(fā)方式

可見的都會(huì)被優(yōu)化 (Visibility Will Optimize)

Swift 會(huì)盡最大能力去優(yōu)化函數(shù)派發(fā)的方式. 例如, 如果你有一個(gè)函數(shù)從來(lái)沒(méi)有 override, Swift 就會(huì)檢車并且在可能的情況下使用直接派發(fā). 這個(gè)優(yōu)化大多數(shù)情況下都表現(xiàn)得很好, 但對(duì)于使用了 target / action 模式的 Cocoa 開發(fā)者就不那么友好了.

另一個(gè)需要注意的是, 如果你沒(méi)有使用 dynamic 修飾的話,這個(gè)優(yōu)化會(huì)默認(rèn)讓 KVO 失效昼榛。如果一個(gè)屬性綁定了 KVO 的話境肾,而這個(gè)屬性的 getter 和 setter 會(huì)被優(yōu)化為直接派發(fā),代碼依舊可以通過(guò)編譯胆屿,不過(guò)動(dòng)態(tài)生成的 KVO 函數(shù)就不會(huì)被觸發(fā)奥喻。

class Teacher {
    dynamic func teach() {
        print("teach")
    }
}
extension Teacher {
    @_dynamicReplacement(for: teach)
   func teach1() {
        print("teach1")
    }
}

let t = Teacher()
t.teach1()    // 實(shí)際調(diào)用teach,而調(diào)用teach()還是打印teach

派發(fā)總結(jié) (Dispatch Summary):

image.png

對(duì)于函數(shù)派發(fā)這里僅僅只做一些粗淺的總結(jié)非迹。
如果您想要了解詳細(xì)的函數(shù)派發(fā)环鲤,可以看看我之前分享的一片文章:Swift的函數(shù)派發(fā)
里面有更詳細(xì)的demo演示舉例

喜歡的老鐵?一個(gè),感謝支持憎兽!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冷离,一起剝皮案震驚了整個(gè)濱河市吵冒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌西剥,老刑警劉巖痹栖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蔫耽,居然都是意外死亡结耀,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門匙铡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)图甜,“玉大人,你說(shuō)我怎么就攤上這事鳖眼『谝悖” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵钦讳,是天一觀的道長(zhǎng)矿瘦。 經(jīng)常有香客問(wèn)我,道長(zhǎng)愿卒,這世上最難降的妖魔是什么缚去? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮琼开,結(jié)果婚禮上易结,老公的妹妹穿的比我還像新娘。我一直安慰自己柜候,他們只是感情好搞动,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著渣刷,像睡著了一般鹦肿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辅柴,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天箩溃,我揣著相機(jī)與錄音,去河邊找鬼碌嘀。 笑死涣旨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的筏餐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼牡拇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼魁瞪!你這毒婦竟也來(lái)了穆律?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤导俘,失蹤者是張志新(化名)和其女友劉穎峦耘,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旅薄,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辅髓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了少梁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洛口。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凯沪,靈堂內(nèi)的尸體忽然破棺而出第焰,到底是詐尸還是另有隱情,我是刑警寧澤妨马,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布挺举,位于F島的核電站,受9級(jí)特大地震影響烘跺,放射性物質(zhì)發(fā)生泄漏湘纵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一滤淳、第九天 我趴在偏房一處隱蔽的房頂上張望梧喷。 院中可真熱鬧,春花似錦娇钱、人聲如沸伤柄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)适刀。三九已至,卻和暖如春煤蹭,著一層夾襖步出監(jiān)牢的瞬間笔喉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工硝皂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留常挚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓稽物,卻偏偏與公主長(zhǎng)得像奄毡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贝或,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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

  • 一.初始類與結(jié)構(gòu)體 了解類與結(jié)構(gòu)體的異同點(diǎn) 結(jié)構(gòu)體和類的主要共同點(diǎn)有: 定義存儲(chǔ)值的屬性 定義方法 定義下標(biāo)以使用...
    劉國(guó)強(qiáng)閱讀 450評(píng)論 0 1
  • 我的博客[https://dengfeng520.github.io/] 1吼过、值類型和引用類型 在iOS中虛擬內(nèi)存...
    小時(shí)間光閱讀 749評(píng)論 0 2
  • 在面向過(guò)程的語(yǔ)言中,要想實(shí)現(xiàn)類似類的功能只能借助結(jié)構(gòu)體,其實(shí)從OC源碼也能看出來(lái),類的組成本就是復(fù)雜的結(jié)構(gòu)體實(shí)現(xiàn)的...
    如風(fēng)如花不如你閱讀 8,203評(píng)論 2 6
  • 1锐秦、類和結(jié)構(gòu)體 1.1 基礎(chǔ)認(rèn)知 類和結(jié)構(gòu)體十分相似,如: 相同點(diǎn): 定義存儲(chǔ)值的屬性盗忱、方法酱床、初始化器、以及下標(biāo)以...
    Jacky_夜火閱讀 450評(píng)論 0 1
  • 類和結(jié)構(gòu)體對(duì)比 Swift 中類和結(jié)構(gòu)體有很多共同點(diǎn)趟佃。共同處在于: 定義屬性用于存儲(chǔ)值 定義方法用于提供功能 定義...
    小驢拉磨閱讀 131評(píng)論 0 0