0.0.0, 内存相关: iOS 面试题记忆辅助

1, 开篇

本文试图回答,如下问题:

1, 堆和栈

2,__weak 修饰符

  1. __block 修饰符
特色

太多博客对这些,都有挺详细的阐述了。

他们并不合适,面试的时候照着念

面试官问:堆和栈

答: 1、栈,系统分配,堆,人为申请开辟;2、栈,空间较小,堆空间较大;3、栈快,堆慢;4、栈,连续,堆,不连续

不能够很好的鼓舞对方

面试官问: __weak 修饰符

答:三两句介绍下全局 weak 表。三两句介绍下是什么机制下。三两句介绍下使用场景

为了把其他博客的内容说出来,候选人把方法名简单报一遍,objc_initWeakobjc_storeWeak

至于对方,没什么感染力

一般的技术博客适合看,不适合说。

( 看,也不一定看得懂。

看懂了几分,不好记忆,面试的时候,不方便及时组织语言阐述 )

总结: 网上大多,对小白不友好。

有了本文, 小白友好型。iOS 面试照着念第一期

本文有参考 C…/iOSInterviewQuestions

照着念:

面试官问个问题,照着下面念,

回答从 20 s 到分钟级别,面试时间丝滑流畅,过的飞快

1, 堆和栈
  • 栈,函数栈。函数运行,就是把一帧一帧的函数压栈,投入计算。计算完成后,执行出栈操作。

栈的内存,系统维护。需要扩大变量的作用域,就把变量放在堆上面。

  • 堆,对象堆。主要放对象,原本程序员手动管理堆上对象的内存,现在有 ARC 帮助管理。
补充:

栈和堆,都存在于计算机的 RAM 中。

栈,存放函数、临时变量和引用对象。计算函数帧的时候,用到临时变量。

接着问: 栈和堆都是同属一块内存,为什么栈比堆快:

答:内存管理,分增删改查。

  • 栈比堆块,主要体现在新增,也就是内存分配上:

栈,连续。创建变量,就是栈的顶部增长。拿到栈的指针,移动固定的长度。操作简单

堆,内存不连续。每次新增,查询 free list, 寻找空余区域。开销较大

free list,记录着空余的、一块一块的内存。

调用 malloc, free list 看哪一块内存 size 大于需要的内存 size, 返回那块内存块的地址,并记录该内存块已占用

随着后续… ( 动态数组扩容等 ), 分配的原始内存块不够,需要加内存,堆上的操作就更加复杂了

  • 再看查:

栈,拿到首地址,计算偏移。

堆,查询一个维护的内存映射表。开销较大

  • 改和删,类似

总结:

  • 栈上的,内存分配和回收很简单。

都是移动栈的指针,返回栈的指针地址

调用函数,栈添加函数帧,栈顶指针前移,

计算完成函数,栈清除函数帧,栈顶指针后移。

栈的内存管理,接近 CPU 指令 ( CPU Instruction ) ( 这句,有一点打压…,具体看个人爱好 )

  • 堆的内存管理,一整套策略。

快,因为操作简单。 面试的时候,记得多少,说…

附加:
  • ds, 数据结构

计算机的栈的行为,与数据结构中栈一致。

计算机的堆,与数据结构中的堆,没有任何关系

  • {},作用域

{…}, 这是一个作用域,

也就是函数帧栈上的,一帧

2, __weak 修饰符

__weak , 可以避免内存泄露的问题。对象之间的强强引用,造成对象不能被正常释放

__weak 不会产生强引用,指向的对象销毁,自动让指针置 nil.

  • 创建

id __weak cat = obj;

这个转文字描述,还好吧

实现是,objc_storeWeak, 把 obj 的地址 ( 赋值对象的地址 ) ,作为键,

把 cat 的,附有 __weak 修饰符的变量的地址作为值, 添加到 weak 表

  • 销毁

如果 obj 为 nil, 则把 weak 表中,所有的 obj 对应的变量地址清空,包括 cat

销毁完整 4 步:

a, 从 weak 表中,获取废弃对象地址为键的记录 ( 获取 )

b, 将记录中所有带 __weak 修饰符变量的地址,置为 nil ( 操作对象 )

c, 从 weak 表中,删除该记录 ( 删 weak 表记录 )

d, 从引用计数表中,删除废弃对象地址为键的记录 ( 删从引用计数表的记录 )

  • 原理

a, weak_table 包含很多条目 weak_entry_t,和条目的个数 num_entries

一个对象对应一个条目 weak_entry_t,weak_entry_t 有 OC 对象的弱引用信息

id __weak cat = obj;

这样下,obj 的地址,对应 weak_entry_t 的 DisguisedPtr<objc_object> referent; // 被弱引用的对象

weak_entry_t 里面有一个联合 Union 的结构,包含定长数组 inline_referrers[WEAK_INLINE_COUNT] 和动态数组 weak_referrer_t *referrers

里面都是放弱引用对象的指针地址

具体是 cat 的地址,算出哈希值,(弱引用该对象的对象指针地址 hash 数组,添加)

弱引用该对象的指针数目小于等于 4 (WEAK_INLINE_COUNT) , weak_entry_t 使用定长数组,反之使用动态数组

b, runtime 维护很多 SideTables,SideTables 包含很多 SideTable,SideTable 包含 weak_table,引用计数表,和自旋锁

讲一下数据结构,可以了。增删改查,自己发挥

3,__block 修饰符

问: 为什么 block 不能修改外面的临时变量?

长这样:

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        int cats = 10;
        Block block = ^{
            
            //  Variable is not assignable (missing __block type specifier)
            cats = 20; // 报错
            NSLog(@"我有 %@ 只小猫", @(cats));
        };
        block();
        
    }
    return 0;
}


答: block 访问外面的临时变量,不能修改。因为他们在当前栈的不同的函数帧上。

简单理解下,block 匿名函数,访问外面的临时变量。外部临时变量是,该函数的传参。

附加:

默认情况下的,函数帧调用栈

__block

该 block 作用域截获的变量 cats, 没有 __block 是简单的传值。

现在传入的是一个结构体。该结构体包含两个成员变量,一个 __forwarding 指针,一个原本的值 cats

其实该结构体有 5 个成员变量,isa 指针,起码的。本文中没啥用的,尽量不提

__forwarding 指针,指向该结构体

cats = 20; 实际的实现是 (__cself -> cats -> __forwarding -> cats ) = 20;

__cself -> cats 是一个结构体,他通过成员变量 __forwarding 访问成员变量 cats

通过成员变量 __forwarding,可以实现无论 __block 变量配置在栈上,还是堆上时,都能够正确地访问

简单理解下,block 匿名函数,访问外面的临时变量。外部临时变量这时候,该函数传参为指针。

其实我也一般的…。就…日本大佬写的书。面试还是要…。话说回来,搞得对面坐着的很…。又不是…都是 Chris Lattner

补充,其他方法

外部的静态变量 ( static ) 和全局变量,是通过指针传递到 block 中的,可修改

static 表示作为静态变量,存储在数据区。

附加:

auto 表示作为自动变量,存储在栈区。

函数里面临时的临时变量,默认有一个 auto 关键字,没啥用

感觉网上…不明觉厉,胡说…

尾, 本文不功利

本文又名,“iOS 开发,不懂的,有点印象吧”

本文仅供参考

本文不够严谨,

小白亲切,有点用

提示:
  • 基础问题,回答偏了,老兄好像没有潜力呀

所以本系列,写几句试试

  • 面试中,一露怯,gg

满口跑火车,…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值