iOS- 基础性问题(面试问题)

App的启动过程:

打开程序——执行main函数——UIAPPlicationMain函数——初始化UIAPPlicationMain函数(设置代理,开启runloop)——监听系统事件,通知AppDelegate——程序结束

总结:面试官问的是应用程序的生命周期,想听到的关键词应该是:main函数、UIApplicationMain、AppDelegate、runloop、监听

另外总结一下关于runloop的知识点:

runloop:运行循环,在程序运行过程中循环做一些事
runloop的作用:保持程序持续运行、处理App中的各种事件、提高资源利用率
runloop在实际项目中的应用:控制线程的生命周期、解决NSTimer在滑动时停止工作的问题、监控应用的卡顿、性能优化


CAAnimation动画分类:

  1. 基础动画(如物品放入购物车进行移动)( CABasicAnimation)
  2. 关键帧动画,图片帧(如人、动物走动)( CAKeyframAnimation)
  3. 转场动画(一个到另一个场景,如翻页)( CATransition)
  4. 组合动画( CAAnimationGroup)

可以做动画的值
1.形状系列:frame bounds
2.位置系列:center
3.色彩系列:alpha color
4.角度系列:transform(旋转的角度)

离屏渲染:是指图形处理单元(GPU)将渲染结果绘制到屏幕之外的缓冲区,而不是直接输出到屏幕。这种技术常用于需要复杂处理或后处理的场景。
缺点:增加内存开销,导致渲染延迟,影响用户体验
圆角+裁剪、阴影、光栅化、遮罩、透明度,用path/绘制 代替

链表和数组有什么区别

数组和链表有以下不同:
(1)存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个节点要保存相邻结点指针;
(2)数据:查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低;
(3)数据:插入/删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动;
(4)越界问题:链表不存在越界问题,数组有越界问题。

数组便于查询,链表便于插入删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。

Socket 的原理

  1. Socket 的基本概念
    -Socket 是应用程序与网络的通信接口
    -每个 Socket 由一个 IP 地址和一个端口号唯一标识。
    -Socket 通信基于 TCP/IP 或 UDP 协议。

  2. Socket 的类型
    -流式 Socket:基于 TCP 协议,提供可靠的、面向连接的通信。数据以字节流的形式传输,确保数据顺序和完整性。(Stream Socket)
    -数据报 Socket:基于 UDP 协议,提供无连接的通信。数据以数据包的形式传输,不保证顺序和可靠性,但传输效率更高。(Datagram Socket)
    -原始 Socket:允许直接访问底层协议(如 IP、ICMP),通常用于网络协议的开发和调试。(Raw Socket)


GCD

同步执行:不会开启新的线程,任务按顺序执行。
异步执行:会开启新的线程,任务可以并发执行。

区别是会不会开启新的线程。组合:
同步串行队列:one by one
异步串行队列:one by one (因为前一个任务不执行完毕,队列不会调度)
同步并行队列:one by one (因为同步执行不会开启新的线程)
异步并发队列:可以实现任务的并发,经常用到

主队列:主队列是串行队列,「只有一个线程」,那就是主线程,添加到主队列中的任务会在主线执行。通过dispatch_get_main_queue获取主队列。

全局队列:全局队列是并发队列。可以通过dispatch_get_global_queue获取不同级别的全局队列。
同步主队列:死锁卡住不执行。
主队列异步:one by one (因为没有开启新线程)

iOS 指针Pointer有几种,分别是什么意思?

  1. 强指针(Strong)强指针会增加对象的引用计数,确保对象在使用期间不会被释放。
  2. 弱指针(Weak)弱指针不会增加对象的引用计数,对象释放时,指针会自动置为nil,避免野指针。
  3. 野指针(Dangling)对象被释放后,指针未置为nil,继续访问该指针会导致崩溃(指向无效内存)。
  4. 空指针(Null)指针未初始化或显式设置为nil/NULL。
注意:ARC 自动管理内存,通常不会出现野指针问题(需注意循环引用问题)。

启用僵尸对象(Zombie Objects):
在调试时,可以启用僵尸对象模式,帮助检测野指针问题:
在 Xcode 中,进入 Edit Scheme -> Run -> Diagnostics,勾选 Enable Zombie Objects。
启用后,释放的对象会变成僵尸对象,访问时会触发异常并打印日志。

- (void)test {
    // 创建一个对象
    // myObject 是一个指向 MyClass 实例的指针
    OC_MyClass *myObject = [[OC_MyClass alloc] init];
    myObject.name = @"Hello";
    // 手动释放对象(在MRC环境下),myObject 指向的内存已经被系统回收
    [myObject release];
   
    // 此时 myObject 是一个野指针,因为它指向的内存已经被释放,但指针本身仍然保留原来的地址值
    NSLog(@"Name: %@", myObject.name); // 访问野指针,可能导致崩溃(如 EXC_BAD_ACCESS 错误)
    //如何避免野指针
    myObject = nil;
}

内存泄漏

指一个对象没有被及时回收,在该回收的时候没有被回收,一直保留在内存中,直到程序结束时才回收。(循环引用 会造成这个问题)
单个对象的内存泄漏情况:
1)有对象的创建,没有对应的release
2)retain的次数和release的次数不匹配
3)在不适当的时候为指针赋值nil
4)在方法中为传入的对象不适当的retain
5)出现循环引用的情况,相互引用的对象没有被释放


自己写的 view 成员,应该用 weak 还是 strong?
  1. 懒加载的对象必须用strong的原因在于,如果使用weak,对象没有被没有被强引用,过了懒加载对象就会被释放掉。

  2. 我个人觉得应该用 strong,因为用 weak 并没有什么特别的优势,其实 weak 变量会有额外的系统维护开销的,如果你没有使用它的特别的理由,那么用 strong 的话应该更好。
    另外:如果你要做 Lazy 加载,那么你也只能选择用 strong。
    当然,如果你非要用 weak,其实也没什么问题,只需要注意在赋值前,先把这个对象用 addSubView 加到父 view 上,否则可能刚刚创建完,它就被释放了。

  3. Storyboard 拖线使用 weak,因为 拖线时,就已经 addSubView 加到父 view 上了


weak和assign的区别?
  • iOS开发中 weak和assign的区别
  • assin与weak的区别
    image.png

    /*
    weak:一般用于修饰控件
    如果指针指向的对象被销毁,那么 weak 会让这个指针也清空,ARC才有,不会让引用计数器+1
    assin:__unsafe_unretained(不安全的,不引用-不持有)
    如果指针指向的对象被销毁,但是 assign 并没有把指针清空,不会让引用计数器+1
    */


iOS中分类(category)和类扩展(Extension)的区别

一、 分类和类扩展区别

1.分类实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

2.Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

二、 分类为啥不能添加成员变量

struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods; // 对象方法列表
const struct _method_list_t *class_methods; // 类方法列表
const struct _protocol_list_t *protocols; // 协议列表
const struct _prop_list_t *properties; // 属性列表
};

1.从结构体可以知道,有属性列表,所以分类可以声明属性,但是分类只会生成该属性对应的get和set的声明,没有去实现该方法。
2.结构体没有成员变量列表,所以不能声明成员变量。

Category的加载处理过程

1.通过Runtime加载某个类的所有Category数据
2.把所有Category的方法、属性、协议数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面
3.将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

三、总结:

1、类别原则上只能添加方法而不能添加属性(能添加属性的原因只是通过runtime解决无setter/getter方法的问题而已,如果调用_成员变量,程序还是会报错)。
2、类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该变量默认是@private类型的。(所以作用范围只能在自身类,而不是子类或者其它地方)
3、类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的,这是因为类扩展是在编译阶段被添加到类中,而分类是在运行时添加到类中。
4、类扩展不能像类别那样拥有独立的实现部分(@implementation部分),和本类共享一个实现。也就是说,类扩展所声明的方法必须依托对应宿主类的实现部分来实现。

//动态添加属性
#import "Person+Nickname.h"
#import <objc/runtime.h>
@implementation Person (Nickname)
- (NSString *)nickname {
   return objc_getAssociatedObject(self, @selector(nickname));
}
- (void)setNickname:(NSString *)nickname {
   objc_setAssociatedObject(self, @selector(nickname), nickname, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end


KVC的底层实现?

当一个对象调用setValue方法时,方法内部会做以下操作:
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。

KVO内部实现原理

1.KVO是基于runtime机制实现的
2.当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
3.如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
4.每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
5.键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
6.补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类


什么是谓词?条件语句

谓词就是通过NSPredicate给定的逻辑条件作为约束条件,完成对数据的筛选。
//定义谓词对象,谓词对象中包含了过滤条件(过滤条件比较多)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];
//使用谓词条件过滤数组中的元素,过滤之后返回查询的结果
NSArray *array = [persons filteredArrayUsingPredicate:predicate];



————————————————————————————————
透明度的View(alpha),opaque的值应该设置为YES,可以优化渲染、提高性能。(当alpha值为0或1时,opaque的值对性能没有影响)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。