ios开发iOS面试题

iOS 笔记(实时更新)

2020-02-19  本文已影响0人  Cary9396

1.关于Block

Block是将函数及其执行上下文封装起来的对象。
Block内部有isa指针,所以说本质也是OC对象。

关于Block变量截获

1.局部变量截获是值截获。
2.局部静态变量截获是指针截获。
3.全局变量,静态全局变量不截获,直接取值。

Block的几种形式

分为全局Block、栈Block、堆Block三种形式。
1.不使用外部变量的Block是全局Block。
2.使用外部变量并且未进行copy操作的是栈Block。
3.对栈Block进行copy操作的,就是堆Block,而对全局Block进行copy,仍是全局Block。
如果对栈Block进行copy,将会copy到堆区,对堆Block进行copy,将会增加引用计数,对全局Block进行copy,因为是已经初始化的,所以什么也不做。
另外由于block捕获的__block修饰的变量会去持有变量,那么如果用__block修饰self,且self持有block,并且block内部使用到__block修饰的self时,就会造成多循环引用,即self持有block,block 持有__block变量,而__block变量持有self,造成内存泄漏。
如果要解决这种循环引用,可以主动断开__block变量对self的持有,即在block内部使用完weakself后,将其置为nil,但这种方式有个问题,如果block一直不被调用,那么循环引用将一直存在。
所以,我们最好还是用__weak来修饰self。
具体参考:https://www.jianshu.com/p/0e1a0e7e988d

2.关于自动布局 Autolayout 优先级的使用

在Autolayout中每个约束都有一个优先级,优先级的范围是1~1000,默认创建的约束优先级为最高的1000
约束优先级核心就是为了如果存在多套约束的情况下,解决约束冲突。
关于固有的约束
有些控件能通过自己显示的内容计算出需要的Size,这个自动计算出来size就叫该控件的固有内容大小。这个大小是和需要显示的内容相关的。UIButton,UILabel就是具有固有内容大小属性的控件。UIButton可以根据它的title字符串长度和需要显示的image来计算需要的Size,UILabel可以根据它的text来计算。
Content Hugging Priority:该优先级表示一个控件抗被拉伸的优先级。优先级越高,越不容易被拉伸,默认是251。
Content Compression Resistance Priority:表示一个控件抗压缩的优先级,优先级越高,越不容易被压缩,默认是750。
具体参考:https://www.cnblogs.com/junhuawang/p/5691302.html

3.关于atomic和nonatomic

atomic只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的,因此在多线程编程时,线程安全还需要开发者自己来处理.
关于选择:atomic系统生成的getter、setter会保证get、set操作的安全性,但相对nonatomic来说,atomic要更耗费资源,且速度要慢,故在iPhone等小型设备上,如果没有多线程之间的通讯,使用nonatomic是更好的选择
具体参考:https://juejin.im/post/5a31dc76f265da430c11d3ab

4.关于响应者链

当事件发生的时候,响应链首先被发送给第一个响应者(往往是事件发生的视图,也就是用户触摸屏幕的地方)。事件将沿着响应者链一直向下传递,直到被接受并作出处理。一般来说,第一响应这是个视图对象或者其子类,当其被触摸后事件就交由它处理,如果他不处理,事件就会被传递给视图控制器对象UIViewController(如果存在),然后是它的父视图对象(superview),以此类推直到顶层视图。接下来会沿着顶层视图(topview)到窗口 (UIwindow 对象)再到程序的(UIApplication 对象),如果整个过程都没有响应这个事件,则该事件被丢弃,一般情况下,在响应链中只要有对象处理事件,事件就会被传递典型的响应路线图如:
First Responser -> The Window ->The Application->AppDelegate

5.关于UIView、UIWindow 和 CALayer

1.UIView:属于UIKit.framework框架,负责渲染矩形区域的内容,为矩形区域添加内容,响应区域的触摸事件,布局和管理一个或多个子视图。

2.UIWindow:属于UIKit.framework框架,是一种特殊的UIView,通常在一个程序中只有一个UIWindow,但可以手动创建多个UIWindow,同时加到程序里.UIWindow在程序中主要起到以下作用:
作为容器传递触摸消息到程序中的View和其他对象与UIViewController协同工作,方便完成设备方向旋转的支持。
作为容器,包含程序所有要显示的视图。
传递触摸消息到其他的UIView或其他对象。
与UIViewController协同工作,完成设备方向旋转的支持。

3.CAlayer:属于QuartzCore.famework,是用来绘制内容的,对内容进行动画处理依赖与UIView来显示,不能处理用户事件.UIView和CALayer是相互依赖的,UIView依赖CALayer提供内容,CALayer依赖UIView提供容器显示绘制内容。

延伸:UIViewController:每个视图控制器都有一个自带的视图,并且负责这个视图相关的一切事务,方便管理视图中的子视图,负责model和view的通信,检测设备旋转以及内存警告,是所有视图控制类的基类,定义了控制器的基本功能。

6.NSAutoreleasePool 是怎么工作的?

自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到栈顶。当一个对象收到发送 autorelease 消息时,它被添加到当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,它们从栈中被删除, 并且会给池子里面所有的对象都会做一次 release 操作。

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

什么情况使用 weak 关键字?

在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate、block。
自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak,使用 storyboard(xib 不行)创建的 vc,会有一个叫 _topLevelObjectsToKeepAliveFromStoryboard 的私有数组强引用所有 top level 的对象,所以这时即便 outlet 声明成 weak 也没关系。当然,也可以使用 strong。

weak 和 assign 的不同点:

weak、assign 修饰的属性指向一个对象时都不会增加对象的引用计数。然而在所指的对象被释放时,weak 属性值会被置为 nil,而 assign 属性不会。
assign 可以用非 OC 对象以及基本类型,而 weak 必须用于 OC 对象。

8.runtime 如何实现 weak 属性?

weak 此特质表明该属性定义了一种「非拥有关系」(nonowning relationship)。为这种属性设置新值时,设置方法既不持有新值(新指向的对象),也不释放旧值(原来指向的对象)。

runtime 对注册的类,会进行内存布局,从一个粗粒度的概念上来讲,这时候会有一个 hash 表,这是一个全局表,表中是用 weak 指向的对象内存地址作为 key,用所有指向该对象的 weak 指针表作为 value。当此对象的引用计数为 0 的时候会 dealloc,假如该对象内存地址是 a,那么就会以 a 为 key,在这个 weak 表中搜索,找到所有以 a 为键的 weak 对象,从而设置为 nil。

runtime 如何实现 weak 属性具体流程大致分为 3 步:

1、初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。
3、释放时:调用 clearDeallocating 函数,clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。

9.category 和 extension 有什么区别?category 是如何加载的?category 的方法覆盖是怎么处理的?

具体参考:https://www.jianshu.com/p/40e28c9f9da5

9.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。
具体参考:https://www.jianshu.com/p/c1b4841969e1

10.class_copyIvarList与class_copyPropertyList的区别

1.class_copyIvarList:能够获取.h和.m中的所有属性以及大括号中声明的变量,获取的属性名称有下划线(大括号中的除外)。
2.class_copyPropertyList:只能获取由property声明的属性,包括.m中的,获取的属性名称不带下划线。

11.class_ro_t 和 class_rw_t 的区别

细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。
具体参考:https://www.jianshu.com/p/823eaedb3697

12.+load方法、+initialize方法以及分类中同名方法的调用顺序

1.+load方法的调用在main()函数之前,并且不需要主动调用,程序启动会主动加载。
主类和分类都会加载+load方法。
加载顺序:主类优先于分类加载,无关编译顺序。
分类间的加载顺序取决于编译顺序:编译在前则先加载,编译在后则后加载。
规则是父类由于子类,子类优于分类(父类>子类>分类)。
2.普通方法中,分类同名方法会覆盖主类的方法。
多个分类中的同名方法只执行一个,即后编译的分类中的方法会覆盖所有前面的同名方法。
可以把声明写在主类,实现写在分类,这样也能调用到分类里面的代码。
同样可以把声明和实现写在不同的分类文件中,还是能找到的,不过主类要相同。
3.当第一次用到类的时候,如果重写了+initialize方法,会去调用。
调用+initialize方法的时候,先调用父类的,如果父类有分类,分类的+initialize方法会覆盖掉父类的。
父类的+initialize方法不一定调用,因为分类可能会重写它。
普通方法优先级:分类>子类>父类
总结:
普通方法的优先级: 分类> 子类 > 父类, 优先级高的同名方法覆盖优先级低的。
+load方法的优先级: 父类> 子类> 分类
+load方法是在main() 函数之前调用,所有的类文件都会加载,包括分类
+load方法不会被覆盖
同一主类的不同分类中的普通同名方法调用, 取决于编译的顺序, 后编译的文件中的同名方法会覆盖前面所有的,包括主类. +load方法的顺序也取决于编译顺序, 但是不会覆盖
分类中的方法名和主类方法名一样会报警告, 不会报错
声明和实现可以写在不同的分类中, 依然能找到实现
当第一次用到类的时候, 如果重写了+ initialize方法,会去调用
当调用子类的+ initialize方法时候, 先调用父类的,如果父类有分类, 那么分类的+ initialize会覆盖掉父类的, 和普通方法差不多
父类的+ initialize不一定会调用, 因为有可能父类的分类重写了它
具体参考:https://blog.csdn.net/appleLg/article/details/79931742

13.Objective-C 消息转发机制

Objective-C 消息转发分为三个步骤:
1.动态模式 (dynamic mode)
实现动态加载,需要重写对应的方法
+(BOOL)resolveInstanceMethod:(SEL)sel;
+(BOOL)resolveClassMethod:(SEL)sel;
如果返回 YES,表示找到,并且需要动态加载(在运行时添加方法实现,而不是在编译期)实现类的声明的方法。
如果返回 NO,表示通过动态模式,没有找到方法的实现,则需要通过其他两种方式接着继续找。
2.快速前向模式 (fast forwarding mode)
需要实现下面的方法,返回代理对象:

14.SideTables, SideTable, weak_table, weak_entry_t

具体参考:https://blog.csdn.net/u013378438/article/details/82790332

15.关联对象实现原理

具体参考:https://www.jianshu.com/p/0f9b990e8b0a

16,如何在关联对象上使用 weak

首先我们在 setter 方法里面使用了一个weak 的局部变量 weakObj 来存储值. 并在 block 中将其捕获并返回.
由于 weakObj 是弱引用, 所以不会修改对象的引用计数. 当对象释放时, 由于 weakObj的 weak属性, 它也会在释放后指向nil. 所以当在 getter 中返回的时候, 自然也是返回 nil.
具体参考:https://www.jianshu.com/p/1b0a1f9ecf39?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

17.Autoreleasepool的数据结构和实现原理

简单说是双向链表,每张链表头尾相接,有 parent、child指针
每创建一个池子,会在首部创建一个 哨兵 对象,作为标记
最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表。
自动释放池是一个个 AutoreleasePoolPage 组成的 一个page是4096字节大小,每个 AutoreleasePoolPage 以双向链表连接起来形成一个自动释放池
当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中
pop 时是传入边界对象,然后对page 中的对象发送release 的消息
具体参考:https://www.jianshu.com/p/50bdd8438857

18.iOS 中的内省方法

1.判断对象类型:
-(BOOL) isKindOfClass: 判断是否是这个类或者这个类的子类的实例
-(BOOL) isMemberOfClass: 判断是否是这个类的实例
2.判断对象or类是否有这个方法
-(BOOL) respondsToSelector: 判读实例是否有这样方法
+(BOOL) instancesRespondToSelector: 判断类是否有这个方法

19关于NSNotificationCenter

1.iOS9.0之前不移除是会崩溃的,iOS9.0之后不移除是不会崩溃的。
在iOS9.0之前,通知中心对观察者对象进行unsafe_unretained 引用,当被引用的对象释放时不会自动置为nil,,也就是成了野指针。
iOS9.0之后通知中心对观察者做了弱引用。
2.不管添加通知在主线程还是子线程,接收通知的方法所在的线程是由发送通知的线程决定的。
关于实现原理参考:https://www.cnblogs.com/miaomiaocat/p/11678591.html
关于同步异步问题请参考:
https://blog.csdn.net/xubinlxb/article/details/52073803
https://www.jianshu.com/p/208568075b4f

20.关于iOS触摸事件

触摸发生时,系统内核生成触摸事件,先由IOKit处理封装成IOHIDEvent对象,通过IPC传递给系统进程SpringBoard,而后再传递给前台APP处理。
事件传递到APP内部时被封装成开发者可见的UIEvent对象,先经过hit-testing寻找第一响应者,而后由Window对象将事件传递给hit-tested view,并开始在响应链上的传递。
UIRespnder、UIGestureRecognizer、UIControl,笼统地讲,事件响应优先级依次递增。
具体参考:https://www.jianshu.com/p/c294d1bd963d

21.关于runloop

具体参考:https://www.cnblogs.com/jiangzzz/p/5619512.html

22.关于KVO

被观察者在销毁前,要移除所有的观察者,iOS10以下会崩溃,iOS11以上不会崩溃
KVO可能崩溃的原因:参考https://www.jianshu.com/p/a3acab9dfc4a
KVO简单原理:参考https://www.jianshu.com/p/24dda55b799c
KVO Options 详细介绍:参考https://www.jianshu.com/p/917770c3d3d5

23.关于GCD和NSOperation

GCD具体参考:https://juejin.im/post/5a90de68f265da4e9b592b40
NSOperation具体参考:https://www.jianshu.com/p/4b1d77054b35

24.关于离屏渲染的理解 以及解决方案

具体参考:https://www.jianshu.com/p/cff0d1b3c915

25.imageName与imageWithContentsOfFile区别

1.两者区别:
imageName 加载图片完成后,会把图片缓存到内存中,这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象;如果缓存中没有找到相应的图片对象,则从指定地方加载图片然后缓存对象,并返回这个图片对象。
而imageWithContentOfFile 则仅只加载图片不缓存。
2.两者的使用场景
imageWithContentOfFile 主要用于加载大图,只加载一次,不太需要缓存;例如用于引导页加载图片。
在不太影响性能的情况下,使用imageName,大量使用imageName方式会增加开销CPU的时间来处理这件事,会影响性能。

26.APP启动监控和优化思路

具体参考:https://www.jianshu.com/p/4acad5aafe93

27.APP耗电量检测与优化

具体参考:https://www.jianshu.com/p/10bd19df2b14

28.APP上架AppStore流程

创建APPID—电脑钥匙串导出CSR文件—选择CSR文件创建发布证书—填写APPID、BundleID、选择发布证书创建PP文件—iTunes connect创建项目、填写基本信息—Xcode Archive打包项目上传—iTunes选择ipa包发布,等待审核
具体参考:https://www.jianshu.com/p/e5ac7b05750a

29.关于路由和组件化

目前主流三种方式:
1.Url-scheme注册
2.利用Runtime实现的target-action方式(CTMediator)
3.protcol-class注册
具体参考:https://www.jianshu.com/p/72d705ecc177

30.关于线程锁

分为互斥锁和自旋锁

互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁, 则等待资源的线程会被唤醒
包含:NSLock、pthread_mutex、NSCondition、NSConditionLock、NSRecursiveLock、dispatch_semaphore、@synchronized 、os_unfair_lock

自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁, 则等待资源的线程会立即执行
包含:OSSpinLock、atomic
自旋锁特点:
1.自旋锁的性能高于互斥锁,因为响应速度快
2.自旋锁虽然会一直自旋等待获取锁,但不会一直占用CPU,超过了操作系统分配的时间片会被强制挂起
3.自旋锁如果不能保证所有线程都是同一优先级,则可能造成死锁。
使用场景:多核处理器情况下: 如果预计线程等待锁的时间比较短,短到比线程两次切换上下文的时间还要少的情况下,自旋锁是更好的选择。
如果时间比较长,则互斥锁是比较好的选择。 单核处理器情况下: 不建议使用自旋锁。
1、所有的锁基本都是创建锁、加锁、等待、解锁的流程,所以并不复杂。
2.如果追求锁的极致性能,可以考虑更偏底层实现的pthread_mutex互斥锁以及信号量的方式。
3.@synchronized的效率最低,但是它使用最方便,所以如果没有性能瓶颈的话使用它也不错。
具体参考:https://www.jianshu.com/p/7911d791e0e6

31.关于KVC

KVC,键值编码 通过key名直接访问对象属性或者给对象属性赋值,而不需要调用明确的存取方法,可以在运行时动态地访问和修改对象属性。
在设置或者取值的时候默认情况下会根据:_key->_iskey->key->iskey的顺序搜索成员
具体参考:
https://juejin.im/post/5e45099e518825495371f3e4
https://www.jianshu.com/p/fb1fbe5eec13

32.NSCache和NSMutableDictionary的相同点与区别

相同点:
NSCache和NSMutableDictionary功能用法基本是相同的
区别:
NSCache是线程安全的,NSMutableDictionary线程不安全,Mutable开发的类一般都是线程不安全的
当内存不足时NSCache会自动释放内存(所以从缓存中取数据的时候总要判断是否为空)
NSCache可以指定缓存的限额,当缓存超出限额自动释放内存
NSCache的Key只是对对象进行了Strong引用,而非拷贝,所以不需要实现NSCopying协议

33.layoutSubviews方法什么时候会被调用

init方法不会调用layoutSubviews,但是是用initWithFrame进行初始化时,当rect的值不为CGRectZero时,会触发
addSubview会触发layoutSubviews方法
setFrame只有当设置的frame的参数的size与原来的size不同,才会触发其view的layoutSubviews方法
滑动UIScrollView会调用scrollview及scrollview上的view的layoutSubviews方法
旋转设备只会调用VC的view的layoutSubviews方法
直接调用[self setNeedsLayout];(这个在上面苹果官方文档里有说明)
-layoutSubviews方法:这个方法默认没有做任何事情,需要子类进行重写
-setNeedsLayout方法:标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定会被调用
-layoutIfNeeded方法:如果有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)
如果要立即刷新,要先调用[view setNeedsLayout],把标记设为需要布局,然后马上调用[view layoutIfNeeded],实现布局
在视图第一次显示之前,标记总是需要刷新的,可以直接调用[view layoutIfNeeded]

34.哪些情况会导致app卡顿,分别可以用什么方法来避免?

1.主线程中进化IO或其他耗时操作,解决:把耗时操作放到子线程中操作
2.GCD并发队列短时间内创建大量任务,解决:使用线程池
3.文本计算,解决:把计算放在子线程中避免阻塞主线程
4.大量图像的绘制,解决:在子线程中对图片进行解码之后再展示
5.高清图片的展示,解法:可在子线程中进行下采样处理之后再展示

35.App网络层有哪些优化策略?

1.优化DNS解析和缓存
2.对传输的数据进行压缩,减少传输的数据
3.使用缓存手段减少请求的发起次数
4.使用策略来减少请求的发起次数,比如在上一个请求未着地之前,不进行新的请求
5.避免网络抖动,提供重发机制

上一篇下一篇

猜你喜欢

热点阅读