1. iOS内存分区情况
-
栈区(Stack) 由编译器自动分配释放,存放函数的参数,局部变量的值等 栈是向低地址扩展的数据结构,是一块连续的内存区域
-
堆区(Heap) 由程序员分配释放 是向高地址扩展的数据结构,是不连续的内存区域
-
全局区 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域 程序结束后由系统释放
-
常量区 常量字符串就是放在这里的 程序结束后由系统释放
-
代码区 存放函数体的二进制代码
-
注:
- 在 iOS 中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责的
- 系统使用一个链表来维护所有已经分配的内存空间(系统仅仅记录,并不管理具体的内容)
- 变量使用结束后,需要释放内存,OC 中是判断引用计数是否为 0,如果是就说明没有任何变量使用该空间,那么系统将其回收
- 当一个 app 启动后,代码区、常量区、全局区大小就已经固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错)
2. UIView 和 CALayer 是什么关系?
-
UIView 继承 UIResponder,而 UIResponder 是响应者对象,可以对iOS 中的事件响应及传递,CALayer 没有继承自 UIResponder,所以 CALayer 不具备响应处理事件的能力。CALayer 是 QuartzCore 中的类,是一个比较底层的用来绘制内容的类,用来绘制UI
-
UIView 对 CALayer 封装属性,对 UIView 设置 frame、center、bounds 等位置信息时,其实都是UIView 对 CALayer 进一层封装,使得我们可以很方便地设置控件的位置;例如圆角、阴影等属性, UIView 就没有进一步封装,所以我们还是需要去设置 Layer 的属性来实现功能。
-
UIView 是 CALayer 的代理,UIView 持有一个 CALayer 的属性,并且是该属性的代理,用来提供一些 CALayer 行的数据,例如动画和绘制。
3.Bounds 和 Frame 的区别?
-
Bounds:一般是相对于自身来说的,是控件的内部尺寸。如果你修改了 Bounds,那么子控件的相对位置也会发生改变。
-
Frame :是相对于父控件来说的,是控件的外部尺寸。
4.Block 对象内存相关知识
iOS 内存分布,一般分为:栈区、堆区、全局区、常量区、代码区。其实 Block 也是一个 Objective-C 对象,常见的有以下三种 Block:
NSMallocBlock :存放在堆区的 Block NSStackBlock : 存放在栈区的 Block NSGlobalBlock : 存放在全局区的 Block 通过代码实验(声明 strong、copy、weak 修饰的 Block,分别引用全局变量、全局静态变量、局部静态变量、普通外部变量) ,得出初步的结论:
Block 内部没有引用外部变量,Block 在全局区,属于 GlobalBlock Block 内部有外部变量: 引用全局变量、全局静态变量、局部静态变量:Block 在全局区,属于 GlobalBlock 引用普通外部变量,用 copy,strong 修饰的 Block 就存放在堆区,属于 MallocBlock;用 weak 修饰的Block 存放在栈区,属于 StackBlock 注意:Block 引用普通外部变量,都是在栈区创建的,只是用 strong、copy 修饰的 Block 会把它从栈区拷贝到堆区一份,而 weak 修饰的 Block 不会;
5.关键字assign、weak、strong、copy该怎么用
assign: 用于对基本数据类型进行赋值操作,不更改引用计数。也可以用来修饰对象,但是,被assign修饰的对象在释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,成为野指针。如果后续在分配对象到堆上的某块内存时,正好分到这块地址,程序就会crash。之所以可以修饰基本数据类型,因为基本数据类型一般分配在栈上,栈的内存会由系统自动处理,不会造成野指针。
weak: 修饰Object类型,修饰的对象在释放后,指针地址会被置为nil,是一种弱引用。 delegate为何要用weak修饰: 在ARC环境下,为避免循环引用,往往会把delegate属性用weak修饰;在MRC下使用assign修饰。weak和strong不同的是:当一个对象不再有strong类型的指针指向它的时候,它就会被释放,即使还有weak型指针指向它,那么这些weak型指针也将被清除。
ARC下的strong等同于MRC下的retain都会把对象引用计数加1。
copy: 会在内存里拷贝一份对象,两个指针指向不同的内存地址。一般用来修饰NSString等有对应可变类型的对象,因为他们有可能和对应的可变类型(NSMutableString)之间进行赋值操作,为确保对象中的字符串不被修改 ,应该在设置属性是拷贝一份。而若用strong修饰,如果对象在外部被修改了,会影响到属性。 block属性为什么需要用copy来修饰?
因为在MRC下,block在创建的时候,它的内存是分配在栈(stack)上的,而不是在堆(heap)上,可能被随时回收。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。通过copy可以把block 拷贝(copy)到堆,保证block的声明域外使用。在ARC下写不写都行,编译器会自动对block进行copy操作。
__block与__weak的区别 __block:在ARC和MRC下都可用,可修饰对象,也可以修饰基本数据类型。 __block: 对象可以在block被重新赋值,__weak不可以。 __weak:只在ARC中使用,只能修饰对象,不能修饰基本数据类型(int、bool)。
同时,在ARC下,要避免block出现循环引用,经常会:__weak typedof(self) weakSelf = self;
6. id和instancetype的区别
相同之处
instancetype和id都能用来表示任意类型
不同之处
instancetype只能作为返回值类型,而id既可以作为返回值类型,也可以作为参数类型,也可以作为变量类型
instancetype在编译期会进行类型检测,id在编译时期编译器不检查类型,只能返回未知类型的对象,调用任何方法不会给出错误提示
id是一个指向继承了NSObject的Objective-C对象的指针
7.Objective-C 如何对内存管理的
答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。 1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。 2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。 3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。
8.KVC的底层实现?
当一个对象调用setValue方法时,方法内部会做以下操作: 1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。 2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。 3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。 4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。 这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
9.ViewController生命周期
按照执行顺序排列:
- initWithCoder:通过nib文件初始化时触发。
- awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
- loadView:开始加载视图控制器自带的view。
- viewDidLoad:视图控制器的view被加载完成。
- viewWillAppear:视图控制器的view将要显示在window上。
- updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
- viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
- viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
- viewDidAppear:视图控制器的view已经展示到window上。
- viewWillDisappear:视图控制器的view将要从window上消失。
- viewDidDisappear:视图控制器的view已经从window上消失。
10.OC中的反射机制
1). class反射 通过类名的字符串形式实例化对象。
Class class = NSClassFromString(@"student");
Student *stu = [[class alloc] init];
将类名变为字符串。
Class class =[Student class];
NSString *className = NSStringFromClass(class);
2). SEL的反射 通过方法的字符串形式实例化方法。
SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];
将方法变成字符串。
NSStringFromSelector(@selector*(setName:));
11. 响应者链和事件传递
- 当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
- UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
- UIWindow将事件向下分发,即UIView。
- UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
- 遍历子控件,重复以上两步。
- 如果没有找到,那么自己就是事件处理者。
- 如果自己不能处理,那么不做任何处理。 其中 UIView不接受事件处理的情况主要有以下三种
- alpha <0.01
- userInteractionEnabled = NO(中间一层userInteractionEnabled = NO,则事件响应者链受阻)
- hidden = YES.
12. iOS的沙盒目录结构是怎样的?
沙盒结构:
- Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
- Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
- Library:
- Caches:存放体积大又不需要备份的数据。(常用的缓存路径)
- Preference:设置目录,iCloud会备份设置信息。
- tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。
13. GCD 与 NSOperation 的区别
GCD 和 NSOperation 都是用于实现多线程: GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。 NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。
14. 如何用GCD同步若干个异步调用?(如若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
// 当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片
});
15. dispatch_barrier_async(栅栏函数)的作用是什么?
作用:
1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。
2.避免数据竞争
// 1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 2.向队列中添加任务
dispatch_async(queue, ^{ // 1.2是并行的
NSLog(@"任务1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2, %@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"任务 barrier, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{ // 这两个是同时执行的
NSLog(@"任务3, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4, %@",[NSThread currentThread]);
});
// 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4
// 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。
16.什么是 RunLoop
从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。 一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。
主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
17. Git vs SVN
Git是分布式的,SVN是集中式的
这是 Git 和 SVN 最大的区别。若能掌握这个概念,两者区别基本搞懂大半。因为 Git 是分布式的,所以 Git 支持离线工作,在本地可以进行很多操作,包括接下来将要重磅推出的分支功能。而 SVN 必须联网才能正常工作。
18.Runtime实现的机制是什么
- 使用时需要导入的头文件 <objc/message.h> <objc/runtime.h>
- Runtime 运行时机制,它是一套C语言库。
- 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。 比如: 类转成了 Runtime 库里面的结构体等数据类型, 方法转成了 Runtime 库里面的C语言函数, 平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制) // OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。 // [stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));
- 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。 有了Runtime库,能做什么事情呢? Runtime库里面包含了跟类、成员变量、方法相关的API。 比如:
- 获取类里面的所有成员变量。
- 为类动态添加成员变量。
- 动态改变类的方法实现。
- 为类动态添加新的方法等。 因此,有了Runtime,想怎么改就怎么改。
19.请简单的介绍下APNS发送系统消息的机制
APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。 APNS的原理:
- 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
- 应用程序接收到设备令牌并发送给自己的后台服务器
- 服务器把要推送的内容和设备发送给APNS
- APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示