腾讯面试试题总结

项目处理

1、介绍一下实习的项目,任务分工做了哪些工作?介绍实习内容?

2、项目里面的数据存储都用了哪些?知道iOS里面有哪些数据存储方法?什么时候该用哪些方法存储?

3、有A B C D E五个类,都要监听登录和注册的通知,发通知给它们五个控制器麻烦,就写了一个单例类,负责发通知,需要接受通知的就实现delegate,因为单例类里面的数组引用了这几个VC,导致不释放,怎么解决?类似NSTimer,不知道什么时机移除VC(可以从这方面考虑:NSNotificationCenter,直接在dealloc里面remove掉就行)因为VC被引用,所以不会执行到dealloc方法,先看下NSNotificationCenter是怎么实现的吧

4、发布出去的版本,怎么收集crash日志?不使用bugly等第三方平台或者这些第三方平台是怎么收集crash日志的?

5、解决过的最精彩的crash问题?

6、项目里面遇到过死锁吗?怎么解决?数据库访问本来就是线程安全的,不会造成死锁啊。什么是死锁?

7、WWDC2016公布了哪些新特性?对苹果系列的最新特性有关注吗?

8、为什么做iOS开发,不做安卓开发?

9、你最出色的是哪方面?iOS开发,iOS开发的哪一方面?

10、一般开始做一个项目,你的架构是如何思考的?

答:参考一  ;参考二

11、容错处理你们一般是注意哪些?

答:在团队协作开发中,由于每个团队成员的水平不一,很难控制代码的质量,保证代码的健壮性,经常会发生由于后台返回异常数据造成APP崩溃闪退的情况,为了避免这样的情况项目中做一些容错处理,显得格外重要,极大程度上降低了因为数据容错不到位产生崩溃闪退的概率。例如:字典,数组,野指针,NSNull等等。

12、如果项目开始容错处理没做,如何防止拦截潜在的崩溃?

答:(1)category给类添加方法用来替换掉原本存在潜在的崩溃的方法;(2)利用runtime方法交换技术,将系统方法替换成类添加新的方法;(3)利用异常的捕获来防止程序的崩溃,并且进行相应的处理。

13、简述push原理,push的证书和其它的右什么不一样?

14、iOS crash后的调试方法?还有用户的错误日志,如何找到出错的代码地址?iOS工具的一些使用?


原理知识问答

1、内存管理

1、autorelease嵌套,系统怎么处理?

答:在NSAutoReleasePool中会有一个array保存所有需要被autorelease的对象, 由于我们要确保在保存对象时array不会对该对象进行强引用(retainCount plus 1), 我们需要用CFMutableArrayRef。

由于每一个线程都有自己的autoReleasePool,所以我们需要保存该线程中所创建的所有autoreleasepools,每创建一个autoreleasepool, 我们可以把它放入一个stack里,然后将这个stack保存起来,例如: 

    NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];

    NSString *key = @"com.my.thread-local.releasepool";

    CFMutableArrayRef stack = threadDict[key];

    if (!stack) {

       stack = CFArrayCreateMutable(NULL, 0, NULL);

       [threadDict setObject:(id)stack forKey:key];

    }

    当dealloc的时候,首先遍历当前autoReleasePool里所有对象,发送release,然后release保存所有对象的CFMutableArray (CFRelease(_objects))。 然后遍历保存在thread里的stack,由于我们将所有在该线程内创建的autoreleasepool都存入这个stack里,我们只需要release所有在self之后的出现的autoreleasepool, 然后把self从stack里移除。由于每一个autoreleasepool都遵从这样的逻辑,这其实就是一个递归的调用。

2、arc原理?

答:ARC (Automatic Reference Counting),在对象被创建时,ARC会保存一大堆关于该对象的信息:对象类型、所有的属性等等,当我们不再需要该对象的时候,ARC会帮助我们销毁该对象。

    当我们alloc、init一个对象实例时,编译器会在该实例使用完后插入objc_release去销毁该对象。如果我们是在使用properties的时候,该property的getter会被写成:

  - (Test *)test {

      return objc_retainAutoreleaseReturnValue(_test);

 }

    可以看到,所有的properties都被retain/autorelease了, 当引用properties时,编译器还会将caller改写为:objc_retainAutoreleaseReturnValue([self test]);这里出现了两次的retain/autorelease,而编译器会优化的只使用一次。

3、arc和mrc怎么破循环应用?

答: 使用__weak或__unsafe_unretained

4、ARC和MRC的本质区别是什么?

5、autorelease变量什么时候释放?子线程里面,需要加autoreleasepool吗?那子线程中的autorelease变量什么时候释放?

答:

(1)手动添加的是大括号结束的时候释放,系统自动释放是在当前runloop循环结束的时候。

(2)

(3)

6、如果父类有一个成员变量A,子类有A,B两个变量,父类指针指向了子类,父类执行delete方法,子类的变量会被释放吗?怎么才能被释放?执行子类的delete方法,两个变量会被释放吗?

7、内存泄漏可能出现的几种原因?怎么处理?

答:(1)第三方框架不当使用,block、delegate和NSTimer的循环引用,非OC对象内存处理,地图类处理,大次数循环内存暴涨。

(2)非OC对象处理:非OC对象,其需要手动执行释放操作例:CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃;其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free。

地图类内存泄漏处理:地图是比较耗费App内存的,因此在根据文档实现某地图相关功能的同时,需要注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理等滞空为nil;注意地图中标注(大头针)的复用,并且在使用完毕时清空标注数组等。

2、线程处理

1、线程安全?

答:不知道要问什么,问lock?还是其他?lock的话有:NSLock、OSSpinLock、NSRecursiveLock、pthread_mutex_t。 我们还可以用serial dispatch_queue_t来作lock。还有dispatch_barrier也可以。至于用哪个,就要具体分析了。

2、NSOpearation可不可以停止?

答: 调用cancel方法可以将NSOperationQueue当前状态设为cancelled,在operation运行中我们要不断的检查当前的operationQueue的状态,看isCancelled返回是否为真,若为真,则立刻结束operation。

3、NSThread,GCD,NSOperation相关的。开启一条线程的方法?线程可以取消吗?

4、子线程中调用connection方法,为什么不回调?

答:因为没有加入runloop,执行完任务就销毁了,所以没有回调。

5、GCD和NSOperation的区别?

3、网络请求

    1、简述http状态码及请求过程,自己写http框架,缓存,异步,并发高性能的解决方案?

答:根据我的经验,用过的HTTP状态码有: 200 OK, 201 Created, 302 Redirect, 304 Not Modified, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 408 Request Time out, 500 Internal Error, 503 Server Not Available。网上能找到全部的状态码,但个人觉得记住一些常用的就足够了,当碰到特殊情况要使用其他状态码,查资料就好了。

    对于http框架,在ios中,通常通过封装NSURLSession来完成网络层的开发,主要注意的是网络层应独立,只完成与后台API的通信,把数据处理或其他与网络通信无关的内容分离开来。

    缓存,除非有特殊要求,个人建议还是使用NSURLCache或者NSCache来完成。如果服务器支持的话,应该好好利用304状态码的特性,这样会节省流量,而且网络响应会快些。

    异步这个我觉得不用多说了吧?NSURLSession都是异步的。

    对于并发高性能:需要知道的是,NSURLSession在iOS中最多可以有4个tasks同时运行,所以应该复用所创建的NSURLSession, 在它之上创建不同的tasks。如果像是tableview中加载图片,可以创建一个队列(queue), 如果tasks超过4个,把超过的放入这个队列中,当之前的任务完成时,检查队列中有没有等待的,如果有,把它们从队列中取出来,然后[task resume]。

    2、在杭州HTTP请求服务器响应快,可能离服务器距离近,而在深圳访问就很慢很慢,会是什么原因?如果用户投诉,怎么分析这个问题?

    3、TCP建立连接的过程,断开连接的过程,为什么是四次握手?

2、socket编程简述

3、asihttp代码原理 ,异步请求的原理,异步请求最大数目,为什么只能这么多?

4、http请求方式?

4、优化处理

1、UI、动画及动画优化方案,UITableViewCell优化方案?

答:  UI, 动画优化要根据实际情况以及profiling的结果来进行具体分析,光这样问还真不知道该怎么回答。

    UITableViewCell的优化也是如此,但是一般的套路是profiling, 尽量少加subviews, 减少off-screen rendering, 比如圆角图片,可以在下载图片时在后台直接画圆角在图片中。下载图片时尽量使用caching。在加载数据时,尽量减少数据处理的时间,尽量不要fetching core data 等等。

2、造成UITabelView卡顿的原因有哪些?

答:1.最常用的就是cell的重用, 注册重用标识符

如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell,当有很多数据的时候,就会堆积很多cell。

如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell。

2.避免cell的重新布局

cell的布局填充等操作比较耗时,一般创建时就布局好;如可以将cell单独放到一个自定义类,初始化时就布局好。

3.提前计算并缓存cell的属性及内容

当我们创建cell的数据源方法时,编译器并不是先创建cell再定cell的高度,而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell。

4.减少cell中控件的数量

尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,不适用的可以先隐藏。

5.不要使用ClearColor,无背景色,透明度也不要设置为0

渲染耗时比较长。

6.使用局部更新

如果只是更新某组的话,使用reloadSection进行局部。

7.加载网络数据,下载图片,使用异步加载,并缓存

8.少使用addView 给cell动态添加view

9.按需加载cell,cell滚动很快时,只加载范围内的cell

10.不要实现无用的代理方法,tableView只遵守两个协议

11.缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可

12.不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。

13.预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕;

14.使用正确的数据结构来存储数据。

5、Runtime、RunLoop

1、Runtime是什么?

2、Runloop是什么?

3、说一下runtime?为什么OC里面是发消息,而C/C++是函数调用?

4、NSTimer时间准吗?为什么不准?如果不准该怎么实现一个精准的NSTimer?

答:(1)不准;

(2)不准的原因:NSTimer加在main RunLoop中,模式是NSDefaultRunLoopMode,main负责所有主线程时间,例如UI界面的操作,复杂的运算,这样在同一个RunLoop中timer就会产生阻塞;当模式的改变时,主线程RunLoop里有两个预置的Mode:kCFRunLoopDefaultMode和UITrackingRunLoopMode,当你创建一个timer并加到DefaultMode时,timer会得到重复回调,但此时滑动一个ScrollView时,RunLoop会将mode切换为UITrackingRunLoopMode,这时timer就不会被回调,并且也不会影响到滑动操作,所以会影响到NSTimer不准的情况。

(3)

方法一:

在主线程中进行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];

在子线程中进行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]; }

}

总结:

一开始的时候系统就为我们将主线程的main runloop隐式的启动了。

在创建线程的时候,可以主动获取当前线程的runloop。每个子线程对应一个runloop

方法二:

使用mach内核级的函数可以使用mach_absolute_time()获取到CPU的tickcount的计数值,可以通过”mach_timebase_info”函数获取到纳秒级的精确度 。然后使用mach_wait_until(uint64_t deadline)函数,直到指定的时间之后,就可以执行指定任务了。

关于数据结构mach_timebase_info的定义如下:

struct mach_timebase_info {uint32_t numer;uint32_t denom;};

#include

#include

static const uint64_t NANOS_PER_USEC = 1000ULL;

static const uint64_t NANOS_PER_MILLISEC = 1000ULL * NANOS_PER_USEC;

static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MILLISEC;

static mach_timebase_info_data_t timebase_info;

static uint64_t nanos_to_abs(uint64_t nanos) {

return nanos * timebase_info.denom / timebase_info.numer;

}

void example_mach_wait_until(int seconds) {

mach_timebase_info(&timebase_info);

uint64_t time_to_wait = nanos_to_abs(seconds * NANOS_PER_SEC);

uint64_t now = mach_absolute_time();

mach_wait_until(now + time_to_wait);

}

方法三:直接使用GCD替代!

6、数据存储处理

1、本地数据库海量数据如何提高查询效率和存储效率?

答:在ios中存储数据基本上就是plist, sqlite 和core data (NSUserDefault其实也是plist), plist建议用在存储简单而且数据量不大的情况,而且对于查询没有太多要求的。sqlite可以高效的查询和存储数据,但是缺点是:C API, 要自己做封装,而且每次都需要读写硬盘,对数据变化不敏感,要手动更新界面从而反应数据中的变化

在iOS开发中,除非有特殊需求,一般都建议使用Core Data. 

    如何提高查询效率:(1)设置合适的index;(2)优化predicate,对于string类型,尽量不要使用"==";(3)使用batchSize;(4) 合理使用batchLimit;(5)对于需要用到的relationship objects,可以使用setRelationshipKeyPathsForPrefetching来减少Faulting overhead;(6)可以使用batchFaulting来减少Faulting overhead。

    如何提高存储效率:(1) 尽量避免在main thread中写数据;(2)不要在Core Data中保存图片,文件等数据;(3)对于删除,更新,尽量使用batch;(4)注意调用[NSManagedObjectContext save:]的时机,尽量是由在后台运行的NSManagedObjectContext来完成写入。

2、在block里面使用_property会造成循环引用吗?怎么解决?除了使用self->_property,可以使用valueforkey来访问吗 在block里面可以修改它的值吗setvalueforkey?可以修改它的值,可以用valueforkey来解决,显式的的使用self,block外先持有self的弱引用。

3、sqlite中插入特殊字符的方法和接收到处理方法?

4、1.CoreData的架构?CoreData的fault机制?CoreData的优势?2.OC语言的特点是什么(或是和其他语言的区别)?OC runtime为什么可以动态添加变量和方法?

7、生命周期

1、Viewcontroller的生命周期?

2、viewcontroller的一些方法的说明viewDidLoad, viewWillDisappear, viewWillAppear方法的顺序和作用?

8、设计模式

1、MVC相互通讯规则知道哪些?

答:MVC是一种设计思想、框架模式和一种把应用中所有类组织起来的策略,把程序分成三块,分别是:

M(Model):负责存取数据。

V(View):用于构建视图,一般根据Model来创建视图。

C(Controller):控制你Model如何呈现在屏幕上,当它需要数据的时候就告诉Model,你帮我获取某某数据;当它需要UI展示和更新的时候就告诉View,你帮我生成一个UI显示某某数据,是MOdel和View的沟通桥梁。

MVC通讯模式

C to M

可以直接单向通信。Controller 需要将 Model 呈现给用户,因此需要知道模型的一切,还需要有同 Model 完全通信的能力,并且能任意使用 Model 的公共 API。

C to V

可以直接单向通信。Controller 通过 View 来布局用户界面。

M to V

永远不要直接通信。Model 是独立于 UI 的,并不需要和 View 直接通信,View 通过 Controller 获取 Model 数据。

V to C 

View 不能对 Controller 知道的太多,因此要通过间接的方式通信。

Target

action:首先 Controller 会给自己留一个 target,再把配套的 action 交给 View 作为联系方式。那么 View接收到某些变化时,View 就会发送 action 给 target 从而达到通知的目的。这里 View 只需要发送action,并不需要知道 Controller 如何去执行方法。

代理:有时候 View 没有足够的逻辑去判断用户操作是否符合规范,他会把判断这些问题的权力委托给其他对象,他只需获得答案就行了,并不会管是谁给的答案。

DataSoure:View 没有拥有他们所显示数据的权力,View 只能向 Controller 请求数据进行显示,Controller 则获取 Model 的数据整理排版后提供给 View。

M to C

同样的 Model 是独立于 UI 存在的,因此无法直接与 Controller 通信,但是当 Model 本身信息发生了改变的时候,会通过下面的方式进行间接通信。

Notification & KVO一种类似电台的方法,Model 信息改变时会广播消息给感兴趣的人 ,只要 Controller 接收到了这个广播的时候就会主动联系 Model,获取新的数据并提供给 View。

 MVC 模式的优点:(1)低耦合性;(2)有利于开发分工;(3)有利于组件重用;(4)可维护性。

9、其他

1、说下OC发消息的过程

2、索引的缺点?

答:(1) 需要空间储存索引;(2) 创建和维护索引需要耗费时间;(3)当删除,插入和更新数据时,索引也需要进行更新,这样降低了写数据的速度。

3、断点续传怎么实现?需要设置什么?

答: 可使用NSURLSession来完成. 主要通过以下的方法

    - (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;

    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;

    如果不是使用NSURLSession, 则要麻烦一些,首先必需在暂停时记录下当前已接收的文件长度,在下一次开始时设置HTTP header里的Range:

    NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.receivedLength];

    [request setValue:range forHTTPHeaderField:@"Range"];

4、什么是OC对象?union是OC对象吗? block是OC对象吗?

答:

(1)每个 Objective-C 对象都是指向某块内存数据的指针,所以在声明变量时,类型后面要跟一个“*”字符:Class的定义:一个结构体

(2)Union不是OC对象

(3)block是特殊的OC对象,定义是个结构体类型,其他OC对象分配在堆上,block是分配在栈上。

5、id是什么类型?

答:是一个指针类型的,可以指向任意OC对象类型的通用对象类型。定义也是结构体。

6、自定义一个类,有两个方法,一个是以new开头的,另一个不是以new开发的,方法里面都是alloc init方法,这两个方法有什么不同?引用计数变化?ARC下,编译器会插入什么语句?

7、在init方法里面,设置背景颜色,会生效吗?

答:会生效。为什么会?

8、了解UIKit结构?


UIKit架构

9、OC中的锁是什么?有哪些?常用的有哪里?

答:(1)在计算机科学中,锁是一种同步机制,用于在存在多线程的环境中实施对资源的访问机制,可以理解为它是用于排除并发的一种策略。

(2)iOS中锁的种类:递归锁、条件锁、分布式锁、一般锁(根据NSLock类里面的分类进行划分)。

(3)@synchronized(关键字加锁)、NSLock(対象锁)、NSCondition、NSConditionLock(条件锁)、NSRecursiveLock(递归锁)、pthread_mutex(互斥锁)、dispatch_semaphore(信号量实现加锁(GCD))、OSSpinLock、pthread_rwlock、POSIX Conditions、os_unfair_lock。

10、自旋锁和互斥锁的异同点?

答:(1)共同点:都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。

(2)不同点:

互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。

自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。

自旋锁的效率高于互斥锁

使用自旋锁时要注意

由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在哪里自旋,这就会浪费CPU时间。

持有自旋锁的线程在sleep之前应该释放自旋锁以便其他可以获得该自旋锁。内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起。

使用任何锁都需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:

1.建立锁所需要的资源

2.当线程被阻塞时所需要的资源

11、用C/C++/OC,任选其一,实现自旋或互斥?

答:(1)原理:

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。

自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

(2)

C++实现

12、编译做过哪些事情?

答:C++,Objective-C都是编译语言,编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率高。

iOS开发目前的常用语言是:Objective和Swift。二者都是编译语言,换句话说都是需要编译才能执行的。二者的编译都是依赖于Clang + LLVM. OC和Swift因为原理上大同小异,知道一个即可!

不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端。所以简单的编译过程如图:

简单编译过程

编译器前端(Clang)的任务:语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。


编译器前端

编译器后端(LLVM(Low level vritual machine))的任务:对机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。

BitCode生成及链接期优化等等


不同架构生成的机器码

执行一次Xcode build的流程(当你在Xcode中,选择build的时候(快捷键command+B)):

(1)编译信息写入辅助文件,创建编译后的文件架构(name.app);

(2)处理文件打包信息,例如在debug环境下:

Entitlements: {

"application-identifier"="app的bundleid";

"aps-environment"=development;

}

(3)执行CocoaPod编译前脚本,例如对于使用CocoaPod的工程会执行CheckPods Manifest.lock

(4)编译各个.m文件,使用CompileC和clang命令:

1.CompileC ClassName.o ClassName.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler

2.export LANG=en_US.US-ASCII

3.export PATH="..."

4.clang-x objective-c -arch x86_64 -fmessage-length=0 -fobjc-arc...

-Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot

iPhoneSimulator10.1.sdk -fasm-blocks ... -I 上文提到的文件 -F 所需要的Framework-iquote 所需要的Framework ... -c ClassName.c -o ClassName.o

Clang的编译命令

13、frame 和 bounds 的 区别 ,bound的大小改变frame 改变吗?


第三方框架问答

1、项目里面使用到什么网络库,用过ASIHTTP库吗?

2、使用过哪些第三方库?

答:AFNetworking、SDWebImage、MBProgressHUD、MJRefresh、Masonry

3、SDWebImage的实现过程

(1)入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片;

(2)进入 SDWebImageManagerdownloadWithURL:delegate:options:userInfo:交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:。

(3)先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager;

(4)SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage:到 UIImageView+WebCache 等前端展示图片;

(5)如果内存缓存中没有,生成 NSInvocationOperation添加到队列开始从硬盘查找图片是否已经缓存;

(6)根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:;

(7)如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存),SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:进而回调展示图片;

(8)如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:;

(9)共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片;

(10)图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败;

(11)connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。

(12)connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

(13)图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多;

(14)在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo 回调给 SDWebImageDownloader;

(15)imageDownloader:didFinishWithImage:回调给 SDWebImageManager 告知图片下载完成;

(16)通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片;

(17)将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存,写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程;

(18)SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存应用结束的时候清理过期图片;

(19)SDWI 也提供了 UIButton+WebCache 和MKAnnotationView+WebCache,方便使用;

(20)SDWebImagePrefetcher 可以预先下载图片,方便后续使用。



数据结构相关问答

1、数据结构与算法相关。知道哪些数据结构?

答:线性表(具体点 数组)连接、栈、队列、二叉树、堆。

2、什么是完全二叉树?堆是什么数据结构?

答:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

3、向量和链表的区别?什么时候选择vector?什么时候选择使用链表?

4、强连通分量了解吗?

答:

(1)概念:有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

(2)定义:

有向图强连通分量:

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。

如果有向图G的每两个顶点都强连通,则称G是一个强连通图。

非强连通图有向图的极大强连通子图,成为强连通分量(strongly connected components)。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,{5},{6}也分别是两个强连通分量。

有向图强连通分量

直接根据定义,用双向遍历取交际的方法求强连通分量,时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或者Tarjan算法两者的时间复杂度都是O(N+M)。本文介绍的是Tarjan算法。

原理:

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一颗子树。

搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以盘对栈顶到栈中的节点是否为一个强连通分量。

定义DFN(u)为节点u搜索的次序编号(时间戳)。Low(u)为u或者u的子树能够追溯到的最早的栈中的节点的次序号。

由定义可以得出:

Low(u)= Min { DFN(u), Low(v)} ((u,v)为树枝边,u为v的父节点DFN(v),(u,v)为指向栈中节点的后向边(非横叉边))

当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

5、iOS中哪里用到栈了?开发过程中哪里用到链表了?栈的特点,链表的特点

6、字典的实现原理?

答:(1)NSDictionary(字典)是使用hash表来实现key和value之间的映射和存储的方法:

- (void)setObject:(id)anObject forKey:(id)aKey;

Objective-C中的字典NSDictionary底层其实是一个哈希表。

(2)哈希原理

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

哈希概念:哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。

(3)哈希存储过程:根据key计算它的哈希值h;假设箱子的个数为n,那么这个键值对应该放在第(h%n)个箱子中;如果该箱子中已经有了键值对,就使用开放寻址法或者拉链法解决冲突。

在使用拉链法解决哈希冲突时,每个箱子其实是一个链表,属于同一个箱子的所有键值对都会排列在链表中。

哈希表还有一个重要的属性: 负载因子(load factor),它用来衡量哈希表的空/满程度,一定程度上也可以体现查询的效率,计算公式为:

负载因子 = 总键值对数 / 箱子个数

负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是 1,或者 0.75 等)时,哈希表将自动扩容。

哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使 key 的哈希值不变,对箱子个数取余的结果也会发生改变,因此所有键值对的存放位置都有可能发生改变,这个过程也称为重哈希(rehash)。

哈希表的扩容并不总是能够有效解决负载因子过大的问题。假设所有 key 的哈希值都一样,那么即使扩容以后他们的位置也不会变化。虽然负载因子会降低,但实际存储在每个箱子中的链表长度并不发生改变,因此也就不能提高哈希表的查询性能。

基于以上总结,细心的朋友可能会发现哈希表的两个问题:

1.如果哈希表中本来箱子就比较多,扩容时需要重新哈希并移动数据,性能影响较大。

2.如果哈希函数设计不合理,哈希表在极端情况下会变成线性表,性能极低。

3.block和函数指针的理解;

相似点:

函数指针和Block都可以实现回调的操作,声明上也很相似,实现上都可以看成是一个代码片段。

函数指针类型和Block类型都可以作为变量和函数参数的类型。(typedef定义别名之后,这个别名就是一个类型)

不同点:

函数指针只能指向预先定义好的函数代码块(可以是其他文件里面定义,通过函数参数动态传入的),函数地址是在编译链接时就已经确定好的。

Block本质是Objective-C对象,是NSObject的子类,可以接收消息。

函数里面只能访问全局变量,而Block代码块不光能访问全局变量,还拥有当前栈内存和堆内存变量的可读性(当然通过__block访问指示符修饰的局部变量还可以在block代码块里面进行修改)。

从内存的角度看,函数指针只不过是指向代码区的一段可执行代码,而block实际上是程序运行过程中在栈内存动态创建的对象,可以向其发送copy消息将block对象拷贝到堆内存,以延长其生命周期。

7、谈谈你对数组和链表认识,还有你是怎么用他们的?


简单算法相关问答

1、100个数,找出其中重复的数?

2、10个数,找出其中最大数和最小数,没有值相等的。

3、归并排序的过程

4、堆排序的过程、快排的过程

5、冒泡排序法

答:

NSMutableArray *arr = [[NSMutableArray alloc] initwithArray:@[@"1",@"2",@"6",@"4"]];

for (int i = 0; i < arr.count-1; i++) {

     for (int j = 0; j < arr.count-1-i; j++) {

            int a = [[arr objectWithIndex:j] intValue];

            int b = [[arr objectWithIndex:j+1] intValue];

           if (a < b) {

                [arr replaceObjectAtIndex:j withObject:[NSString stringWithFormat:@"%d",b]];

                [arr replaceObjectAtIndex:j+1 withObject:[NSString stringWithFormat:@"%d",a]];

           }

    }

}

6、用最有效的代码实现 1. 对一个排序整型数组去重 2. 对一个排序整型数组找出其中两数和为K的组合 3. 救两个数组的交集

7、猴子摘香蕉,共50个,一次可以摘一个或两个,问摘法种数?

答: 

斐波那契数列

long long fun1(long long n) {

    return n > 1 ? fun1(n-1) + fun1(n-2) : 1;

}

long long fun2(long long n) {

       long long a1 = 1, a2 = 1, t = 0;

       for (int i=1; i < n; i++) {

           t = a1 + a2;

           a1 = a2;

           a2 = t;

        }

       return n > 1 ? t : 1;

}

8、求循环数组的头?请参考网上很多代码,对于链表的话,有两个指针,一个走一步,一个走两步,就能确定是否是循环数组。

9、交换两个数组 使他们的和尽量最小

10、二叉树前序遍历非DFS实现

答:DFS:深度优先搜索


其他语言相关知识

1、C++相关的。什么是虚函数?虚函数是怎么实现的?什么是多态?

2、为什么类的析构函数设置成虚函数?为什么构造函数不设置成虚函数?

3、GPUimage的滤镜链问题,NSDictionary的实现,以及复杂度。


参考网站:

https://www.jianshu.com/p/dd17bdcff9f7

https://www.jianshu.com/p/92653db0e76a

http://www.cocoachina.com/bbs/read.php?tid=460991


1. void merge (int &a[], int count) { int i = 0, j = i+1 while ( i!=j && j

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容