Swift5出了,主要是ABI稳定了,从ABIDashboard来看为了解决ABI稳定问题,对typemetadata也有不少改动。众所周知,我们App的JSON库HandyJSON是强依赖metadata结构的,如果metadata有大规模的改动可能直接导致这个库完全不能用,本着早发现早治疗的心态我赶快下载了Xcode10.2beta,一跑果然编不过了,没办法只好自己着手来解决问题了。
Metadata的结构演进为了便于理解,先画个图看一下metadata的具体结构,每一格代码一个指针长度,这是64位系统下的metadata结构,32位系统下nominaltypedescriptor的偏移在11个指针长度的位置,官方文档里有详细的说明。Swifttypemetadata的结构其实并没有明显的变化,而其中的nominaltypedescriptor结构却经历了一系列的变化。
Swift4.2以前
在Swift4.2(不包括4.2)以前的结构是这样的:
struct_NominalTypeDescriptor{varmangledName:Int32varnumberOfFields:Int32varfieldOffsetVector:Int32varfieldNames:Int32varfieldTypesAccessor:Int32}
复制代码nominaltypedescriptor包含了属性的名字和访问属性的类型信息的函数,HandyJSON最初的原理就是从nominaltypedescriptor中取得属性的类型信息然后把JSON字串里的相应值赋进去,由于fieldTypeAccessor符合c的callingconvention,把指针强转一下就能获得类型信息:
varfieldTypes:[Any.Type]?{guardletnominalTypeDescriptor=self.nominalTypeDescriptorelse{returnnil}guardletfunction=nominalTypeDescriptor.fieldTypesAccessorelse{returnnil}return(0..nominalTypeDescriptor.numberOfFields).map{returnunsafeBitCast(function(UnsafePointerInt(pointer)).advanced(by:0).pointee,to:Any.Type.self)}}
Swift4.2
Swift4.2对nominaltypedescriptor做了调整,struct和class结构变得有所不同,乍看没有少什么东西,其实对fieldTypesAccessor这个函数做了修改,不再符合c的callingconvention,因此不可以再从nominaltypedescriptor获取类型信息。
struct_StructContextDescriptor:_ContextDescriptorProtocol{varflags:Int32varparent:Int32varmangledName:Int32varfieldTypesAccessor:Int32varnumberOfFields:Int32varfieldOffsetVector:Int32}struct_ClassContextDescriptor:_ContextDescriptorProtocol{varflags:Int32varparent:Int32varmangledName:Int32varfieldTypesAccessor:Int32varsuperClsRef:Int32varreservedWord1:Int32varreservedWord2:Int32varnumImmediateMembers:Int32varnumberOfFields:Int32varfieldOffsetVector:Int32}
尽管苹果希望我们用Mirror来做反射,但是其实Mirror至今为止都不包含属性的类型的信息,因此苹果留了一个临时接口swift_getFieldAt来帮助我们获取类型信息:
_silgen_name("swift_getFieldAt")func_getFieldAt(_type:Any.Type,_index:Int,_callback:
convention(c)(UnsafePointerCChar,UnsafeRawPointer,UnsafeMutableRawPointer)-Void,_ctx:UnsafeMutableRawPointer)为什么说是临时的呢,因为Swift5的时候就发现这个接口没了。。。。
Swift5.0
到了Swift5.0的时候,前面已经说过了获取类型的那个接口没了,那么我们只好翻出Swift的源码来找找思路了,找到TypeContextDescriptorBuilderBase类的layout()方法:
voidlayout(){asImpl().