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
}
}
這里我們借助兩個(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é)格式輸出)
看到p1
與p2
變量都引用了同一個(gè)Person
的實(shí)例地址猪钮。
所以引用類型存儲(chǔ)的是實(shí)例內(nèi)存地址的引用党饮。
而p1
和p2
兩個(gè)變量本身的地址是不一樣的,而他倆內(nèi)存是挨著的友酱,剛好相差8個(gè)字節(jié)晴音,這兩變量的內(nèi)存地址存儲(chǔ)的就是當(dāng)前實(shí)例對(duì)象的內(nèi)存地址:
值類型
struct Person {
var name: String
init(name: String) {
self.name = name
}
}
看到輸出的內(nèi)容就和class的不一樣了。
值類型存儲(chǔ)的就是具體實(shí)例(或者說(shuō)具體的值)缔杉。
引用類型 和 值類型 的存儲(chǔ)位置
一般情況下锤躁,值類型存儲(chǔ)在棧上,引用類型存儲(chǔ)在堆上或详。
先來(lái)了解一下我們的內(nèi)存模型:
我們把系統(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"
}
struct是值類型,所以存放第一個(gè)首地址是指向age的嗡善,并且很明顯struct存儲(chǔ)在棧區(qū),因?yàn)榈刂肥沁B續(xù)的学歧,age到name剛好中間相差8個(gè)字節(jié)罩引。
如果struct的成員包含了一個(gè)引用類型呢?
struct Person {
var age: Int = 18
var name: String = "a"
var t = Teacher()
}
class Teacher {
var age = 10
var name = "tearcher"
}
age枝笨、name和t在棧區(qū)這沒(méi)有問(wèn)題袁铐,而Teacher開辟的地址0x0000600000ec1080在堆區(qū)。
類的內(nèi)存分布:
class Person {
var age = 18
var name = "a"
}
我們知道p1存儲(chǔ)在方法棧上横浑,Person實(shí)例的地址是0x00006000024a9b90剔桨,仍然要在堆區(qū)中開辟內(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)行編譯的篓冲,如下圖所示:
OC 通過(guò) clang 編譯器編譯成 IR,然后再生成可執(zhí)行文件 .o(這里也就是我們的機(jī)器碼);
Swift 則是通過(guò) Swift 編譯器編譯成 IR宠哄,然后在生成可執(zhí)行文件壹将。
// 語(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
Person是純swift類
,在創(chuàng)建實(shí)例的時(shí)候承粤,會(huì)調(diào)用SwiftTest.Person.__allocating_init()
暴区; 底層會(huì)調(diào)用swift_allocObject
和SwiftTest.Person.init()
。
Person是繼承NSObject的類
辛臊,在創(chuàng)建實(shí)例的時(shí)候仙粱,會(huì)調(diào)用SwiftTest.Person.__allocating_init()
;底層會(huì)調(diào)用objc_allocWithZone
和objc_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)的邏輯
對(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é): Metadata
和 RefCount
,默認(rèn)占用 16 字節(jié)大小辐真。
RefCount是一個(gè)64位的引用計(jì)數(shù)须尚,那么HeapMetadata
到底是什么呢?
源碼分析class的數(shù)據(jù)結(jié)構(gòu)
HeapMetadata
給 HeapMetadata
起了別名 TargetHeapMetadata
所以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ù):
這里的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
撩轰、ClassISA
、CacheData
、Data
這里面的數(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è)案例:
此時(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)體:
swift中函數(shù)的參數(shù)默認(rèn)在最后是傳遞self的想许,而objective-c是在方法列表前面默認(rèn)傳遞self
和cmd
。
找到test
和moveBy
方法断序,可以看出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ū)別舉例:
值類型的實(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ǔ)的值妥色。
偽代碼:
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的匯編):
斷點(diǎn)打在 t.teach() 來(lái)看看匯編
在__allocating_init()
和swift_release
之間的 blr x8
就是teach方法的調(diào)用设联,然后我們來(lái)看看blr x8
的調(diào)用里面是啥,推測(cè)對(duì)不對(duì):
那么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)用
可以看到三個(gè)函數(shù)的內(nèi)存是連續(xù)的,并且都相差8個(gè)字節(jié)悉稠。
分析第一個(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
:
驗(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
文件二汛,打開并拖拽文件最后:
這個(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
找到 Description
成員變量
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
這個(gè)類是用來(lái)創(chuàng)建當(dāng)前的Matedata和Descriptor用的丘侠。然后找到layout
函數(shù):
首先來(lái)看看 super::layout()
做了啥:
這里的各種add是不是與上面的TargetClassDescriptor
數(shù)據(jù)結(jié)構(gòu)有點(diǎn)類似了氯葬,這個(gè)layout
就是在創(chuàng)建Descriptor
進(jìn)行賦值操作!
再回來(lái)看看ClassContextDescriptorBuilder
的layout
函數(shù):
addVTable();
添加虛函數(shù)表
addOverrideTable();
添加重載虛函數(shù)表
此時(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
担神。
- 文件頭
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)表 等炕置。
-
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
默垄,Segment
中section
就可以被解讀為是是代碼,常量或者一些其他的數(shù)據(jù)類型甚纲。在裝載在內(nèi)存中時(shí)口锭,也是根據(jù)Segment
做內(nèi)存映射的。
拿到Mach-O
文件的步驟:
1.編譯工程介杆,找到Products
目錄里
2.找到應(yīng)用程序鹃操,右鍵顯示包內(nèi)容,找到可執(zhí)行文件exec
3.打開軟件 MachOView
將可執(zhí)行文件拖拽進(jìn)去后
從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
在Mach-O的data區(qū)里的__TEXT,__swift5_types
就是存放 所有的struct/enum/類的Descriptor
的地址信息这溅;以每4個(gè)字節(jié)來(lái)做區(qū)分组民。第一個(gè)4字節(jié)就是Teacher的Descriptor:
掏出計(jì)算器,所以Teacher的Descriptor在mach-o上的地址:
0xFFFFFBF0 + 0xBC58 = 0x10000B848 // Descriptor
而0x100000000是Mach-O文件叫虛擬內(nèi)存的基地址悲靴,在Mach-O中也能找到:
所以Descriptor在mach-o的data區(qū)的偏移量:
0x10000B848 - 0x100000000 = 0xB848
然后再data區(qū)找到 __TEXT,__const
里邊臭胜,去找0xB848的位置:
在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
:
所以size后面的8個(gè)就是teach()方法的內(nèi)容,再往后8個(gè)就是teach1()方法的內(nèi)容浇揩,再往后8個(gè)就是teach2()的內(nèi)容:
來(lái)驗(yàn)證一下紅色畫線的地方就是teach()方法的內(nèi)容仪壮,而我們紅色畫線的開始位置是0xB87C,就是我們程序運(yùn)行時(shí)在內(nèi)存時(shí)的偏移量胳徽,它需要加上程序運(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):
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是什么呢:
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í)刻
所以驗(yàn)證了我們的猜想:
**TargetClassDescriptor
數(shù)據(jù)結(jié)構(gòu)里有 size
和 V-Table
**
來(lái)swift源碼里看看V-Table是怎么創(chuàng)建的:
通過(guò)Metadata
來(lái)獲取當(dāng)前的描述Descriptor
蠢挡,把Descriptor
的method
加載到了對(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)用的時(shí)候直接拿到方法地址直接調(diào)用了乐设。
結(jié)構(gòu)體的方法調(diào)用方式是直接派發(fā)
默認(rèn)方法調(diào)度方式總結(jié):
影響函數(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ì)
private
或fileprivate
對(duì)象進(jìn)行檢查额获,確保沒(méi)有其他繼承關(guān)系的情形下,自動(dòng)打上final
標(biāo)記恭应,進(jìn)而使得 對(duì)象獲得靜態(tài)派發(fā)
的特性
(fileprivate: 只允許在定義的源文件中訪問(wèn)抄邀,private : 定義的聲明 中訪問(wèn))
可見的都會(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):
對(duì)于函數(shù)派發(fā)這里僅僅只做一些粗淺的總結(jié)非迹。
如果您想要了解詳細(xì)的函數(shù)派發(fā)环鲤,可以看看我之前分享的一片文章:Swift的函數(shù)派發(fā)
里面有更詳細(xì)的demo演示舉例
喜歡的老鐵?一個(gè),感謝支持憎兽!