iOS之面试高频常问必会的题目整理

1. 简单介绍下NSURLConnection类及+ sendSynchronousRequest:returningResponse:error:– initWithRequest:delegate:两个方法的区别?

答: NSURLConnection主要用于网络访问,其中+ sendSynchronousRequest:returningResponse:error:是同步访问数据,即当前线程会阻塞,并等待request的返回的response,而– initWithRequest:delegate:使用的是异步加载,当其完成网络访问后,会通过delegate回到主线程,并其委托的对象。

2. 在项目什么时候选择使用GCD,什么时候选择NSOperation

答: 项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

3. ViewController的didReceiveMemoryWarning怎么被调用

答:[supper didReceiveMemoryWarning];

4. 写一个setter方法用于完成@property(nonatomic, retain) NSString *name,写一个setter方法用于完成@property(nonatomic, copy) NSString *name

- (void)setName:(NSString *)str{
    [str retain];
    [_name release];
    _name = str;
}
- (void)setName:(NSString *)str{
    id t = [str copy];
    [_name release];
    _name = t;
}

5. 对于语句NSString *obj = [[NSData alloc] init]; obj在编译时和运行时分别时什么类型的对象?

答: 编译时是NSString的类型;运行时是NSData类型的对象

6. Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?

答:线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:

7. 浅复制和深复制的区别?

答:浅层复制:只复制指向对象的指针,而不复制引用对象本身。
深层复制:复制引用对象本身。

8. PerformSelecter

当调用 NSObject 的performSelecter:afterDelay:后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用performSelector:onThread:时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

9. 优化你是从哪几方面着手?

一、首页启动速度
启动过程中做的事情越少越好(尽可能将多个接口合并)
不在UI线程上作耗时的操作(数据的处理在子线程进行,处理完通知主线程刷新节目)
在合适的时机开始后台任务(例如在用户指引节目就可以开始准备加载的数据)
二、页面浏览速度
json的处理(iOS 自带的NSJSONSerialization,Jsonkit,SBJson)
数据的分页(后端数据多的话,就要分页返回,例如网易新闻,或者 微博记录)
数据压缩(大数据也可以压缩返回,减少流量,加快反应速度)
内容缓存(例如网易新闻的最新新闻列表都是要缓存到本地,从本地加载,可以缓存到内存,或者数据库,根据情况而定)
延时加载tab(比如app有5个tab,可以先加载第一个要显示的tab,其他的在显示时候加载,按需加载)
算法的优化(核心算法的优化,例如有些app 有个 联系人姓名用汉语拼音的首字母排序)
三、操作流畅度优化
Tableview 优化(tableview cell的加载优化)
ViewController加载优化(不同view之间的跳转,可以提前准备好数据)
四、数据库的优化
数据库设计上面的重构
查询语句的优化
分库分表(数据太多的时候,可以分不同的表或者库)
五、服务器端和客户端的交互优化
客户端尽量减少请求
服务端尽量做多的逻辑处理
服务器端和客户端采取推拉结合的方式(可以利用一些同步机制)
通信协议的优化(减少报文的大小)
电量使用优化(尽量不要使用后台运行)
六、非技术性能优化
产品设计的逻辑性(产品的设计一定要符合逻辑,或者逻辑尽量简单,否则会让程序员抓狂,有时候用了好大力气,才可以完成一个小小的逻辑设计问题)
界面交互的规范(每个模块的界面的交互尽量统一,符合操作习惯)
代码规范(这个可以隐形带来app 性能的提高,比如 用if else 还是switch ,或者是用!还是 ==)
code review(坚持code Review 持续重构代码。减少代码的逻辑复杂度)

10. 什么情况使用 weak 关键字,相比 assign 有什么不同?

1.在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,如自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
IBOutlet连出来的视图属性为什么可以被设置成weak?
答:因为父控件的subViews数组已经对它有一个强引用。
不同点
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)

11. 用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

答:用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
1.因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
2.如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。

12. runtime如何实现weak变量的自动置nil?

runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为nil

13. runloop是什么/runloop的概念?

runloop是线程相关的基础框架的一部分。一个runloop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。其实内部就是do-while循环,这个循环内部不断地处理各种任务(比如Source,Timer,Observer)。使用runloop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。

14. UITableViewCell上有个UILabel,显示NSTimer实现的秒表时间,手指滚动cell过程中,label是否刷新,为什么?

这是否刷新取决于timer加入到Run Loop中的Mode是什么。Mode主要是用来指定事件在运行循环中的优先级的,分为

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
  • UITrackingRunLoopMode:ScrollView滑动时会切换到该Mode
  • UIInitializationRunLoopMode:run loop启动时,会切换到该mode
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
    苹果公开提供的Mode有两个
  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes)
    在编程中:如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。当我们滚动的时候,也希望不调度,那就应该使用默认模式。但是,如果希望在滚动时,定时器也要回调,那就应该使用common mode。

15. NStimer准吗?谈谈你的看法?如果不准该怎样实现一个精确的NSTimer?

不准;不准的原因如下
1、NSTimer加在main runloop中,模式是NSDefaultRunLoopMode,main负责所有主线程事件,例如UI界面的操作,复杂的运算,这样在同一个runloop中timer就会产生阻塞。
2、模式的改变。主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。
当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个ScrollView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。所以就会影响到NSTimer不准的情况。
PS:DefaultMode 是 App 平时所处的状态,rackingRunLoopMode 是追踪 ScrollView 滑动时的状态。
方法:
1、在主线程中进行NSTimer操作,但是将NSTimer实例加到main runloop的特定mode(模式)中。避免被复杂运算操作或者UI界面刷新所干扰self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
2、在子线程中进行NSTimer的操作,再在主线程中修改UI界面显示操作结果
-(void)timerMethod2 {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
-(void)newThread{
@autoreleasepool{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}

16. NSOperation 相比于 GCD 有哪些优势?

GCD是基于c的底层api,NSOperation属于object-c类。ios 首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用gcd实现的。
相对于GCD:
1、NSOperation拥有更多的函数可用,具体查看api。
2、在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
3、有kvo可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)。
4、NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
GCD主要与block结合使用。代码简洁高效。
GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。
具体使用哪个,依需求而定。 从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。

17. 如何访问并修改一个类的私有属性?

有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性。

18. 如何捕获异常?

1. 在app启动时(didFinishLaunchingWithOptions),添加一个异常捕获的监听
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
2. 实现捕获异常日志并保存到本地的方法
void UncaughtExceptionHandler(NSException *exception){
    //异常日志获取
    NSArray  *excpArr = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSString *excpCnt = [NSString stringWithFormat:@"exceptionType: %@ \n reason: %@ \n stackSymbols: %@",name,reason,excpArr];
    //日常日志保存(可以将此功能单独提炼到一个方法中)
    NSArray  *dirArr  = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *dirPath = dirArr[0];
    NSString *logDir = [dirPath stringByAppendingString:@"/CrashLog"];

    BOOL isExistLogDir = YES;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:logDir]) {
        isExistLogDir = [fileManager createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:nil];
    }
    if (isExistLogDir) {
        //此处可扩展
        NSString *logPath = [logDir stringByAppendingString:@"/crashLog.txt"];
        [excpCnt writeToFile:logPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    }
}

19. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?

答:Object-c的类不可以多重继承;可以实现多个接口,通过实现多个接口可以完成C++的多重继承;Category是类别,一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。

20. Category(分类),Extension(扩展)和继承的区别

答:1.分类
category原则上只能在现有类基础上添加新的方法(能添加属性的原因只是通过runtime解决无setter/getter的问题而已),类别中的方法没被实现编译器是不会有任何警告的,这是因为类别是在运行时添加到类中的
2.扩展
iOS中的extension就是匿名的分类,只有头文件没有实现文件。类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(使用范围只能在自身类,而不是子类或其他地方),类扩展中声明的方法没被实现,编译器会报警,这是因为类扩展是在编译阶段被添加到类中的
3.继承
在iOS中继承是单继承,既只能有一个父类。在继承中,子类可以使用父类的方法和变量,当子类想对本类或者父类的变量进行初始化,那么需要重写init()方法 。父类也可以访问子类的方法和成员变量

21. 简述内存分区情况

1).代码区:存放函数二进制代码
2).数据区:系统运行时申请内存并初始化,系统退出时由系统释放。存放全局变量、静态变量、常量
3).堆区:通过malloc等函数或new等操作符动态申请得到,需程序员手动申请和释放
4).栈区:函数模块内申请,函数结束时由系统自动释放。存放局部变量、函数参数

22. 直接调用_objc_msgForward函数将会发生什么?

_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
一旦调用_objc_msgForward,将跳过查找 IMP 的过程,直接触发“消息转发”,如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:“我没有在这个对象里找到这个方法的实现”

23. 对于Run Loop的理解

  • RunLoop,是多线程的法宝,即一个线程一次只能执行一个任务,执行完任务后就会退出线程。主线程执行完即时任务时会继续等待接收事件而不退出。非主线程通常来说就是为了执行某一任务的,执行完毕就需要归还资源,因此默认是不运行RunLoop的;
  • 每一个线程都有其对应的RunLoop,只是默认只有主线程的RunLoop是启动的,其它子线程的RunLoop默认是不启动的,若要启动则需要手动启动;
  • 在一个单独的线程中,如果需要在处理完某个任务后不退出,继续等待接收事件,则需要启用RunLoop;
  • NSRunLoop提供了一个添加NSTimer的方法,可以指定Mode,如果要让任何情况下都回调,则需要设置Mode为Common模式;
  • 实质上,对于子线程的runloop默认是不存在的,因为苹果采用了懒加载的方式。如果我们没有手动调用[NSRunLoop currentRunLoop]的话,就不会去查询是否存在当前线程的RunLoop,也就不会去加载,更不会创建。

24. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

1.每一个类对象中都一个对象方法列表(对象方法缓存)
2.类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
3.方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
4.当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
5.当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找

25. runtime 中,SEL 和 IMP 的区别

方法名 SEL – 表示该方法的名称;
IMP – 指向该方法的具体实现的函数指针,说白了IMP就是实现方法。

26.block底层实现

block本质是指向一个结构体的一个指针
运行时机制 比较高级的特性 纯C语言
平时写的OC代码 转换成C语言运行时的代码
指令:clang -rewrite-objc main.m(可以打印验证)
默认情况下,任何block都是在栈里面的,随时可能被回收
只要对其做一次copy操作 block的内存就会放在堆里面 不会释放
只有copy才能产生一个新的内存地址 所有地址会发生改变

27. TCP协议三次握手

TCP协议采用了三次握手策略。用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,它一定会向对方确认是否成功送达。握手过程中使用了TCP的标志——SYN(synchronize)和ACK(acknowledgement)。发送端首先发送一个带SYN标志的数据包给对方。接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息。最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束。
TCP协议三次握手示意图

28. @property 的本质是什么?

@property = ivar + getter + setter;
“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)

29. KVC的底层实现?

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

30. ViewController生命周期

按照执行顺序排列:
1). initWithCoder:通过nib文件初始化时触发。
2). awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
3). loadView:开始加载视图控制器自带的view。
4). viewDidLoad:视图控制器的view被加载完成。
5). viewWillAppear:视图控制器的view将要显示在window上。
6). updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7). viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8). viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9). viewDidAppear:视图控制器的view已经展示到window上。
10). viewWillDisappear:视图控制器的view将要从window上消失。
11). viewDidDisappear:视图控制器的view已经从window上消失。

31. 如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
// 创建队列组
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(), ^{
        // 合并图片
});

32. dispatch_barrier_async(栅栏函数)的作用是什么?

函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
作用:
	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 由于是并行处理先后顺序不定。

33.autoreleasePool的数据结构

简单说是双向链表,每张链表头尾相接,有parent、child指针,每创建一个池子,会在首部创建一个哨兵对象作为标记,最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表

  autoreleasePool的实现原理

autoreleasePool是一个延时release的机制,在自动释放池被销毁或耗尽时,会向池中的所有对象发送release消息,释放所有autorelease对象
autoreleasePool并没有单独的结构,而是由若干个autoreleasePoolPage作为结点以双向链表的形式组合而成

  1. 每一个指针代表一个加入到释放池的对象或者是哨兵对象,哨兵对象是在@autoreleasepool{}构建的时候插入的
  2. 当自动释放池pop的时候,所有哨兵对象之后的对象都会release
  3. 链表会在一个Page空间占满时进行增加,一个autoreleasePoolPage的空间被占满时,会新建一个autoreleasePoolPage对象连接链表,后来的autorelease对象在新的page加入

34.一个NSObject对象占用多少内存

一个指针变量所占用的大小,64bit8个字节,32bit4个字节

35.对象的isa指针指向哪里

instance对象的isa指针指向class对象,class对象的isa指针指向meta-class对象,meta-class对象的isa指针指向基类的meta-class对象,基类自己的isa指针也指向自己

36.OC的类信息存放在哪里

成员变量的具体值存放在instance对象,对象方法、协议、属性、成员变量信息存放在class对象,类方法信息存放在meta-class对象

37.为什么使用runtime技术中的关联对象可以为类别添加属性

关联对象都由AssociationsManager管理,AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而mapkey是这个对象的指针地址,而这个mapvalue又是另外一个AssociationsHashMap,里面保存了关联对象的kv

38.用过哪些锁?哪些锁的性能比较高?谈下Objective-C都有哪些锁机制,你一般用哪个

常用的锁有NSLock、@synchronized代码块、信号量 dispatch_semaphore_t

os_unfair_lock(推荐)
dispatch_semaphore(推荐)
pthread_mutex(推荐)
dispatch_queue(DISPATCH_QUEUE_SERIAL)(推荐)
NSLock()
NSCondition()
@synchronized(最不推荐)

信号量性能最高
@synchronized代码块最方便

39.为什么一定要在主线程里面更新UI

UIKit不是线程安全的,容易产生UI更新上的混乱

40.类和结构体的区别

类是引用类型,结构体是值类型。结构体变量分配在栈,OC对象分配在堆。结构体只能封装属性,类不仅可以封装属性也可以封装方法

41.引用和指针的区别

指针的本质也就是变量,它不仅有自己的地址,也有它所存放的值,只不过这个值是地址而已
引用也就是指针常量,它是一个对象的别名,既然初始化了所指向的地址,那么它一定不为空,而且地址不可变
引用必须被初始化,指针不必
引用初始化以后不能被改变,指针可以改变所指的对象
不存在指向空值的引用,但存在指向空值的指针

42.id类型、nilNilNULLNSNULL的区别

id类型是一个独特的数据类型,可以转换为任何数据类型,id类型的变量可以存放任何数据类型的对象,id声明的对象具有运行时特性,可以指向任意类型的对象
nil是一个实例对象值,如果我们要把一个对象设置为空的时候就用nil
Nil是一个类对象的值,如果我们要把一个class的对象设置为空的时候就用Nil
NULL指向基本数据类型的空指针C语言的变量的指针为空
NSNull是一个对象,它用在不能使用nil的场合

43.weak的底层实现的原理是什么

weak表其实是一个hash哈希表,Key是所指对象的地址,Valueweak指针的地址数组
runtime维护了一个weak表,用于存储指向某个对象的所有weak指针,weak表其实是一个hash表,Key是所指对象的地址,valueweak指针的地址数组
为什么value是数组?因为一个对象可能被多个弱引用指针指向

44.weak原理实现步骤

weak的实现原理可概括三步

  1. 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址
  2. 添加引用时:objc_initWeak函数会调用objc_storeWeak()函数,objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表
  3. 释放时,调用clearDeallocating函数,clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entryweak表中删除,最后清理对象的记录

45.编译过程做了哪些事情

Objective、Swift都是编译语言。编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率较高
Objective、Swift二者的编译都是依赖于Clang + LLVM
不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端

  • 编译器前端:编译器前端的任务是进行语法、语义分析,生成中间代码。在这个过程中会进行类型检查,如果发现错误或者警告会标注出来在哪一行
  • 编译器后端:编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。LLVM优化器会进行BitCode的生成,链接器优化等等,LLVM机器码生成器会针对不同的架构,比如arm64等生成不同的机器码

46.Linux命令之-Strings

strings命令是在对象文件或者二进制文件中查找可打印的字符串,常用来在二进制文件中查找字符串,与grep配合使用,例如一个用法就是在编译的so中定义字符串常量作为动态库的版本号,然后就可以使用strings+grep组合命令查看当前编译的so的版本号了。输入strings -h查看strings命令的用法,下面为具体用法实例
strings /lib/tls/libc.so.6 | grep GLIBC
strings /Users/y**ar/Desktop/Demo2/Libraries/libiPhone-lib.a | grep "UIWebView"
cat /Users/y**ar/Desktop/Demo2/Libraries/libiPhone-lib.a

47.OC格式化打印

%d 整数
%02d 表示不足2位补0
%u 无符号整型
%f   浮点(双字节)
%.2f 精度浮点数,只保留两位小数
%x,%X   十六进制整数
%o   八进制整数
%p   指针
%s   C字符串
%c   字符

Swift

1、Swift中struct和class有什么区别?

struct是值引用,更轻量,存放于栈区,class是类型引用,存放于堆区。struct无法继承,class可继承。

2、Swift中的方法调用有哪些形式?

答:直接派发、函数表派发、消息机制派发。派发方式受声明位置,引用类型,特定行为的影响。为什么Swift有这么多派发形式?为了效率。

参考文章:深入理解 Swift 派发机制

3、Swift和OC有什么区别?

Swift和OC的区别有很多,这里简要总结这几条:

 SwiftObjective-C
语言特性静态语言,更加安全动态语言,不那么安全
语法更精简冗长
命名空间
方法调用直接调用,函数表调用,消息转发消息转发
泛型/元组/高阶函数
语言效率性能更高,速度更快略低
文件特性.swift 单文件.h/.m包含头文件
编程特性可以更好的实现函数式编程/响应式编程面向对象编程

4、从OC向Swift迁移的时候遇到过什么问题?

可以参考这篇文章:OC项目转Swift指南 里的混编注意事项。

5、怎么理解面向协议编程?

面向对象是以对象的视角观察整体结构,万物皆为对象。

面向协议则是用协议的方式组织各个类的关系,Swift底层几乎所有类都构建在协议之上。

面向协议能够解决面向对象的菱形继承,横切关注点和动态派发的安全性等问题。

参考喵神的面向协议编程与 Cocoa 的邂逅 (上)

OC语法

1、Block是如何实现的?Block对应的数据结构是什么样子的?__block的作用是什么?它对应的数据结构又是什么样子的?

block本质是一个对象,底层用struct实现。

数据结构如下:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
  • isa 指针,所有对象都有该指针,用于实现对象相关的功能。

  • flags,用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用。

  • reserved,保留变量。

  • invoke,函数指针,指向具体的 block 实现的函数调用地址。

  • descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。

  • variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

__block的作用是可以获取对应变量的指针,使其可以在block内部被修改。通过反编译的代码我们可以看到该对象是这样的:

struct __Block_byref_i_0 {
    void *__isa;
    __Block_byref_i_0 *__forwarding;
    int __flags;
    int __size;
    int val; //变量名
};

对于block的深入了解,可以参考《Objective-C高级编程》第二章或者唐巧的这篇谈Objective-C block的实现

2、GCD中的Block是在堆上还是栈上?

堆上。可以通过block的isa指针确认。

3、NSCoding协议是干什么用的?

一种编码协议,归档时和解档时需要依赖该协议定义的编码和解码方法。Foundation和Cocoa Touch中的大部分类都遵循了这个协议,一般被NSKeyedArchiver做自定义对象持久化时使用。

4、KVO的实现原理

利用Runtime生成一个中间对象,让原对象的isa指针指向它,然后重写setter方法,插入willChangeValueForKey和didChangeValueForKey方法。当属性变化时会调用,会调用这两个方法通知到外界属性变化。

5、NSOperation有哪些特性,比着GCD有哪些优点,它有哪些API?

NSOperation是对GCD的封装,具有面向对象的特点,可以更方便的进行封装,可以设置依赖关系。

API可以查看NSOperation文档。

6、NSNotificaiton是同步还是异步的,如果发通知时在子线程,接收在哪个线程?

同步。子线程。

UI

1、事件响应链是如何传递的?

手势的点击会发生两个重要事情,事件传递和事件响应。

事件传递:从UIApplication开始,到window,再逐步往下层(子视图)找,直到找到最深层的子视图,其为first responder。用到的判断方法是pointInside:withEventhitTest:withEvent

事件响应:从识别到的视图(first responder)开始验证能否响应事件,如果不能就交给其上层(父视图)视图,如果能相应将不再往下传递,如果直到找到UIApplication层还没有相应,那就忽略该次点击。用到的判断方法是touchesBegan:withEventtouchesMoved:withEvent等。

这两个过程大致的相反的。

2、什么是异步渲染?

异步渲染就是在子线程进行绘制,然后拿到主线程显示。

UIView的显示是通过CALayer实现的,CALayer的显示则是通过contents进行的。异步渲染的实现原理是当我们改变UIView的frame时,会调用layer的setNeedsDisplay,然后调用layer的display方法。我们不能在非主线程将内容绘制到layer的context上,但我们单独开一个子线程通过CGBitmapContextCreateImage()绘制内容,绘制完成之后切回主线程,将内容赋值到contents上。

这个步骤可以参照YYText中YYTextAsyncLayer.m文件中的实现方式。

3、layoutsubviews是在什么时机调用的?

  • init初始化不会触发。

  • addSubview时。

  • 设置frame且前后值变化,frame为zero且不添加到指定视图不会触发。

  • 旋转Screen会触发父视图的layoutSubviews。

  • 滚动UIScrollView引起View重新布局时会触发layoutSubviews。

4、一张图片的展示经历了哪些步骤?

这个可以参考我之前写的一篇文章iOS开发图片格式选择 中的前半部分内容。

5、什么是离屏渲染,什么情况会导致离屏渲染?

如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer,作为像素数据存储区域。如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。

以阴影为例,为什么它会导致离屏渲染。因为GPU的渲染是遵循“画家算法”,一层一层绘制的,但阴影很特殊,它需要全部内容绘制完成,再根据外轮廓进行绘制。这就导致了,阴影这一层要一直占据一块内存区域,这就导致了离屏渲染。

类似导致离屏渲染的情况还有:

  • cornerRadius+clipsToBounds
  • group opacity 组透明度
  • mask 遮罩
  • UIBlurEffect 毛玻璃效果

有一篇文章详细的讨论了这些情况:关于iOS离屏渲染的深入研究

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

6、CoreAnimation这个框架的作用什么,它跟UIKit的关系是什么?

CoreAnimation虽然直译是核心动画,但它其实是一个图像渲染框架,动画实现只是它的一部分功能。

看这张图我们可以知道,它是UIKit和AppKit的底层实现,位于Metal、Core Graphics和GPU之上之上。

苹果官方文档:About Core Animation

引用计数

1、ARC方案的原理是什么?它是在什么时候做的隐式添加release操作?

ARC(Automatic Reference Cunting)自动引用计数,意即通过LLVM编译器自动管理对应的引用计数状态。ARC开启时无需再次键入retain或者release代码。

它是在编译阶段添加retain或者release代码的。

2、循环引用有哪些场景,如何避免?

循环引用及两个及以上对象出现引用环,导致对象无法释放的情况。一般在block,delegate,NSTimer时容易出现这个问题。

解决方案就是让环的其中一环节实现弱引用。

3、为什么当我们在使用block时外面是weak 声明一个weakSelf,还要在block内部使用strong再持有一下?

block外界声明weak是为了实现block对对象的弱持有,而里面的作用是为了保证在进到block时不会发生释放。

4、Autoreleasepool是实现机制是什么?它是什么时候释放内部的对象的?它内部的数据结构是什么样的?当我提到哨兵对象时,会继续问哨兵对象的作用是什么,为什么要设计它?

Autoreleasepool的原理是一个双向列表,它会对加入其中的对象实现延迟释放。当Autoreleasepool调用drain方法时会释放内部标记为autorelease的对象。

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

哨兵对象类似一个指针,指向自动释放池的栈顶位置,它的作用就是用于标记当前自动释放池需要释放内部对象时,释放到那个地方结束,每次入栈时它用于确定添加的位置,然后再次移动到栈顶。

关于自动释放池的底层探究可以看draveness的这篇自动释放池的前世今生 ---- 深入解析 autoreleasepool

5、哪些对象会放入到Autoreleasepool中?

有两种情况生成的对象会加入到autoreleasepool中:

  • 非alloc/new/copy/mutablecopy 开始的方式初始化时。
  • id的指针或对象的指针在没有显示指定时

引用计数带来的一次讨论

6、weak的实现原理是什么?当引用对象销毁是它是如何管理内部的Hash表的?(这里要参阅weak源码)

runTime会把对weak修饰的对象放到一个全局的哈希表中,用weak修饰的对象的内存地址为key,weak指针为值,在对象进行销毁时,用通过自身地址去哈希表中查找到所有指向此对象的weak指针,并把所有的weak指针置位nil。

Runtime

1、消息发送的流程是怎样的?

OC中的方法调用会转化成给对象发送消息,发送消息会调用这个方法:

objc_msgSend(receiver, @selector(message))

该过程有以下关键步骤:

  • 先确定调用方法的类已经都加载完毕,如果没加载完毕的话进行加载

  • 从cache中查找方法

  • cache中没有找到对应的方法,则到方法列表中查,查到则缓存

  • 如果本类中查询到没有结果,则遍历所有父类重复上面的查找过程,直到NSObject

2、关联对象时什么情况下会导致内存泄露?

关联对象可以理解就是持有了一个对象,如果是retain等方式的持有,而该对象也持有了本类,那就是导致了循环引用。

3、消息转发的流程是什么?

消息转发是发生在接收者(receiver)没有找到对应的方法(method)的时候,该步骤有如下几个关键步骤:

  • 消息转发的时候,如果是实例方法会走resolveInstanceMethod:,如果是类方法会走resolveClassMethod:,它们的返回值都是Bool,需要我们确定是否进行转发。
  • 如果第一步返回YES,确定转发就会进到下个方法forwardingTargetForSelector,这个方法需要我们指定一个被用receiver。
  • methodSignatureForSelector用于指定方法签名,forwardInvocation用于处理Invocation,进行完整转发。
  • 如果消息转发也没有处理即为无法处理,会调用doesNotRecognizeSelector,引发崩溃。

更多了解可以参考iOS开发·runtime原理与实践: 消息转发篇(Message Forwarding) (消息机制,方法未实现+API不兼容奔溃,模拟多继承)

4、category能否添加属性,为什么?能否添加实例变量,为什么?

可以添加属性,这里的属性指@property,但跟类里的@property又不一样。正常的@property为:实例变量Ivar + Setter + Getter 方法,分类里的@property这三者都没有,需要我们手动实现。

分类是运行时被编译的,这时类的结构已经固定了,所以我们无法添加实例变量。

对于分类自定义Setter和Getter方法,我们可以通过关联对象(Associated Object)进行实现。

5、元类的作用是什么?

元类的作用是存储类方法,同时它也是为了让OC的类结构能够形成闭环。

对于为甚设计元类有以下原因;

  • 在OC的世界里一切皆对象(借鉴于Smalltalk),metaclass的设计就是要为满足这一点。

  • 在OC中Class也是一种对象,它对应的类就是metaclassmetaclass也是一种对象,它的类是root metaclass,在往上根元类(root metaclass)指向自己,形成了一个闭环,一个完备的设计。

如果不要metaclass可不可以?也是可以的,在objc_class再加一个类方法指针。但是这样的设计会将消息传递的过程复杂化,所以为了消息传递流程的复用,为了一切皆对象的思想,就有了metaclass。

关于这一话题的深入讨论可以参考这两篇文章:

为什么要存在MetaClass

为什么要设计metaclass

6、类方法是存储到什么地方的?类属性呢?

类方法和类属性都是存储到元类中的。

类属性在Swift用的多些,OC中很少有人用到,但其实它也是有的,写法如下:

@interface Person : NSObject
// 在属性类别中加上class
@property (class, nonatomic, copy) NSString *name;
@end
// 调用方式
NSString *temp = Person.name;

需要注意的是跟实例属性不一样,类属性不会自动生成实例变量和setter,getter方法,需要我们手动实现。具体实现方法可以参考这个文章:Objective-C Class Properties

7、讲几个runtime的应用场景

  • hook系统方法进行方法交换。
  • 了解一个类(闭源)的私有属性和方法。
  • 关联对象,实现添加分类属性的功能。
  • 修改isa指针,自定义KVO。

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

Runloop

1、讲一下对Runloop的理解?

Runloop就是一个运行循环,它保证了在没有任务的时候线程不退出,有任务的时候即使响应。Runloop跟线程,事件响应,手势识别,页面更新,定时器都有着紧密联系。

深入了解推荐ibireme的这篇深入理解RunLoop

2、可以用Runloop实现什么功能?

  • 检测卡顿
  • 线程保活
  • 性能优化,将一些耗时操作放到runloop wait的情况处理。

性能优化

1、对TableView进行性能优化有哪些方式?

  • 缓存高度
  • 异步渲染
  • 减少离屏渲染

2、Xcode的Instruments都有哪些调试的工具?

  • Activity Monitor(活动监视器):监控进程的CPU、内存、磁盘、网络使用情况。是程序在手机

    运行真正占用内存大小

  • Allocations(内存分配):跟踪过程的匿名虚拟内存和堆的对象提供类名和可选保留/释放历史

  • Core Animation(图形性能):显示程序显卡性能以及CPU使用情况

  • Core Data:跟踪Core Data文件系统活动

  • Energy Log:耗电量监控

  • File Activity:检测文件创建、移动、变化、删除等

  • Leaks(泄漏):一般的措施内存使用情况,检查泄漏的内存,并提供了所有活动的分配和泄漏模块的类对象分配统计信息以及内存地址历史记录

  • Network:用链接工具分析你的程序如何使用TCP/IP和UDP/IP链接

  • System Usage:记录关于文件读写,sockets,I/O系统活动,输入输出

  • Time Profiler(时间探查):方法执行耗时分析

  • Zombies:测量一般的内存使用,专注于检测过度释放的野指针对象。也提供对象分配统计以及主动分配的内存地址历史

3、讲一下你做过的性能优化的事情。

这个根据自己情况来说吧。

4、如何检测卡顿,都有哪些方法?

  • FPS,通过CADisplayLink计算1s内刷新次数,也可以利用Instruments里的Core Animation。
  • 利用Runloop,实时计算 kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting 两个状态区域之间的耗时是否超过某个阀值
  • 子线程检测,每次检测时设置标记位为YES,然后派发任务到主线程中将标记位设置为NO。接着子线程沉睡超时阙值时长,判断标志位是否成功设置成NO,如果没有说明主线程发生了卡顿。参考ANREye的实现

5、缩小包体积有哪些方案?

  • 图片压缩,无用图片删除
  • 一些大图可以动态下发
  • 删除无用类,无用方法
  • 减少三方库的依赖

计算机相关

1、项目编译的流程是什么?手机上的应用程序自点击图标开始到首屏内容展示都经历了哪些步骤?

编译流程:

  • 预处理:处理宏定义,删除注释,展开头文件。

  • 词法分析:把代码切成一个个token,比如大小括号等于号还有字符串

  • 语法分析:验证语法是否正确,合成抽象语法树AST

  • 静态分析:查找代码错误

  • 类型检查:动态和静态

  • 目标代码的生成与优化,包括删除多余指令,选择合适的寻址方式,如果开启了bitcode,会做进一步的优化

  • 汇编:由汇编器生成汇编语言

  • 机器码:由汇编语言转成机器码,生成.o文件

应用启动的流程:

启动的前提是完成编译,运行程序即运行编译过后的目标程序,它分为main函数前和main函数后:

main前

  • 加载可执行文件(App的.o文件集合)

  • 加载动态链接库(系统和应用的动态链接库),进行rebase指针调整和bind符号绑定

  • Objc运行时的初始处理,包括Objc相关类的注册,category注册,selector唯一性检查

  • 初始化,包括执行+load()、attribute(constructor)修饰的函数的调用、创建C++静态全局变量

main后

  • 首页初始化所需要配置文件的读写操作

  • 首页界面渲染

2、对于基本数据类型,一般是存储到栈中的,它有没有可能存在堆上,什么情况下会存储到堆上?

栈和堆都是同属一块内存,只不过一个是高地址往低地址存储,一个从低地址往高地址存储,他们并没有严格的界限说一个值只能放在堆上或者栈上。所以基本数据类型也是可以存储到堆上的。

当该基础类型变量被__block捕获时,该变量连同block都会被copy到堆上。

3、数据库中的事务是什么意思?

事务就是访问并操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行。如果其中一个步骤出错就要撤销整个操作,回滚到进入事务之前的状态。

4、使用过什么数据库(我回答的Sqlite,Realm),Realm在使用时有哪些注意事项,如何实现批量操作?

对于Realm感兴趣的同学可以看下其官方文档

Realm需要注意的主要就是不能直接跨线程访问同一对象。

批量操作可以在一个单独的事务中执行多个数据库的修改。

5、LRU算法是否了解,如何实现一套LRU算法?

LRU(Least recently used 最近最少使用)算法是一个缓存淘汰算法,其作用就是当缓存很多时,该淘汰哪些内容,见名知意,它的核心思想是淘汰最近使用最少的内容。实现它的关键步骤是:

  • 新数据插入到链表的头部

  • 每当缓存命中时,则将数据移动到链表头部

  • 链表满时,将尾部数据清除

这个算法在SDWebImage和Kingfisher等需要处理缓存的库中都有实现。

6、知道哪些设计模式,怎么理解设计模式的作用?

工厂模式、观察者模式、中介者模式、单例模式。这个根据实际情况说吧。

7、如果有1000万个Int类型的数字,如何对他们排序?

这里的隐藏含义是,内存不够用时如何排序,还有一个隐藏含义是硬盘足够大。这是可以采用分而治之的方法,将数据分成若干块,使每一小块满足当前内容大小,然后对每块内容单独排序,最后采用归并排序对所有块进行排序,就得到了一个有序序列。

8、设计一套数据库方案,实现类似微信的搜索关键词能快速检索出包含该字符串的聊天信息,并展示对应数量(聊天记录的数据量较大)

可以对聊天记录的文本值加上索引。正常情况下数据库搜索都是全量检索的,加上索引之后只会检索满足条件的记录,大大降低检索量。

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

简历相关问题

1、Lottie实现动画效果的原理是什么?

iOS里的动画基本都是基于CoreAnimation里的API实现的,Lottie也是如此。在AE上实现动画效果,通过插件导出对应的json文件,Lottie的库解析该json,转成对应的系统API方法。图片的引用可以使用Base64编到json里,也可以通过项目集成,通过路径引用。

2、OClint实现静态分析的原理是什么,它是如何做到的?

具体可以参考我之前写的如何通过静态分析提高iOS代码质量

3、MVVM和MVC有什么区别?

对比架构时,可以从是否职责分离,可测试性,可易维护性三个维度对比。

更多对比可以参考我翻译的一篇文章:【译】iOS 架构模式–浅析MVC, MVP, MVVM 和 VIPER

4、静态库和动态库的区别是什么?

静态库:链接时被完整复制到可执行文件中,多次使用就多份拷贝。

动态库:链接时不复制,而是由系统动态加载到内存,内存中只会有一份该动态库。

5、了解Flutter吗?它有没有使用UIKit?它是如何渲染UI的?

UIKit是基于CoreAnimation渲染的,而Flutter并没有用到它,而是自己基于C++实现了一套渲染框架。

6、二进制重排的核心依据是什么?

修改链接顺序,减少启动时的缺页中断。

实践步骤可以参考李斌同学的这篇iOS 优化篇 - 启动优化之Clang插桩实现二进制重排

7、如何设计一套切换主题的方案?

核心思路是观察者模式+协议(通知),当获取到主题切换时,通知各个实现了主题协议的类进行更新。

8、AVPlayer和IJKPlayer有什么区别?用IJKPlayer如何实现一个缓存视频列表每条视频前1s的内容?

因为对IJKPlayer和FFmpeg了解的不是很深,这个我也没有确切答案,如果有了解的小伙伴可以评论告知我。

9、类似微博的短视频列表,滑动停留播放,如何实现?

这个主要就是检测contentOffset和屏幕中间位置,设置一些边界条件,处理滑动过程中的切换行为。

10、使用python做过哪些事?如何理解脚本语言?

多语言管理,csv多语言文件读取,然后写入到项目Localizable.strings中;抓取项目中的多语言字符串。

脚本(script) 其实就是一系列指令,计算机看了指令就知道自己该做什么事情。像常见的Python,Shell,Ruby都是脚本语言,他们通常不需要编译,通过解释器运行。

数据结构与算法

1、什么是Hash表,什么是Hash碰撞,解决Hash碰撞有什么方法?

哈希表(Hash Table,也叫散列表),是根据关键码值 (Key-Value) 而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。我们常用的Dictionary就是一种Hash表。

那什么是Hash碰撞呢,我们知道Hash表的查找是通过键值进行定位的,当两个不同的输入对应一个输出时,即为Hash碰撞,也被称为Hash冲突。

如果使用字典的例子你可能联想不到冲突的情况,我们假设另一种情况:假设hash表的大小为9(即有9个槽),现在要把一串数据存到表里:5,28,19,15,20,33,12,17,10。我们使用的hash函数是对9取余。这样的话会出现hash(5)=5,hash(28)=1,hash(19)=1。28和19都对应一个地址,这就出现了Hash冲突。

解决Hash冲突的方式有开放定址法和链地址法。

2、如何遍历二叉树?

二叉树的遍历有三种方式,对于上面这棵二叉树,他们的遍历结果为:

前序遍历:根节点 > 左子节点 > 右子节点。

10,6,4,8,14,12,16

中序遍历:左子节点 > 根节点 > 右子节点。

4,6,8,10,12,14,16

后序遍历:左子节点 > 右子节点 > 根节点。

4,8,6,12,16,14,10

3、简述下快速排序的过程,时间复杂度是多少?

快排的思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。

一个简单的Swift实现方式如下:

func quicksort<T: Comparable>(_ a: [T]) -> [T] {
  guard a.count > 1 else { return a }

  let pivot = a[a.count/2]
  let less = a.filter { $0 < pivot }
  let equal = a.filter { $0 == pivot }
  let greater = a.filter { $0 > pivot }

  return quicksort(less) + equal + quicksort(greater)
}

快速排序是有好几种的,他们的区别在于如何实现filter和分区基准值的选取。

快排的时间复杂度是O(nlogn),空间复杂度是O(logn)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值