内存管理
Swift
中使用自动引用计数(ARC)机制来追踪和管理内存。
class HotpotCat {
var age: Int = 18
var name: String = "Hotpot"
}
var hp = HotpotCat()
var hp1 = hp
var hp2 = hp
通过 lldb
直接查看refCounted
,这里的6
其实就是引用计数3
。
分析下源码看下refCounted
到底是什么?
源码分析
直接定位到HeapObject.cpp
文件
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
首先refCounted
是个宏定义SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
,他的实现是InlineRefCounts refCounts
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
InlineRefCounts
其实是RefCounts
类。
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
RefCounts
是一个模板类,那么真正在运行时期类型的决定取决于传进来的模板参数RefCountBits
。从源码可以看到传进来的是InlineRefCountBits
。
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
InlineRefCountBits
又是一个模板类,RefCountIsInline
模板参数定义如下:
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
参数是一个枚举,只有true
和fals
e两个值。所以直接分析下RefCountBitsT
看到有一个属性BitsType
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
BitsType bits;
struct RefCountBitsInt<refcountIsInline, 8> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
它是把结构体RefCountBitsInt
中的Type
取了个别名叫BitsType
。所以本质上BitsType
就是一个uint64
位整形。所以refCounted
本质上就是操作的这64位整形。
强引用
我们看下创建对象的时候refCounted
干了什么
HeapObject() = default;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
创建对象的时候传了两个参数:metadata
和refCounts
,看下InlineRefCounts::Initialized
其实是一个enum
RefCountBits(0, 1)
传进去了一个0和一个1。继续看下RefCountBits
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
其实真正调用的就是RefCounts
,看下初始化方法
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
- 0和1分别对应
uint32_t strongExtraCount, uint32_t unownedCount
- 分别做了位移操作
offset具体内容如下:
struct RefCountBitOffsets<8> {
/*
The bottom 32 bits (on 64 bit architectures, fewer on 32 bit) of the refcount
field are effectively a union of two different configurations:
---Normal case---
Bit 0: Does this object need to call out to the ObjC runtime for deallocation
Bits 1-31: Unowned refcount
---Immortal case---
All bits set, the object does not deallocate or have a refcount
*/
static const size_t PureSwiftDeallocShift = 0;
static const size_t PureSwiftDeallocBitCount = 1;
static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);
static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
static const size_t UnownedRefCountBitCount = 31;
static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);
static const size_t IsImmortalShift = 0; // overlaps PureSwiftDealloc and UnownedRefCount
static const size_t IsImmortalBitCount = 32;
static const uint64_t IsImmortalMask = maskForField(IsImmortal);
static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
static const size_t IsDeinitingBitCount = 1;
static const uint64_t IsDeinitingMask = maskForField(IsDeiniting);
static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
static const size_t StrongExtraRefCountBitCount = 30;
static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
static const size_t UseSlowRCBitCount = 1;
static const uint64_t UseSlowRCMask = maskForField(UseSlowRC);
static const size_t SideTableShift = 0;
static const size_t SideTableBitCount = 62;
static const uint64_t SideTableMask = maskForField(SideTable);
static const size_t SideTableUnusedLowBits = 3;
static const size_t SideTableMarkShift = SideTableBitCount;
static const size_t SideTableMarkBitCount = 1;
static const uint64_t SideTableMarkMask = maskForField(SideTableMark);
};
总结如下:
refCounted分析
接着刚开始的refCounted
值0x0000000600000003
1~31
位UnownedRefCount
,33~62
位StrongExtraRefCount
分析一下SIL代码
copy_addr的作用相当于
%new = load $*HotpotCat
strong_retain %new
store %new to %9
strong_retain
本质就是调用swift_retain
HeapObject *swift::swift_retain(HeapObject *object) {
CALL_IMPL(swift_retain, (object));
}
CALL_IMPL
调用的就是_swift_retain_
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
return object;
}
这里是调用了refCounts.increment
// Increment the reference count.
void increment(uint32_t inc = 1) {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// constant propagation will remove this in swift_retain, it should only
// be present in swift_retain_n
if (inc != 1 && oldbits.isImmortal(true)) {
return;
}
RefCountBits newbits;
do {
newbits = oldbits;
bool fast = newbits.incrementStrongExtraRefCount(inc);
if (SWIFT_UNLIKELY(!fast)) {
if (oldbits.isImmortal(false))
return;
return incrementSlow(oldbits, inc);
}
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
}
incrementStrongExtraRefCount
实现
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
对inc
做了强制类型转换,bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
意味着对33~64位加1。也就是在33位上+1然后进位。
逐行断点看下lldb中的变化如下:
class HotpotCat {
var age: Int = 18
var name: String = "Hotpot"
}
var hp = HotpotCat()
var hp1 = hp
var hp2 = hp
print(CFGetRetainCount(hp as AnyObject))
CFGetRetainCount
会增加引用计数,这里2
、4
、6
是因为31
位是IsDeinitingMask
标志位。真正引用计数需要<<1
也就是除以2
。也就是1
、2
、3
。强引用默认创建出来的对象引用计数是1。
弱引用
首先,我们已经知道在没有使用弱引用前refcountd
的内容33~62
存储着强引用的引用计数。
声明一个弱引用,我们可以在编译器看到它是一个可选值:
弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为
nil
。
在这里hp = nil
会报错,因为这里hp
已经确定为HotpotCat
类型,不能把'nil'赋值给HotpotCat
类型。如果要置为nil
,hp
要声明为可选类型var hp: HotpotCat?
打个断点看看弱引用都干了什么?
可以看到直接调用了
swift_weakInit
。再看下refcountd
:
(lldb) po hp
(lldb) po hp
<HotpotCat: 0x1052369c0>
(lldb) po hp2
▿ Optional<HotpotCat>
▿ some : <HotpotCat: 0x1052369c0>
(lldb) x/8g 0x1052369c0
0x1052369c0: 0x0000000100008188 0xc000000020a46fbc
0x1052369d0: 0x0000000000000012 0x0000746f70746f48
0x1052369e0: 0xe600000000000000 0x000000000000005f
0x1052369f0: 0x00000009a0080001 0x00007fff80bd3718
可以看到refcountd
变成了0xc000000020a46fbc
,引用计数也找不到了。
源码分析
定义了一个weak
变量,编译器自动调用了swift_weakInit
函数,这个函数是由WeakReference
调用的。说明weak
字段在编译器声明的过程当中自动生成了WeakReference
对象。
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
WeakReference
用来管理弱引用。
nativeInit
源码:
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
object
不为空,则调用formWeakReference
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
这段代码可以看到做了2件事
1.创建一个sideTable
2.incrementWeak
allocateSideTable
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
//1.拿到原有的引用计数
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// Preflight failures before allocating a new side table.
if (oldbits.hasSideTable()) {
// Already have a side table. Return it.
return oldbits.getSideTable();
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
// Preflight passed. Allocate a side table.
// FIXME: custom side table allocator
//2.通过HeapObject创建了一个HeapObjectSideTableEntry实例对象
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
//3.将创建的实例对象地址给了InlineRefCountBits,也就是 RefCountBitsT
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
// Already have a side table. Return it and delete ours.
// Read before delete to streamline barriers.
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
side->initRefCounts(oldbits);
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
这个函数做了三件事:1.拿到原有的引用计数。2.通过HeapObject创建了一个HeapObjectSideTableEntry实例对象。3.将创建的实例对象地址给了InlineRefCountBits,也就是 RefCountBitsT。
这里调用RefCountBitsT
的方法变了
//强引用
LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
//弱引用
LLVM_ATTRIBUTE_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
在弱引用方法中把创建出来的地址做了偏移操作然后存放到了内存当中。
这里其实是把side
地址存放在了uint64_t
中。就解释了上面refcountd
变成了0xc000000020a46fbc
的原因,也就是这里存储了HeapObjectSideTableEntry
实例对象的地址。
地址分析
我们还原下0xc000000020a46fbc
。
由上面的源码可以看到62好63位是保留字段,我们去掉。
由此得到我们散列表的内存地址为:
0x20A46FBC
。
HeapObjectSideTableEntry
拿到地址了,我们看下HeapObjectSideTableEntry
里面到底干了什么
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
public:
HeapObjectSideTableEntry(HeapObject *newObject)
: object(newObject), refCounts()
{ }
可以看到除了object
还有一个SideTableRefCounts
。
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
SideTableRefCountBits
源码如下:
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
uint32_t weakBits;
public:
LLVM_ATTRIBUTE_ALWAYS_INLINE
SideTableRefCountBits() = default;
LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
SideTableRefCountBits(uint32_t strongExtraCount, uint32_t unownedCount)
: RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
// weak refcount starts at 1 on behalf of the unowned count
//这里弱引用计数默认从1开始。
, weakBits(1)
{ }
LLVM_ATTRIBUTE_ALWAYS_INLINE
SideTableRefCountBits(HeapObjectSideTableEntry* side) = delete;
LLVM_ATTRIBUTE_ALWAYS_INLINE
SideTableRefCountBits(InlineRefCountBits newbits)
: RefCountBitsT<RefCountNotInline>(&newbits), weakBits(1)
{ }
LLVM_ATTRIBUTE_ALWAYS_INLINE
void incrementWeakRefCount() {
weakBits++;
}
LLVM_ATTRIBUTE_ALWAYS_INLINE
bool decrementWeakRefCount() {
assert(weakBits > 0);
weakBits--;
return weakBits == 0;
}
LLVM_ATTRIBUTE_ALWAYS_INLINE
uint32_t getWeakRefCount() {
return weakBits;
}
// Side table ref count never has a side table of its own.
LLVM_ATTRIBUTE_ALWAYS_INLINE
bool hasSideTable() {
return false;
}
};
可以看到SideTableRefCountBits
继承于RefCountBitsT
(存储uint_64_t
),这里多了一个weakBits
(uint32_t)。
uint_64_t
保留原来的引用计数,uint32_t
保存弱引用计数。
继续还原0x20A46FBC
。左移3
位拿到弱引用信息变为0x105237DE0
,直接读取下:
(lldb) po hp
<HotpotCat: 0x1052369c0>
(lldb) po hp2
▿ Optional<HotpotCat>
▿ some : <HotpotCat: 0x1052369c0>
(lldb) x/8g 0x105237DE0
0x105237de0: 0x00000001052369c0 0x0000000000000000
0x105237df0: 0x0000000200000003 0x0000000000000002
0x105237e00: 0x0000000000000000 0x0000000000000000
0x105237e10: 0x3e30633936330002 0x00027fff80bc7d98
-
0x00000001052369c0
就是实例对象的地址。 -
0x0000000200000003
就是没有weak
的时候的refcounts
。(这里有一点需要注意下,长时间断点这个值会变,原因暂时不明) -
0x0000000000000002
就是弱引用计数。
这里弱引用为2
的原因是因为SideTableRefCountBits
初始化的时候从1
开始,并且进行了incrementWeak
。
//初始化为1
SideTableRefCountBits(uint32_t strongExtraCount, uint32_t unownedCount)
: RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
// weak refcount starts at 1 on behalf of the unowned count
//弱引用计数默认从1开始。
, weakBits(1)
{ }
//+1
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
这里也就解释了被引用对象释放了为什么还能直接访问 Side Table
? Side Table
的生命周期与对象是分离的,当强引用计数为 0
时,只有HeapObject
被释放了。
只有所有的 weak
引用者都被释放了或相关变量被置nil
后,Side Table
才能得以释放。
循环引用
闭包一般默认捕获外部变量
var age = 10
//和oc中block一致,都会捕获。
let closure = {
age += 1
}
closure()
print(age)
11
(lldb)
闭包内部对变量的修改会改变外部原始变量的值。
看一个循环引用的例子:
class HotpotCat {
var age = 18
var closure: (() -> ())?
deinit {
print("HotpotCat deinit")
}
}
func test() {
var hotpot = HotpotCat()
hotpot.closure = {
hotpot.age = 1
}
}
test()
swift中有两种解决循环引用的方式:weak和unowned,区别是unowned不允许设置为nil。
//unowned
func test() {
var hotpot = HotpotCat()
hotpot.closure = { [unowned hotpot] in
hotpot.age = 1
}
}
//weak
func test() {
var hotpot = HotpotCat()
hotpot.closure = { [weak hotpot] in
hotpot?.age = 1//weak为可选值
}
}
捕获列表
定义在参数列表之前,捕获列表被写为用逗号括起来的表达式列表,并用方括号括起来。如果使用捕获列表,即使省略参数名称,参数类型和返回值,也必须加关键字
in
。
那么捕获列表的作用呢?
func test() {
var age = 10
var height = 1.85
let closure = { [age] in
print(age)
print(height)
}
age = 1
height = 1.75
closure()
}
test()
10
1.75
对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量或变量来初始化捕获列表中定义的常量。
Swift Runtime
对于纯swift类,我们用runtime获取一下它的方法列表和属性列表
//swift 地层结构与 OC 部分一致
class HotpotCat {
var age: Int = 18
func test(){
print("test")
}
}
let hotpot = HotpotCat() //HotpotCat .Type
func test(){
//获取方法列表
var methodCount: UInt32 = 0
let methodlist = class_copyMethodList(HotpotCat.self, &methodCount)
for i in 0 ..< numericCast(methodCount) {
if let method = methodlist?[i] {
let methodName = method_getName(method)
print("method:\(String(describing: methodName))")
} else {
print("not found method")
}
}
//获取属性列表
var count: UInt32 = 0
let proList = class_copyPropertyList(HotpotCat.self, &count)
for i in 0 ..< numericCast(count) {
if let property = proList?[i] {
let propertyName = property_getName(property)
print("property:\(String(utf8String: propertyName)!)")
} else {
print("not found property")
}
}
print("run")
}
test()
print("end")
输出
run
end
那么添加@objc关键字获取一下:
class HotpotCat {
@objc var age: Int = 18
@objc func test(){
print("test")
}
}
method:test
method:age
method:setAge:
property:age
run
end
这个时候可以获取到,但是oc无法调用。
继承NSObject看下:
class HotpotCat: NSObject {
var age: Int = 18
func test(){
print("test")
}
}
method:init
run
end
在这里编译器只对必要方法init
添加了@objc
。
class HotpotCat: NSObject {
@objc var age: Int = 18
@objc func test(){
print("test")
}
}
method:init
method:test
method:age
method:setAge:
property:age
run
end
这个时候我们自己的方法才暴露给了OC类。
那么这个时候在Swift类中调用HotpotCat的test方法呢?
可以看到仍然是函数表调用,这里编译器优化成了函数表调用。
那么test改为dynamic修饰呢?
class HotpotCat: NSObject {
var age: Int = 18
dynamic func test(){
print("test")
}
}
method:init
run
test
end
oc依然无法调用test,同时添加@objc + dynamic
class HotpotCat: NSObject {
var age: Int = 18
@objc dynamic func test(){
print("test")
}
}
method:init
method:test
run
test
end
这个时候test的调用已经变成了动态调用。
- 对于纯
Swift
类来说,没有动态特性。方法和属性不加任何修饰符的情况下。这个时候不具备我们所谓的Runtime特性。 - 对于纯
Swift
类方法和属性添加@objc
标识,可以通过Runtime API
拿到方法和属性,但是在OC中无法进行调度。 - 对于继承自NSObject的类来说,如果想要动态获取当前属性和方法,必须在声明前加
@objc
,否则无法通过Runtime API
获取;若要进行方法交换需要同时加上dynamic
标识。否则方法也只是暴露给OC使用(前面章节说过内部也是调用swift的方法),不具备动态特性。
源码分析
Swift
默认基类_SwiftObject
Swift
实现了NSObject
协议。Swift
为了和OC
交互在Swift
里面保留了这样的数据结构,底层数据结构和OC部分一致。TargetAnyClassMetadata
中也可以看到对应信息:isa,superclass在objc源码中可以就看到
swift_class_t
继承自objc_class
struct swift_class_t : objc_class {
uint32_t flags;
uint32_t instanceAddressOffset;
uint32_t instanceSize;
uint16_t instanceAlignMask;
uint16_t reserved;
uint32_t classSize;
uint32_t classAddressOffset;
void *description;
// ...
void *baseAddress() {
return (void *)((uint8_t *)this - classAddressOffset);
}
};
元类型、AnyClass、Self
AnyObject
class HotpotCat {
var age: Int = 18
var name: String = "hotpot"
}
var hp = HotpotCat()
//hp1 代表 HotpotCat 实例对象
var hp1: AnyObject = hp
//hp2 代表 HotpotCat 类的类型
var hp2: AnyObject = HotpotCat.self
//这个协议只能类遵守
protocol Hotpot: AnyObject {
}
在我们不知道对象的具体类型时,可以用AnyObject处理,比如:
var age: AnyObject = 10 as NSNumber
需要确保对象是instance、类、协议(仅类遵从的)。有点类似oc
的id
的意思。
Any
class HotpotCat {
var age: Int = 18
var name: String = "hotpot"
}
var hp = HotpotCat()
var age: AnyObject = 10 as NSNumber
func test() {
print("test")
}
//这里就只能用Any修饰了
var hp2:HotpotCat?
var array:[Any] = [hp,1,"hotpot",true,test,age,hp2]
Any
包含AnyObject
。代表任意类型,包括函数和可选类型。
AnyClass
public typealias AnyClass = AnyObject.Type
AnyClass
是AnyObject.Type
类型。
T.self
class HotpotCat {
var age: Int = 18
var name: String = "hotpot"
}
var hp = HotpotCat()
// 本身
var hp1 = hp.self
//metadata元类型
var hp2: AnyObject = HotpotCat.self
(lldb) po hp
<HotpotCat: 0x105807040>
(lldb) po hp1
<HotpotCat: 0x105807040>
(lldb) po hp2
<SwiftARC.HotpotCat: 0x100008188>
(lldb) po hp.age.self
18
(lldb) po Int.self
Swift.Int
-
instance
/值``.self
就是其本身。 -
Class
返回metadata``.self
,类型返回类型本身(Int
)。
.Type
是一种类型,T.self
是T.Type
类型。也就是元类型的类型。
type(of:)
class Hotpot {
var age: Int = 18
func test() {
print("Hotpot Class")
}
}
class HotpotCat: Hotpot {
override func test() {
print("HotpotCat Class")
}
}
func test(_ value: Hotpot) {//这里编译期类型就是Hotpot
//这里动态类型是 HotpotCat
value.test()
//dynamic type: type(of:)获取动态类型
print(type(of: value))
}
var hp = HotpotCat()
test(hp)
HotpotCat Class
这里相当于做了类型转换。
如果改成协议呢?
protocol HPProtocol {
}
class Hotpot: HPProtocol {
var age: Int = 18
func test() {
print("Hotpot Class")
}
}
func test(_ value: HPProtocol) {
print(type(of: value))
}
var hp = Hotpot()
var hp1: HPProtocol = Hotpot()
test(hp)
test(hp1)
Hotpot
Hotpot
这里符合我们的预期。那么test
改成泛型呢?
func test<T>(_ value: T) {
print(type(of: value))
}
Hotpot
HPProtocol
在有协议有泛型的参与下hp2
在type(of:)
后变成了HPProtocol
。所以在有协议和泛型的时候type(of:)
并不能推断出来。那么把value转换一下呢?
func test<T>(_ value: T) {
print(type(of: value as Any))
}
Hotpot
Hotpot
所以在有协议和泛型的时候如果要获取类型,我们需要先转成Any
再获取。
总结
- AnyObject:代表任意类的instance,类的类型,仅类遵守的协议。
- Any:代表任意类型,包括funcation类型或者optional类型。
- AnyClass:代表任意实例的类型,AnyObject.Type。
- T.self:T是实例,返回实例本身;T是类型,返回Metadata。
- T.Type:一种类型,T.self是T.Type类型。
- type(of:):用来获取一个值得动态类型。