1, 开篇
本文试图回答,如下问题:
1, 堆和栈
2,__weak
修饰符
__block
修饰符
特色
太多博客对这些,都有挺详细的阐述了。
他们并不合适,面试的时候照着念
面试官问:堆和栈
答: 1、栈,系统分配,堆,人为申请开辟;2、栈,空间较小,堆空间较大;3、栈快,堆慢;4、栈,连续,堆,不连续
不能够很好的鼓舞对方
面试官问: __weak 修饰符
答:三两句介绍下全局 weak 表。三两句介绍下是什么机制下。三两句介绍下使用场景
为了把其他博客的内容说出来,候选人把方法名简单报一遍,objc_initWeak
和 objc_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
满口跑火车,…