Series of articles:OC Basic principle series.OC Basic knowledge series.Swift bottom exploration series

preface

Continuing with Swift, this article explores the following:

  • Use SIL to understand the object creation process
  • Swift class structure analysis
  • Store properties & compute properties
  • Delayed storage properties & singleton creation

The Swift object creation process

Swift compilation

Before we introduce SIL, let’s take a lookSwift compilation, let’s look at the following code:

We create an LjTeacher class and create an instance object to teacher using the default initialization method

What we need to look at is what does this default initialization method actually do at the bottom? So we introduced SIL (Swift Intermediate Language)

SIL

The language of iOS developmentOCorSwiftIn theThe underlyingIs through thedifferenttheThe compilerCompile, and then passLLVMTo generate the.o Executable file, as shown in the figure below

  • OCthroughClang compiler, compiled intoIRAnd thenGenerate an executable file. O(Ours, that isMachine code)
  • SwiftbySwift, the compilerCompiled intoIRAnd then regenerate intoExecutable file.O

The following is a Swift file compilation process

SIL (Swift Intermediate Language) is the Intermediate code in Swift compilation, which is mainly used to further analyze and optimize Swift code. SIL is located between Sema and LLVM IR

Note: It is important to note thatSwiftwithOCtheThe difference betweenIs thatSwift generates high-level SILWe can get throughswiftc -hTerminal command to view all swifTC commandsFor example, define the following code in the main.swift file

  • Using abstract syntax trees:swiftc -dump-ast main.swift

  • Generate SIL files:swiftc -emit-sil main.swift >> ./main.sil && code main.sil, the entry function of main is as follows
// main // '@main' : identifies the 'entry function' of the current main.swift. The identifier name in SIL is prefixed with '@' sil@main: $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>) -> Int32 {// '%0, %1' Can be thought of as a constant in development, once assigned, it cannot be modified, and if you want to continue using it, you need to continuously add up the number (note: $Int32, $Int32, $Int32, $Int32, $Int32, $Int32, $Int32, $Int32, $Int32, $Int32, $Int32 $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): // 'alloc_global' : create a 'global variable', i.e. 't' alloc_global @$s4main1tAA10LjTeacherCvp ' %3 = global_addr @$s4main1tAA10LjTeacherCvp: $*LjTeacher // user: %4 = metatype $@thick ljteacher.type // user: %6 // assign the function address of '__allocating_init' to %5 // function_ref ljteacher.__allocating_init () %5 = function_ref @$s4main10LjTeacherCACycfC : $@convention(method) (@thick LjTeacher.Type) -> @owned LjTeacher // user: %6 // 'apply' calls' __allocating_init 'to initialize a variable and assign the value to %6 %6 = apply %5(%4) : $@convention(method) (@thick LjTeacher.Type) -> @owned LjTeacher // user: Store %6 to %3: $*LjTeacher // id: $*LjTeacher // struct $Int32 (%8: Int), struct $Int32 (%8: Int) $Builtin.Int32) // user: %10 return %9 : $Int32 // id: %10 } // end sil function 'main'Copy the code

[Note] : the code command is configured in.zshrc, you can specify the software to open the corresponding file in the terminal

$open. ZSHRC / / * * * * * * add the following aliases alias subl = '/ Applications/SublimeText app/Contents/SharedSupport/bin/subl' alias Code = '/ Applications/Visual \ Studio \ code app/Contents/Resources/app/bin/code' / / * * * * * * use $code. The main sil / / if you want to highlight sil file, The plug-in needs to be installed: VSCode SILCopy the code
  • From the SIL file, you can see that the code has been obfuscated and can be restored with the following command tos4main1tAA10LjTeacherCvpFor example:xcrun swift-demangle s4main1tAA10LjTeacherCvp
  • Search in SIL filess4main10LjTeacherCACycfC, its internal implementation is mainlyAllocate memory + initialize variables
    • allocing_ref:createA LjTeacherInstance objects, of the current instance objectThe reference count is 1
    • Call the init method
/ / * * * * * * * * * the main entrance to the function code in * * * * * * * * * % 5 = function_ref @ $s4main10LjTeacherCACycfC: $@convention(method) (@thick ljteacher.type) -> @owned LjTeacher // s4main10LjTeacherCACycfC is __allocating_init() LjTeacher.__allocating_init() sil hidden [exact_self_class] @$s4main10LjTeacherCACycfC : $@convention(method) (@thick LjTeacher.Type) -> @owned LjTeacher { // %0 "$metatype" bb0(%0 : $@thick LjTeacher.Type): %1 = alloc_ref $LjTeacher // user: // function_ref ljteacher.init () initializes the current variable %2 = function_ref @$s4main10LjTeacherCACycfc: $@convention(method) (@owned LjTeacher) -> @owned LjTeacher // user: %3 = apply %2(%1) : $@convention(method) (@owned LjTeacher) -> @owned LjTeacher // user: %4 return %3 : $LjTeacher // id: %4 } // end sil function '$s4main10LjTeacherCACycfC'Copy the code

The SIL language is very important for analyzing Swift source code, and more information about its syntax can be found here

Symbol breakpoint debugging

  • Set this parameter in Demo_allocing_initSymbol breakpoint

What is found internally isswift_allocObject

The source code to debug

Let’s explore object creation in Swift with swift_allocObject

  • inREPLWrite (or copy) the following code in the command interaction line, and searchswift_allocObjectThe function adds a breakpoint, and then defines an instance object t

  • The breakpoint is broken and view local on the left for detailed information

  • Among themrequiredSizeIs the actual memory size allocated, is40
  • requiredAlignmentMaskisswiftIn theByte alignmentThe andSame thing in OCThe,It has to be a multiple of 8.insufficientThe willauto-completeWith the aim ofSpace for timeCome,Improve memory operation efficiency

Swift_allocObject source analysis

Swift_allocObject source code is as follows, mainly divided into the following parts:

  • throughswift_slowAllocAllocates memory and aligns memory bytes
  • throughnew + HeapObject + metadataInitialize an instance object
  • The return value of the function isHeapObjectType, so of the current objectMemory structureisHeapObjectMemory structure of

  • Enter theswift_slowAllocThe interior of the function is mainly throughmallocinThe heapIn theAllocate memory sizeAnd,Return memory addressMainly used inStoring instance variables

  • To enter the HeapObject initialization method, metadata and refCounts are required

  • Among themmetadataType isHeapMetadata, is a pointer type, accounting for8 bytes
  • refCounts(Reference count, type isInlineRefCountsAnd theInlineRefCountsIs a classRefCountsAlias, account for8Bytes), used by SwiftArc reference count

conclusion

  • For instance objectstIn terms of, its essence is aHeapObjectStructure, default16 bytesMemory size (metadata8 bytes +refCounts8 bytes), compared with OC as follows
    • OCIn theInstance objectsIs the nature ofThe structure of the body, is theobjc_objectforTemplate inheritanceOne of themIsa pointer, accounting for8 bytes
    • SwiftIn theInstance objects, the defaultThan OCIn theThere was an extra refCounted reference count size, the defaultThe attribute takes 16 bytes
  • The memory allocation process for objects in Swift is as follows:__allocating_init --> swift_allocObject_ --> _swift_allocObject --> swift_slowAlloc --> malloc
  • initAnd that’s where the responsibilities areInitialize a variableAnd thisOC is consistentthe

Two questions remain regarding the above analysis: what is metadata and how is 40 calculated? So let’s continue exploring

In the demo, we can passRuntimeMethod to get the memory size of the classThis is to the left during source debugginglocaltheRequiredSize values are equal, from theHeapObjectWe know from the analysis of,A class with no attributes takes up 16 bytes by default

For Int and String, if we go into the underlying definition, both are struct types, are they both 8 bytes? This can be verified by printing its memory size

********* @frozen public struct Int: FixedWidthInteger, SignedInteger {... } / / * * * * * * * * * the String to the underlying definition * * * * * * * * * @ frozen public struct String {... } / / * * * * * * * * * verify the * * * * * * * * * print (MemoryLayout < Int >. Stride) print (MemoryLayout < String >. Stride) / / * * * * * * * * * printing result * * * * * * * * * 8 to 16Copy the code

As you can see from the printed result, Int takes up 8 bytes and String takes up 16 bytes (more on that later), which is different from OC

So this also explains why LjTeacher memory size = 40, i.e. 40 = metadata (8 bytes) +refCount (8 bytes) + Int (8 bytes) + String (16 bytes)

The source of 40 is verified, but metadata is not known, continue to analyze

Explore the structure of classes in Swift

We know that in OC classes are inherited from objc_class templates, see the OC basic principles – class structure

In Swift, however, the underlying structure of the class is HeapObject, which has metadata + refCounts

HeapMetadata type analysis

Let’s take a look at metadata and see what it is!

  • Enter theHeapMetadataDefinition,TargetHeapMetaDataThe alias of the type that accepts one parameterInprocess

  • Enter theTargetHeapMetaDataDefinition, in essence, is aTemplate type, which defines someThe requiredtheThe data structure. thisThere are no attributes in the structure.There are only initialization methods, passed in aParameter of type MetadataKind(The structure does not, then only in the parent class) herekindisThe incomingtheInprocess

  • Enter theTargetMetaDataDefinition, there’s oneKind properties.kindThe type ofInprocess that was passed in earlier. And from that,For the kind, itstypeitIs unsigned long, the mainUsed to distinguish which type of metadata

The parameter kind in the initialization method is MetadataKind. The type of the parameter is MetadataKind

  • Enter theMetadataKindDefinition, there’s one in there#include "MetadataKind.def", click enter, which recordsAll types of metadata.All Kind typesSummarized below
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
  • Go back toTargetMetaDataStruct definition, find methodgetClassObjectIn the methodMatch the kindThe return value isTargetClassMetadatatype
    • If it isClassIs directly correctthis(Current pointer, that is, metadata) strong conversionClassMetadata

This can be verified by LLDB

  • po metadata->getKind(), get that its kind is Class
  • po metadata->getClassObject(),x/8g 0x0000000110efdc70At this addressstorageisMetadata information

So,TargetMetadataandTargetClassMetadataEssentially,The sameBecause inIn memory structure, you canConvert the pointer directly, so to speak, we thinkThe structure of the bodyIn fact, it isTargetClassMetadata

  • Enter theTargetClassMetadataDefinition, inherited fromTargetAnyClassMetadata, has the following properties, which are part of the class structure

  • Enter theTargetAnyClassMetadataDefinition, inherited fromTargetHeapMetadata

conclusion

To sum up, whenWhen the kind of metadata is ClassOpinion for the followingInheritance chain:

  • The actual type returned by the current class isTargetClassMetadataAnd theTargetMetaDataThere is only one attribute inkind.TargetAnyClassMetaDataThere are four attributes in, respectivelyKind, uperclass, cacheData, data (not shown in figure)
  • The currentClass An attribute held in memorybyTargetClassMetadataAttribute +TargetAnyClassMetaDataAttribute +TargetMetaDataProperty composition, soMetadata The data structureAs shown below.
struct swift_class_t: NSObject{ void *kind; // Equivalent to isa in OC, the actual type of kind is unsigned long void *superClass; void *cacheData; void *data; uint32_t flags; //4 bytes uint32_t instanceAddressOffset; //4 bytes uint32_t instanceSize; //4 bytes uint16_t instanceAlignMask; //2 bytes uint16_t reserved; //2 bytes uint32_t classSize; // uint32_t classAddressOffset; //4 bytes void *description; . }Copy the code

Compared with OC

  • Instance object & class
    • Instance object in OCNature isThe structure of the bodyIs throughThe underlying objc_Object templateCreate, class isInherited from objc_class
    • Instance object in SwiftNature is alsoThe structure of the body.Type is HeapObjectThan OC,morearefCounts
  • Methods list
    • Method storage in OCinMethodList of the objc_class structure class_rw_tIn the
    • Methods in SWIFT are stored in the metadata metadata
  • Reference counting
    • ARC in OC maintains isHash table
    • An ARC in Swift is an object that has one inside itrefCountsattribute

Swift properties

In SWIFT, attributes fall into the following categories

  • 1.Storage properties
  • 2.Calculate attribute
  • 3.Deferred storage property
  • 4.The type attribute

Storage properties

There are two types of storage properties

  • 1. EitherConstant storage property, i.e.,Let decorate
  • 2. EitherVariable storage properties, i.e.,Var decoration

Define the following code:In the codeThe age, name,Both of themVariable storage propertiesThis point can be found inSILIn the embodiment

class LjTeacher {
    @_hasStorage @_hasInitialValue var age: Int { get set }
    @_hasStorage @_hasInitialValue var name: String { get set }
    init(age: Int, name: String)
    @objc deinit
}
Copy the code

[Storage properties] : it will occupy the memory space of the instance object

  • po t
  • x/8gxMemory address, i.eHeapObject Storage address

You can draw a class property diagram:

Calculate attribute

[calculation attribute] : does not occupy memory space, essentially is the set/get method attribute we through a demo to illustrate, the following writing method is possible?

class LjTeacher {
    var age: Int{
        get{
            return 18
        }
        set{
            age = newValue
        }
    }
}
Copy the code

In real programming, the compiler would issue the following warning, which means that age.set is called again in the set method of ageFound after runcollapseThe reason is thatA call to age in the set method of age results in a circular referenceRecursion occurs

Verification: No memory usage

For the feature that it does not occupy memory space, we still use the LjTeacher case to verify, and print the memory size of the following class

(metadata + refCounts = 16 bytes +height (8 bytes) = 24) From here we can prove that the weight attribute does not occupy memory space.

Validation: Essentially a set/get method

  • willmain.swiftconvertSILFile:swiftc -emit-sil main.swift >> ./main.sil
  • To viewSILFile, forStorage properties, there are_hasStorage identifier
class Square {
  @_hasStorage @_hasInitialValue var height: Double { get set }
  var weight: Double { get set }
  init(height: Double, weight: Double)
  @objc deinit
}
Copy the code

For computed properties,SILOnly in theSetter and gettermethods

Attribute observer (didSet, willSet)

  • WillSet:NewValue is called before the newValue is stored
  • DidSet:OldValue is called after the new value is stored

validation

  • This operation can be verified by Demo

  • It can also be verified by compilationMain. swift compiles to mail.silIn the SIL fileFind the set method of name

Issues related to

Does the property observer fire in the init method?

In the following code, does setting name in the init method trigger the property observer?Run results found andI didn't go willSet, didSetIn the printing method, so there are the following conclusions:

  • ininitMethod, if the property is called, isAttribute observers are not triggeredthe
  • In the initmainlyInitialize the current variable, in addition toThe defaultThe former16 bytesOther attributes willCall memset to clear memory space(because it could be dirty data, that is, someone else has used it)The assignment

Summary: Neither the initializer (that is, the init method setting) nor the definition setting default (that is, calling other property values in didSet) will fire

Where can I add a property observer?

There are three main places to add:

  • 1.Class to define a storage property
  • 2.Store attributes inherited by a class

  • 3.Attributes are evaluated by class inheritance

What is the order in which didSet and WillSet are called when the subclass and superclass evaluate properties exist together?

We verify this with the following code conclusionFor:Same property.A subclassandEvery parent class has an attribute observer, the order is:First subclass willset.Father willset class.Superclass didset again.Post-subclass didSet, that is:Son and father, father and son

If a subclass calls init of its parent class, does that trigger the watch property?

Based on the above problem, modify the LjMediumTeacher class as the following figureWe can see it by printingTrigger observerMainly becauseThe subclass calls init of its parent class.Already initialized, andInitialize theprocessensuretheAll attributes have valuesSuper.init ensures that the variable is initializedYou can observe propertiesthe

Lazy properties

The delay attribute is mainly described as follows:

  • 1.Storage properties with lazy modifications
  • 2.The delay attribute must have a default initial value
  • 3.Deferred storage is not assigned until the first access
  • 4.Deferred storage properties are not thread-safe
  • 5.Effect of deferred storage properties on the size of instance objects

Let’s break it down one by one

Storage properties with lazy modifications

The delay attribute must have a default initial value

If defined as an optional type, an error is reported, as shown below

Deferred storage is not assigned until the first access

You can debug to see memory changes in instance variables

  • ageBefore the first visitMemory condition: at this timeAge has no value and is 0x0

  • ageAfter the first visitMemory condition: at this timeAge has a value of 24

You can verify from above that the lazy load storage property is assigned only on the first access

We can also passsilFile to view, which can be added when generating the SIL fileRestore obfuscated names in swiftThe command (i.excrun swift-demangle) :swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && code main.sil, the demo code is as follows

  • Kind of + main:Lazy to modifyThe storage property of the underlying is oneoptionaltype

  • setter+getterYou can verify from the getter that, on the first access, nothing becomes a valued operation

Through SIL, the following two points are stated:

  • 1.lazyThe underlying default isoptionalIn theWhen not accessedThe default isnilThe memory representation of alpha is alpha0x0. During the first visit,callIs attributivegetterMethod, its internal implementation is throughBranch of the current enumLet’s do oneThe assignment operation
  • 2. Is the optional type 16 bytes? Can be achieved byMemoryLayoutprint
    • The size:The actual size
    • Stride:Distribution of size(Mostly due to memory alignment)

Why is the actual size 9? Optional is essentially an enum in which an Int is 8 bytes and another byte is used to store case values.

Deferred storage properties are not thread-safe

Continue parsing the sil file above, mainly looking at the getter method for age if there are two threads:

  • Thread 1At this point access age, whoseAge has no valueThe,Enter the BB2 process
  • thenThe time slice allocates the CPU to thread 2foroptionalSay, still isnoneAnd so willEnter the BB2 process
  • So at this point,Thread 1 will do the assignment once.Thread 2 will also go through the assignmentAnd,There is no guarantee that the property was initialized only once

Effect of deferred storage properties on the size of instance objects

Let’s see if there’s a difference between memory that doesn’t use lazy and memory that uses lazy.

  • Do not use the lazyModify the case of the classThe memory size is 24

  • The use of lazyModify the case of the classThe memory size is 32

So it turns out,The use of lazyandDo not use the lazy, itsThe memory size of instance objects is differentthe

The type attribute

Type attributes are described as follows:

  • 1.Use the keyword static and is a global variable
  • 2.The type attribute must have a default initial value
  • 3.Type attributes are initialized only once

Use the keyword static modifier

Generating SIL files

  • View the definition and findOne more global variable, soThe type attribute is a global variable

  • To viewThe entry functionIn theThe age of acquisition

  • To viewGetter method for age

  • Among themglobalinit_33_596AE01B81CE0F4EFFD4B7F23A0D7C04_func0Is a global variable initialization function

  • In the static code, through breakpoint debugging, we find that the call isswift_onceSaid,Property is initialized only once

  • Source searchswift_onceAnd its interior is throughGCDtheDispatch_once_f singletonThe implementation. From here you can seevalidationPoint 3 above:Type attributes are initialized only once

The type attribute must have a default initial value

As shown in the figure below, an error is reported if no default initial value is givenSo forThe type attributeOne isThe global variableAnd onlyInitialize onceAnd the other isThread safetythe

Creation of a singleton

conclusion

  • Storage propertiesWill occupy the instance variabletheMemory space
  • Calculate attributeNo memory footprint, itsnatureisSet/get methods
  • Attribute observer
    • willset: is called before the new value is stored, notifying the subclass first and then the parent class (because some additional operations may need to be done in the parent class), i.eThe son of the father
    • didSet: After the new value is stored, the parent class is told first, and then the child class is notified (the parent class takes precedence over the child class), i.eFather and son
    • In the classinitMethods the assignmentNot triggerAttribute to observe
    • Attributes can be added inClass defined storage properties, inherited storage properties, inherited compute propertiesIn the
    • A subclass calls the parent classinitMethod,The view property is triggered
  • Deferred storage property
    • Use lazy decorationStore attributes, andThere must beaThe default value
    • Only in theFor the first time,beaccessAs can beThe assignmentAnd isThread insecuritythe
    • The use of lazyandDo not use the lazyWill,Instance objectstheMemory size mattersMainly becauselazyinThe underlyingisOptional type.optionalThe nature ofEnum is, in addition toStore the property itselfthememoryThe size,It takes one byteUsed forStorage case
  • The type attribute
    • usestaticModifier, and there must be oneDefault initial value
    • Is a global variable and will only be used byInitialize once, it isThread safetythe
    • Used to createThe singletonObject:
      • usestatic + letCreating instance variables
      • initMethod has access toprivate

Wrote last

Write more content, due to my limited ability, some places may explain the problem, please can point out, at the same time for Swift class, object, attribute questions, welcome everyone to leave a message, but also hope you like a lot of support. I hope that we can communicate with each other, explore and make progress together!