Swift -04:内存管理,元类型,Mirror

1.swift强引用

我们编写如下代码

class PWTeacher {
    var age:Int = 18
    var name:String = "Kody"
}
let t = PWTeacher()
var t1 = t;
var t2 = t

我们经过lldb调试

image.png

那么引用计算内都包含什么信息呢,接下来我们用源码探索一下
在HeapObject.cpp文件中的swift_allocObject方法中,我们看下HeapObject()
我们看一下HeapObject的成员变量中

struct HeapObject {
  HeapMetadata const *metadata;
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

InlineRefCounts----->RefCounts<InlineRefCountBits>-----> InlineRefCountBits---->RefCountBitsT<RefCountIsInline>--->RefCountBitsT---->BitsType bits
BitsType实质是uint64_t所以管理引用计数操作的实质是uint64_t
那我们在创建swiftObject的时候是怎么操作的呢
HeapObject(metadata)--->refCounts(InlineRefCounts::Initialized)
Initialized下面

constexpr RefCounts(Initialized_t)
    : refCounts(RefCountBits(0, 1)) {}

我们查看RefCountBitsT的初始化方法

  RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
    : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
           (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
           (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
  { }

Offsets--->RefCountBitOffsets, RefCountBitOffsets我们可以用下面表示


image.png

我们知道t的引用计算应该是3,结合上面的图

image.png

33位到62的二进制值为11,十进制位3.
我们在var t1 = t;是swift怎么处理的呢
我们通过汇编调试,看到swift_retain在swift源码中看一下swift_retain具体实现object->refCounts.increment(1);

 bool incrementStrongExtraRefCount(uint32_t inc) {
    // This deliberately overflows into the UseSlowRC field.
    bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
    return (SignedBitsType(bits) >= 0);
  }

让我们需要加的1移动到33位,然后加到bits里

2.swift弱引用

class PWTeacher {
    var numberOneStudent:PWStudent?
    deinit {
        print("PWTeacher deinit")
    }
    
}

class PWStudent {
    var teacher:PWTeacher?
    deinit {
        print("PWStudents deinit")
    }
    
}

PWTeacher和PWStudent中相互包含对象,这就会产生引用,我们可以用weak来消除循环引用

func test()  {
    let teacher = PWTeacher()
    weak  var teacherWeak = teacher
    let student = PWStudent()
    student.teacher = teacherWeak
}
test()

weak到底做了什么呢我们汇编调试发现,weak修饰的对象,底层调用的是swift_weakInit,我们去源码看一下,到底做了什么
swift_weakInit------>ref->nativeInit(value)---->object->refCounts.formWeakReference()---->allocateSideTable(true)
我们重点看一下allocateSideTable

  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());

HeapObjectSideTableEntry对象保存实力对象和refCounts
refCounts是SideTableRefCounts类型
SideTableRefCountBits继承自RefCountBitsT,我们在前面分析到RefCountBitsT的成员变量是uint64_t,SideTableRefCountBits的成员变量是uint32_t
在InlineRefCountBits(side)中,会把side的地址指针保存下来

RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
           | (BitsType(1) << Offsets::UseSlowRCShift)
           | (BitsType(1) << Offsets::SideTableMarkShift))

我们知道bits是64位,保存side的规则是 side的地址右移3位,且62,63位保存其他。

var t =  Teacher()
weak var tw = t;
print("end")

在print处打断点,看内存布局

(lldb) p t
(RAMLayout.Teacher) $R8 = 0x000000010382e800 (age = 18)

(lldb) x/8gx 0x000000010382e800
0x10382e800: 0x0000000100008158 0xc0000000200e0ad0
0x10382e810: 0x0000000000000012 0x0000000000000000
0x10382e820: 0x00007fff88a9e9f0 0xffffffffffffffff
0x10382e830: 0x0000000800000000 0x0000000000000000

0xc0000000200e0ad0保存side的内存地址,0xc0000000200e0ad0把62和63位设置为0,然后向左平移3位得到0x100705680

(lldb) x/4gx 0x100705680
0x100705680: 0x000000010382e800 0x0000000000000000
0x100705690: 0x0000000a00000003 0x0000000000000002

可知0x000000010382e800为t的地址
weak必须是一个可选类型,才能允许被设置成nil

3.闭包的循环引用

3.1.闭包一般默认捕获外部的变量

func test()  {
    var age = 10
    let clourse = {
        age += 1
    }
    clourse()
    print(age)
}
test()
//打印结果是11

3.2.实例变量在闭包中修改

class Teacher {
    var age:Int = 18
    //反初始化器
    deinit {
        print("Teacher deinit")
    }
}
func test()  {
    let t = Teacher()
    let clourse = {
        t.age += 1
    }
    clourse()
    print(t.age)
}
test()

deinit和oc中的dealloc相似,在对象销毁的时候调用。
打印结果

19
Teacher deinit

对于上面的例子我们经过修改


class Teacher {
    var age:Int = 18
    var finishBlock:(()->())?
        deinit {
        print("Teacher deinit")
    }
}
func test()  {
    let t = Teacher()
    let clourse = {
        t.finishBlock = {
            t.age = 20
        }
    }
    clourse()
    print(t.age)
}
test()
//打印结果
18

从打印结果可以知道deinit没有被调用,产生了循环引用。
t->finishBlock->t
怎么解决循环引用呢

3.3解决循环引用

解决循环引用有两种方式,一种是weak,一种是unowned
weak是弱引用,我们在前面和oc中都有用到,weak修改的变量是可选类型,可以被设置为nil,unowned引用和weak最大的区别是无法被设置成nil。
unowned在运行期间我们都假定有值的,如果经过unowned修饰的实例被置为nil,我们再调用会出现野指针
我们上面的例子修改如下

func test()  {
    let t = Teacher()
    let clourse = {
        t.finishBlock =  {
            [unowned t] in
            t.age = 20
        }
    }
    clourse()
    print(t.age)
}

我们能看到deinit的打印。
如果我们用weak来改变循环引用

func test()  {
    let t = Teacher()
    let clourse = {
        t.finishBlock =  {
            [weak t] in
            t?.age = 20
        }
    }
    clourse()
    print(t.age)
}

但是需要注意的是 t?.age = 20。

3.4.捕获列表

定义在参数列表之前,捕获列表被写为用逗号括起来的表达式列表,并用方括号括起来。如果使用捕获列表,则即时省略参数名称,参数类型和返回类型也必须使用in关键字

var age = 0
var height = 0.0
let clourse = {
    [age] in
    print(age)
    print(height)
}
age = 10
height = 1.85
clourse()

打印结果是0和1.85
对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量和变量,来初始化捕获列表中定义的常量。

4.Swift Runtime探索

4.1纯swift类获取

class PWTeacher {
  var name:String = "PW"
func teach()  {
        print("teach")
    }
}
func test()  {
    var count:UInt32 = 0;
    let methods = class_copyMethodList(PWTeacher.self, &count);
    for i in 0..<Int(count) {
        guard let method  = methods?[i] else { return }
        let methodString = method_getName(method)
                print("方法\(methodString)")

        
    }
    var proCount:UInt32 = 0
    let propertys  =  class_copyPropertyList(PWTeacher.self, &proCount);
    for j in 0..<Int(proCount) {
        guard let property = propertys?[j] else { return }
      let propertyName =  property_getName(property)
        print("成员变量\(String(utf8String: propertyName)!)")
    }

    
}

test()

我们没有打印,我们修改

4.2.@objc修饰

class PWTeacher {
 @objc  var name:String = "PW"

   @objc func teach()  {
        print("teach")
    }
}

我们看打印结果

方法teach
方法name
方法setName:
成员变量name

但是这种,我们类我们无法在oc中访问。

4.2.swift类继承NSObject

class PWTeacher :NSObject{
   var name:String = "PW"

   func teach()  {
        print("teach")
    }
}

打印结果

方法init
方法.cxx_destruct

我们看 项目-Swift.h文件

@interface PWTeacher : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

只能看到一个init方法

4.2.swift类继承NSObject并添加@objc关键字

class PWTeacher :NSObject{
 @objc  var name:String = "PW"

 @objc  func teach()  {
        print("teach")
    }
}

打印结果

方法teach
方法init
方法name
方法.cxx_destruct
方法setName:
成员变量name

在看项目-Swift.h文件

@interface PWTeacher : NSObject
@property (nonatomic, copy) NSString * _Nonnull name;
- (void)teach;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

我们可以在oc文件中访问PWTeacher类

4.2.swift类继承NSObject添加dynamic

class PWTeacher :NSObject{
  var name:String = "PW"

 dynamic  func teach()  {
        print("teach")
    }
}

打印结果

方法init
方法.cxx_destruct

即时给方法添加了dynamic,在oc中还是访问不到的该方法的
总结
1.对于纯 Swift 类来说,没有 动态特性。⽅法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的 Runtime 特性了,这和我们在上⼀节课的⽅法调度(V-Table调度)是不谋⽽合的。 dynamic (动态特性)
2.对于纯 Swift 类,⽅法和属性添加 @objc 标识的情况下,当前我们可以通过 Runtime API 拿到,但 是在我们的 OC 中是没法进⾏调度的。
3.对于继承⾃ NSObject 类来说,如果我们想要动态的获取当前的属性和⽅法,必须在其声明前添加 @objc 关键字,⽅法交换: dynamic的标识。否则也是没有办法通过 Runtime API 获取的。
补充
在swift中默认的基类是SwiftObject在swift源码中可以看到

SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
 @private
  Class isa;
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}

- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

- (Class)superclass;
- (Class)class;
- (instancetype)self;
- (struct _NSZone *)zone;
...
}

5.反射

反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。上⾯我们分析过 了,对于⼀个纯 Swift 类来说,并不⽀持我们直接像 OC 那样操作;但是 Swift 标准库依然提供了反射机 制让我们访问成员信息, 反射的⽤法⾮常简单,我们⼀起来熟悉⼀下:

class PWTeacher :NSObject{
  var name:String = "PW"
 var age = 18
}
func test()  {
 let mirror = Mirror.init(reflecting: PWTeacher.self);
    for pro in mirror.children {
        print("\(pro.label ?? "123")--\(pro.value)")
    }
    print("end")
    
}

test()

我们只看到打印end,for循环内没有打印,这是因为 Mirror.init内reflecting参数需要传实例
我们修改如下

 let mirror = Mirror.init(reflecting: PWTeacher());
//打印结果
name--PW
age--18
end

6.元类型、AnyClass、Self

1.AnyObject:
代表任意类的 instance,类的类型,仅类遵守的协议。

let t = PWTeacher()
let t2:AnyObject = t //类的 instance
let t3:AnyObject = PWTeacher.self //类的类型
protocol PWJsonMap:AnyObject {
    
}
class PWStudent: PWJsonMap { //仅类遵守的协议,如果是结构体遵守协议就会报错
    
}

2.Any:
代表任意类型,包括 funcation 类型或者 Optional 类型

var arrary:[AnyObject] = [1,"pw",true]
//编译器会报错,我们修改AnyObject为Any,编译成功
var arrary:[Any] = [1,"pw",true]

3.AnyClass
代表任意实例的类型: AnyObject.Type
4.T.self
如果 T是实例对象,返回的就是它本身; T 是类,那么返回的是 Metadata
5.T.Type
⼀种类型, T.self 是 T.Type 类型
6.type(of:)
⽤来获取⼀个值的动态类型

func test(value:Any)  { 
  let typeValue = type(of: value)
    print(typeValue)
}
let age = 10
test(value: age)
//输出是Int

test(value:Any)是静态类型
type(of: value)是动态类型
用继承关系说明这种情况

class PWTeacher :NSObject{
    func teach()  {
        print("PWTeacher teach")
    }
}
class PWParTimreTeacher: PWTeacher {  
    override func teach() {
        print("PWParTimreTeacher teach")
    }
    
}
func test(value:PWTeacher)  {
    value.teach()
    print(type(of: value))
}
let teacher = PWParTimreTeacher()
test(value: teacher)

协议下type的情况

protocol PWProtocol {
    
}
class PWTeacher :PWProtocol{
    func teach()  {
        print("PWTeacher teach")
    }
    
}
func test(value:PWProtocol)  {
    print(type(of: value))
}

let teacher = PWTeacher()
let t2 :PWProtocol = PWTeacher()
test(value: teacher)
test(value: t2)

打印结果是PWTeacher
我们修改test函数

func test<T>(value:T)  {
    print(type(of: value))
}

打印结果是

PWTeacher
PWProtocol

这并不是我们想要的,于是我们修改test方法

func test<T>(value:T)  {
    print(type(of: value as Any))
}

运行后的打印结果是

PWTeacher
PWTeacher
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335