原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 宏观题
- 一、KVO
- 二、Runtime
- 三、网络
- 四、多线程
- 五、Block
- 六、OC对象
- 七、Category
- 八、内存管理
- 九、RunLoop
- 十、View
- 十一、通知和委托
- 十二、性能优化和安全
- 十三、框架和架构
- 十四、Swift
- 十五、图像音视频
- 十六、本地缓存
- 十七、数据结构和算法
- 十八、其他系统框架
- 十九、设计模式
七、Category
1、底层结构
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
2、category & extension区别,能给NSObject添加Extension吗,结果如何
category
是runtime
动态添加到类中,extension
在编译时已经绑定到所属类
3、Objective-C类别的作用?继承和类别在实现中有何区别?
category
可以在不获悉,不改变原来代码的情况下往里面添加新的方法,只能添加,不能删除修改。并且如果类别和原来类中的方法产生名称冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级。
类别主要有3个作用:
(1)将类的实现分散到多个不同文件或多个不同框架中。
(2)创建对私有方法的前向引用。
(3)向对象添加非正式协议。
继承可以增加,修改或者删除方法,并且可以增加属性。
4、 Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?
Object-c
的类不可以多重继承。可以实现多个接口,通过实现多个接口可以完成C++
的多重继承。
Category
是类别,一般情况用分类好, 用Category
去重写类的方法,仅对本Category
有效,不会影响到其他类与原有类的关系。
5、类别和类扩展的区别?
category
和extensions
的不同在于后者可以添加属性。另外后者添加的方法是必须要实现的。extensions
可以认为是一个私有的Category
。
6、@protocol和category中如何使用@property
在protocol
中使用property
只会生成setter
和getter
方法声明,我们使用属性的目的,是希望遵守协议的对象能实现该属性。
category
使用@property
也是只会生成setter
和getter
方法的声明, 如果真的需要给category
增加属性的实现, 需要借助于运行时的两个函数:
objc_ setAssociatedObject
objc_ getAssociatedObject
八、内存管理
1、ARC的实现原理?ARC下对retain & release做了哪些优化
原理:
对不同的变量修饰符,编译器(LLVM)在合适的位置插入相应的代码来管理内存(引用计数)。
再配合runtime
对对象进行referenceCount
的管理。
优化:
Tagged Pointers
,不在引用计数的管理范畴;
hash
表的对应hash
函数简化,以及扩容策略简化;
2、weak的实现原理?SideTable的结构是什么样的
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
3、Autoreleasepool的原理? 所使用的的数据结构是什么?
class AutoreleasePoolPage {
pthread_t const thread;
id *next;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
}
static inline void *push();
static inline void pop(void *token);
static inline id *autoreleaseFast(id obj);
AutoreleasePoolPage
为结点的双向链表,结合runloop
中observer
的监听,再通过内置的push
与pop
函数来达到对象引用计数的管理。
push
是加入一个哨兵POOL_BOUNDARY(nil)
并返回对应的存放地址。
pop
找到push
返回的地址,然后从最后一个childPage(hotPage)
存放的对象(next)
开始出栈,做release
操作,直到POOL_BOUNDARY
;
循环中创建了许多临时变量,需要手写 autorelease pool
。
4、ARC下哪些情况会造成内存泄漏
Block
,delegate
, NSTimer
、CADisplaylink
5、为什么值类型不需要管理,而引用类型需要管理呢?那是因为他们分配内存方式不一样。
- 值类型会被放入栈中,他们依次紧密排列,在内存中占有一块连续的内存空间,遵循先进后出的原则。
- 引用类型会被放到堆中,当给对象分配内存空间时,会随机的从内存当中开辟空间,对象与对象之间可能会留有不确定大小的空白空间,因此会产生很多内存碎片,需要我们管理。
- 栈内存与堆内存从性能上比较,栈内存要优于堆内存,这是因为栈遵循先进后出的原则,因此当数据量过大时,存入栈会明显的降低性能。
- 值类型和引用类型之间是可以相互转化的,把值类型转化为引用类型的过程叫做装箱,比如把
int
包装为NSNumber
,这个过程会增加程序的运行时间,降低性能。在拆箱的过程中,我们一定要注意数据原有的类型,如果类型错误,可能导致拆箱失败,因此会存在安全性的问题。手动的拆箱和装箱,都会增加程序的运行时间,降低代码可读性,影响性能。 - 在IOS开发过程中,栈内存中的值类型系统会自动管理,堆内存中的引用类型是需要我们管理的。如果引用计数为0,对象回收,不为0不回收。当对象执行
alloc
、new
或者retain
时,引用计数加1,release
时,引用计数减1。
6、Objective-C管理内存的方式
MRC(人工引用计数),手动管理内存
MRC模式下,所有的对象都需要手动的添加retain
、release
代码来管理内存。使用MRC,需要遵守谁创建,谁回收的原则。当引用计数为0的时候,必须回收,引用计数不为0,不能回收,如果引用计数为0,但是没有回收,会造成内存泄露。如果引用计数为0,继续释放,会造成野指针。为了避免出现野指针,我们在释放的时候,会先让指针=nil
。
ARC(自动引用计数),自动管理内存
在ARC模式下,只要没有强指针(强引用)指向对象,对象就会被释放。在ARC模式下,不允许使用retain
、release
、retainCount
等方法。并且,如果使用dealloc
方法时,不允许调用[super dealloc]
方法。
ARC模式下的property
变量修饰词为strong
、weak
,相当于MRC模式下的retain
、assign
。
strong
:代替retain
,代表强引用。
weak
:代替assign
,声明了一个可以自动设置nil
的弱引用,但是比assign
多一个功能,指针指向的地址被释放之后,指针本身也会自动被释放。
7、堆和栈的区别
管理方式:
对于栈来讲,是由编译器自动管理,无需我们手工控制;
对于堆来说,释放工作由程序员控制,容易产生memory leak
。
申请大小:
栈: 在WINDOWS
下,栈的大小是2M (也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow
。
堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
碎片问题:
对于堆来讲,频繁的new/delete
势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。
对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,永远都不可能有一个内存块从栈中间弹出
分配方式:
堆都是动态分配的,没有静态分配的堆。
栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloc
函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持: 分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
堆则是C/C++
函数库提供的,它的机制是很复杂的。
8、自动释放池是什么,如何工作的?
当您向一个对象发送一个autorelease
消息时,就会将该对象放入到最新的自动释放池。 当程序执行到作用域结束的位置时,自动释放池就会被释放,池中的所有对象也就被释放。
-
ojc-c
是通过一种referring counting
(引用计数)的方式来管理内存的,对象在开始分配内存的时候引用计数为一,以后每当碰到有copy
,retain
的时候引用计数都会加一,每 当碰到release
和autorelease
的时候引用计数就会减一,如果此对象的计数变为了0,就会被系统销毁。 -
NSAutoreleasePool
就是用来做引用计数的管理工作的,这个东西一般不用你管的。 -
autorelease
和release
没什么区别,只是引用计数减1的时机不同而已,autorelease
会在对象的使用真正结束的时候才做引用计数减一。
9、NSArray释放
NSArray
对象会retain
(retain
值加一)任何数组中的对象。当NSArray
被卸载(dealloc
)的时候,所有数组中的对象会被执行一次释放(retain
值减一)。不仅仅是NSAray
,任何收集类(CollectionClasses
)都执行类似操作。
10、为什么很多内置的类,如TableViewCortrller的delegate的属性 是assign不是retain?
常见的delegale
往往是assgn
方式的属性而不是retain
方式的属性, 赋值不会增加引用计数,就是为了防止delegation
两端产生不必要的循环引用。
如果一个UITableViewController
对象a
通过retain
获取了UITableView
对象b
的所有权,这个UITableView
对象b
的delegale
又是a
,如果这个delegate
是retain
方式的,那基本上就没有机会释放这两个对象了。
11、程序在内存中运行时,内存分几个区?各自用途?
栈区由编译器自动分配释放存放函数的参数值,局部变量的值等。在高级语言中不需要显式的分配和释放。
堆区一般由程序员手动分配释放,如果不释放可有由OS释放。
数据区存储全局和静态变量。初始化的全局和静态变量在一块区域,未初始化的放在相邻的一块区域,程序结束后由系统释放。
代码区存放函数体的二进制代码。
12、对象的内存销毁时间表,分哪几个步骤?
1、
调用-release :
引用计数变为零;
对象正在被销毁,生命周期即将结束;
调用[self dealloc]
2、
父类调用-dealloc:
继承关系中最直接继承的父类再调用-dealloc
NSObject
调-dealloce
3、
调用object _dispose():
为C++的实例变量们(iVars)
调用destructors
;
为ARC状态下的实例变量们(iVars)
调用-release
;
解除所有使用runtime Associato
方法关联的对象;
解除所有_weak
引用;
调用free()
。
13、请说明并比较Swift语言中的以下关键词: strong, weak, unowned
Swift
的内存管理机制与Objective-C
一样为ARC (Automatic Reference Counting)
。它的基本原理是,一个对象在没有任何强引用指向它时,其占用的内存会被回收。反之,只要有任何一个强引用指向该对象,它就会一直存在于内存中。
strong
代表着强引用,是默认属性。当一个对象被声明为strong
时,就表示父层级对该对象有一个强引用的指向。此时该对象的引用计数会增加1。
weak
代表着弱引用。当对象被声明为weak
时,父层级对此对象没有指向,该对象的引用计数不会增加1。它在对象释放后弱引用也随即消失。继续访问该对象,程序会得到nil
,不会崩溃
unowned
与弱引用本质上一样。唯一不同的是,对象在释放后,依然有一个无效的引用指向对象,它不是Optional
也不指向nil
.如果继续访问该对象,程序就会崩溃。
14、不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放? (比如在一 个vc的viewDidLoad中创建)
手动干预释放时机:指定autorcleasepool
, 当前作用域大括号结束时释放。
系统自动去释放:autorelease
对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的runloop
迭代结束时释放。
autoreleasepool 以栈、双向链表的形式实现,主要通过下列三个函数完成.
objc_ autoreleasepoolPush
objc_ autoreleasepoolPop
objc_ autorelease
看函数名就可以知道,对autorelease分别执行push,和pop操作。销毁对象时执行release操作。
15、weak的实现原理?SideTable的结构是什么样的?
weak
表其实是一个hash
表,Key
是所指对象的地址,Value
是weak
指针的地址数组,weak
是弱引用,所引用对象的计数器不会+1,并在引用对象被释放的时候自动被设置为nil
。通常用于解决循环引用问题。
初始化时:runtime
会调用objc_initWeak
函数,初始化一个新的weak
指针指向对象的地址。
添加引用时:objc_initWeak
函数会调用objc_storeWeak()
函数, objc_storeWeak()
的作用是更新指针指向,创建对应的弱引用表。
释放时:调用clearDeallocating
函数。clearDeallocating
函数首先根据对象地址获取所有weak
指针地址的数组,然后遍历这个数组把其中的数据设为nil
,最后把这个entry
从weak
表中删除,最后清理对象的记录。
对象释放后:
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating,详细过程如下:
a. 从weak表中获取废弃对象的地址为键值的记录
b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
c. 将weak表中该记录删除
d. 从引用计数表中删除废弃对象的地址为键值的记录
九、RunLoop
1、RunLoop基本作用
- 保持程序持续运行,程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的
RunLoop
,RunLoop
保证主线程不会被销毁,也就保证了程序的持续运行。 - 处理App中的各种事件(比如:触摸事件,定时器事件,
Selector
事件等) - 节省CPU资源,提高程序性能,程序运行起来时,当什么操作都没有做的时候,
RunLoop
就告诉CUP
,现在没有事情做,我要去休息,这时CUP
就会将其资源释放出来去做其他的事情,当有事情做的时候RunLoop
就会立马起来去做事情
2、RunLoop在哪里开启
UIApplicationMain
函数内启动了Runloop
,程序不会马上退出,而是保持运行状态。因此每一个应用必须要有一个runloop
,我们知道主线程一开起来,就会跑一个和主线程对应的RunLoop
,那么RunLoop
一定是在程序的入口main
函数中开启。
3、 RunLoop对象
[NSRunLoop currentRunLoop];// 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop];// 获得主线程的RunLoop对象
CFRunLoopGetCurrent();// 获得当前线程的RunLoop对象
CFRunLoopGetMain();// 获得主线程的RunLoop对象
4、RunLoop和线程间的关系
每条线程都有唯一的一个与之对应的RunLoop
对象,RunLoop
保存在一个全局的Dictionary
里,线程作为key
,RunLoop
作为value
,主线程的RunLoop
已经自动创建好了,子线程的RunLoop
需要主动创建,RunLoop
在第一次获取时创建,在线程结束时销毁。
5、计时器
使用计时器需要注意,计时器一定要加入RunLoop
中,并且选好model
才能运行。scheduledTimerWithTimeInterval
方法创建一个计时器并加入到RunLoop
中所以可以直接使用。如果计时器的repeats
选择YES
说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid
。不能在dealloc
中调用,因为一旦设置为repeats
为yes
,计时器会强持有self
, 导致dealloc
永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear
中调用,这样当类需要被回收的时候就可以正常进入dealloc
中了。
十、View
1、隐式动画 & 显示动画
隐式动画: 未指定动画类型,仅改变了一个属性,然后Core Animation
来决定如何并且何时去做动画
显示动画:将动画动作显示添加到自定的layer
上来实现。
使用UIView
自带的API设置某些属性可以执行动画,而直接调用就不行;UIView
默认情况下禁止了layer
动画,但是在 animation block
中又重新启用了它们。
2、响应链 / 传递链
事件传递从上到下:hitTest:withEvent:
、 pointInside:withEvent:
事件响应从下到上:nextResponder
响应链:由离用户最近的view向系统传递。initial view –> super view –> …..–> view controller –> window –> Application –> AppDelegate
传递链:由系统向离用户最近的view传递。 UIKit –> active app's event queue –> window –> root view –>……–>lowest view
3、图层和UIView的区别是什么?
两者最大的区别是图层不会直接渲染到屏幕上,UIView
是i0S系统中界面元素的基础,所有的界面元素都是继承自它。它本身完全是由CoreAnimation
来实现的。它真正的绘图部分,是由一个CALayer
类来管理。UIView
本身更像是一个CALayer
的管理器。一个UIView
上可以有n
个CALayer
,每个layer
显示一种东西。
4、简单描述一下XIB与Storyboards,说一下他们的优缺点。
优点:
XIB:在编译前就提供了可视化界面,可以直接拖控件,也可以直接给控件添加约束,更直观一些,而且类文件中就少了创建控件的代码,确实简化不少,通常每个XIB对应一个类。
Storyboard:在编译前提供了可视化界面,可拖控件,可加约束,在开发时比较直观,而且一个storyboard可以有很多的界面,每个界面对应一个类文件,通过storybard, 可以直观地看出整个App的结构。
缺点:
XIB:需求变动时,需要修改XIB很大,有时候甚至需要重新添加约束,导致开发周期变长。XIB载入相比纯代码自然要慢一些。对于比较复杂逻辑控制不同状态下显示不同内容时,使用XIB是比较困难的。当多人团队或者多团队开发时,如果XIB文件被改动,极易导致冲突,而且解决冲突相对要困难很多。
Storyboard:需求变动时,需要修改storyboard上对应的界面的约束,与XIB一样可能要重新添加约束,或者添加约束会造成大量的冲突,尤其是多团队开发。对于复杂逻辑控制不同显示内容时,比较困难。当多人团队或者多团队开发时,大家会同时修改一个storyboard, 导致大量冲突,解决起来相当困难。
5、点击屏幕时是如何互动的
iOS系统检测到手指触摸(Touch)
操作时会将其打包成一个UIEvent
对象, 并放入当前活动Application
的事件队列,单例的UIApplication
会从事件队列中取出触摸事件并传递给单例的UIWindow
来处理,UIWindow
对象首先会使用hitTest:withEvent:
方法寻找此次Touch
操作初始点所在的视图(View)
,即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view
。
UIWindow
实例对象会首先在它的内容视图上调用hitTest:withEvent
,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:
(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:
返回YES
, 则继续逐级调用,直到找到touch
操作发生的位置,这个视图也就是要找的hit-test view
。
事件的传递和响应分两个链:
传递链:由系统向离用户最近的view
传递。UIKit > active app's event queue > window > root view >lowest view
响应链:由离用户最近的view
向系统传递。initial view > super view > view controller > window > Application
6、UI Button的继承关系
UIButton : UIControl : UIView : UlResponder : NSObject
7、使用drawRect有什么影响?
drawRect
方法依赖Core Graphics
框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch
事件的方式:每次按钮被点击后,都会用setNeddsDisplay
进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton
实例。
8、自动布局(Auto Layout)的作用是什么?请概括一下它是如何运行的。
相比于使用autoresizingmasks
和手动布局的方式来布局程序的用户界面,使用Auto Layout
将会是一个更好的选择。你可以使用Auto Layout
来给视图添加约束并且定义他们之间的关系。这个关系可以是视图和它的父视图之间的,和兄弟视图之间的关系,甚至是和它自己的关系。
相对于明确的设置视图的frame
,AutoLayout
可以使用约束来设定两个视图之间的间距和相对位置。Auto Layout
使用这些约束条件来计算用户视图上的元素在运行时的位置。你必须给视图设置足够的约束条件以防止位置的模糊不清。另外需注意的是,设置过多的约束条件也会导致冲突甚至程序的崩溃。
9、为什么当Core Animation完成时,layer 又会恢复到原先的状态?
因为这些产生的动画只是假象,并没有对layer
进行改变。呈现的界面实际上是模型图层的复制,但是它的属性值表示了当前外观效果,动画的过程实际上只是修改了呈现的界面,并没有对图层的属性进行改变,所以在动画结束以后图层会恢复到原先状态。
10、谈谈控制器View的加载过程?
当程序访问了控制器的View
属性时会先判断控制器的View
是否存在,如果存在就直接返回已经存在的View
;
如果不存在,就会先调用loadView
这个方法; .
如果控制器的IoadView
方法实现了,就会按照loadView
方法加载自定义的View;
如果控制器的loadView
方法没有实现就会判断storyboard
是否存在;
如果storyboard
存在就会按照storyboard
加载控制器的View
;
如果storyboard
不存在,就会创建一个空视图返回。
11、Quatrz 2D的绘图功能的三个核心概念是什么并简述其作用。
上下文:主要用于描述图形写入哪里;
路径:是在图层上绘制的内容;
状态:用于保存配置属性的值、填充和轮廓,alpha值等。
12、LayoutSubViews在什么时候被调用?
当View
本身的frame
改变时,会调用这个方法。
13、Core Animation动画和UIView动画
Core Animation
动画一切都是假象,并不会真实的改变图层的属性
如果在播放动画的时候, 不需要与用户交互,推荐使用Core Animation动画。UIView
动画必须通过修改属性的真实值,才有动画效果。
十一、通知和委托
1、什么时候用delegate,什么时候用Notification?
delegate
针对one-to-one
关系,并且reciever
可以返回值给sender
。
notification
可以针对one-to-one/many/none
,reciever
无法返回值给sender
。
所以,delegate
用于sender
希望接受到reciever的某个反回值,
notification用于通知多个
object`某个事件。
2、本地通知和远程推送通知对基本概念和用法?
本地通知和远程推送通知都可以向不在前台运行的应用发送消息,这种消息既可能是即将发生的事件也可能是服务器的新数据,不管是本地通知还是远程通知,他们在程序界面的显示效果相同,都可能显示为一段警告信息或应用程序图标上的微章。
本地通知和远程推送通知的基本目的都是让应用程序能够通知用户某些事情,而且不需要应用程序在前台运行。二者的区别在于本地通知由本应用负责调用,只能从当前设备上的iOS发出,而远程通知由远程服务器上的程序发送到APNS
,再由APNS
把消息推送至设备上的程序。
3、 KVO, NSNotification, delegate及block区别是什么?
KVO
就是cocoa
框架实现的观察者模式,一般同KVC
搭配使用,通过KV0
可以监测一个值的变化,比如View
的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。
NSNotification
是通知,也是一对多的使用场景。在某些情况下,KV0
和NSNotification
是一 样的, 都是状态变化之后告知对方。NSNotification
的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KV0
多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。
delegate
是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate
通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate
(代理 人)就可以了,由其他类完成所需要的操作。所以delegate
是一对一 关系。
block
是delegate
的另一种形式,是函数式编程的一种形式。使用场景跟delegate
一样,相比delegate
灵活。
delegate
一般的使用场景是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate
。Notification
一般是进行全局通知。delegate
是强关联,就是委托和代理双方互相知道。Notification
是弱关联,消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。
4、Push Notification是如何工作的?
用户的设备与苹果APNS
服务器形成一个长连接, 用户设备会发送uuid
和Bundle idenidentifer
给苹果服务器,苹果服务器会加密生成一个device Token
给用户设备,然后设备会将deviceToken
发送给APP的服务器,服务器会将device token
存进他们的数据库。
如果有人发送消息给某一个用户 ,服务器端就会去查询该用户的device Token,然后将device Token和要发送的信息发送给苹果服务器,苹果服务器通过device Token找到该用户的设备并将消息推送到设备上。
5、为什么NotificationCenter要removeObserver?如何实现自动remove?
如果不移除的话,万一注册通知的类被销毁以后又发了通知,程序会崩溃,因为向野指针发送了消息。
十二、性能优化和安全
1、有些图片加载的比较慢怎么处理?你是怎么优化程序的性能的?
1> 图片下载放在异步线程
2> 图片下载过程中使用占位图片
3> 如果图片较大,可以考虑多线程断点下载
2、App需要加载超大量的数据,给服务器发送请求,但是服务器卡住了如何解决?
1> 设置请求超时
2>给用户提示请求超时
3>根据用户操作再次请求数据
3、如果在网络数据处理过程中,发现一处比较卡, 一般怎么解决?
1> 检查网络请求操作是否被放在主线程了
2> 看看异步请求的数量是否太多了(子线程数量)
3> 数据量是否太大? 如果太大,先清除一些不必要的对象 (看不见的数据、图片)
4> 手机CPU使用率和内存问题
4、怎么防止别人反编译你的app?
1.本地数据加密:对UserDefaults
, sqlite
存储文件数据加密,保护帐号和关键信息
2.URL编码加密:对程序中出现的URL
进行编码加密,防止URL
被静态分析
3.网络传输数据加密:对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据
4.方法体,方法名高级混淆:对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码
5.程序结构混排加密:对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低
6.借助第三方APP加固:网易云易盾
5、遇到tableView卡顿嘛?会造成卡顿的原因大致有哪些?
1.没有使用到cell
的重用机制,注册重用标识符。
2.避免cell
的重新布局,cell
的布 局填充等操作比较耗时,一般创建时就布局好。
3 提前计算并缓存cell
的属性及内容,当我们创建cell
的数据源方法时,编译器并不是先创建cell
再定cell
的高度。
4.减少celI
中控件的数量,不同风格的CelI
可以使用不同的重用标识符,初始化时添加控件,不适用的可以先隐藏。
5.不要使用ClearColor
,无背景色,透明度也不要设置为0,渲染耗时比较长。
6.使用局部更新,如果只是更新某组的话,使用reloadScction
进行局部更新。
7.加载网络数据,下载图片, 使用异步加载,并缓存。
8.少使用addSubView
给cell
动态添加view
。
9.按需加载cell
, celI
滚动很快时,只加载可见范围内的cell
。
10.不要实现无用的代理方法,tableView
只遵守两个协议。
13.预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context
里先将其画一遍,导出成UIImage
对象,然后再绘制到屏幕;
14.不阻塞主线程,UITableView
停止或者减速滑动结束的时候再进行异步加载图片(SDWeblmage
已经做到了)
6、iOS是如何提高安全性,保护用户隐私信息的?
要求使用https
链接,并验证证书的正确性,对用户敏感数据使用密钥进行加密,对传输的内容进行加密。
为了获取所在地(Location
) 数据,增加了一条新的While In Use
许可项。这就意味着在未活跃的状态下,若有应用想访问所在地,就会弹出和该应用有关的警告信息。用户很方便地就可以取消该许可。
当设备使用Touch ID
允许应用进入时,只能接触到设备本人,这会使用户的银行应用相对安全些。
7、哪些途径可以让ViewController瘦下来?
把Data Source
和其他Protocols
分离出来(将UITableView
或者UCollectionView
的代码提取出来放在其他类中)
把网络请求逻辑移到ViewModel
层
把View
代码移到View
层(自定义View
)
8、有哪些常见的Crash场景?
访问了僵尸对象
访问野指针
访问了不存在的方法
数组越界
在定时器下一次回调前将定时器释放
9、MD5和Base64的区别是什么,各自场景是什么?
md5:用户密码存储文件校验
实质是一种散列表的计算方式
是一种不可逆的摘要算法
任意长度的明文字符串,加密后得到的密文字符串是长度固定的。
base64:公开的代码加密、url加密
实质是一种编码格式,如同UTF-8
是一种用64个字符来表示任意二进制数据的方法
可逆性。
可以将图片等二进制文件转换为文本文件。
可以把非ASCII
字符的数据转换成ASCII字符
,避免不可见字符。
10、苹果的iOS系统采用了以下哪些严格的安全机制?
代码签名
权限隔离
沙盒执行环境
十三、框架和架构
1、你实现过一个框架或者库以供别人使用么?如果有,请谈一谈构建经验 ;如果没有,请设想和设计框架的API,并指出大概需要如何做、需要注意一些什么方面?
1> 提供给外界的接口功能是否实用、够用
2> 别人使用我的框架时,能不能根据类名、方法名就猜出接口的具体作用
3> 别人调用接口时,提供的参数是否够用、调用起来是否简单
4> 别人使用我的框架时,要不要再导入依赖其他的框架
十四、Swift
1、说说Swift为什么将String, Array, Dictionary设计 成值类型?
要解答这个问题,就要和Objective-C
中相同的数据结构设计进行比较。Objective-C
中, 字符串,数组,字典,皆被设计为引用类型。
值类型相比引用类型,最大的优势在于内存使用的高效。值类型在栈上操作,引用类型在堆上操作。栈上的操作仅仅是单个指针的上下移动,而堆上的操作则牵涉到合并、移位、重新链接等。也就是说Swift
这样设计,大幅减少了堆上的内存分配和回收的次数。
String
, Array
, Dictionary
设计成值类型, 也是为了线程安全考虑。通过Swift
的let
设置,使得这些数据达到了真正意义上的“不变”,它也从根本上解决了多线程中内存访问和操作顺序的问题。
2、用Swift将协议(protocol) 中的部分方法设计成可选(optional) ,该怎样实现?
@optional
和@required
是Objective-C
中特有的关键字。Swift中,默认所有方法在协议中都是必须实现的。协议里方法不可以直接定义optional
。在协议和方法前都加上@objc
关键字,然后再在方法前加上optional
关键字。该方法实际上是把协议转化为Objcctive-C
的方式然后进行可选定义。第2种方式用扩展(extension)
来规定可选方法。Swift中, 协议扩展(protocol extension)
可以定义部分方法的默认实现,这样这些方法在实际调用中就是可选实现的了。
3、试比较Swift和Objective-C中的初始化方法(init) 有什么异同?
同时新增convenience
和required
两个修饰初始化方法的关键词。
convenience
只是提供一种方 便的初始化方法,必须通过调用同一个类中designated
初始化方法来完成。
required
是强制子类重写父类中所修饰的初始化方法。
4、泛型类
泛型协议是通过typealias
部分实现的。typealias
不是一个泛型类型,它只是一个占位符的名字。泛型一般是用来解决代码复用的问题。比如你有一个函数,它带有一个参数,参数类型是A, 然而当参数类型改变成B的时候,你不得不复制这个函数。
5、对一个optional变量拆包有多少种方法?并在安全方面进行评价。
拆包.jpg 拆包.jpg6、在Swfit中,什么时候用结构体,什么时候用类?
函数式编程倾向于值类型,面向对象编程更喜欢类。
类支持继承,结构体不支持。
类是引用类型,结构体是值类型。
并没有通用的规则决定结构体和类哪一个更好用。有一个好的经验是多使用结构体,在运行时,结构体的在性能方面更优于类, 原因是结构体的方法调用是静态绑定,而类的方法调用是动态实现的。
7、举例说明Swift里面有哪些是Objective-C中没有的?
Swift
引入了在Objcctive-C
中没有的一些高级数据类型,例如tuples
(元组),可以使你创建和传递一组数值。
Swift
还引入了可选项类型(Optionals)
,用于处理变量值不存在的情况。
可选项的意思有两种:一是变量是存在的,例如等于X,二是变量值根本不存在。
Optionals
类似于Objcctive-C
中指向nil
的指针,但是适用于所有的数据类型,而非仅仅局限于类,Optionals
相比于Objctive-C
中nil
指针更加安全和简明,并且也是Swift
诸多最强大功能之一。
8、Swift是一门安全语言吗?
Swift
是一门类型安全的语言,Optionals
就是代表 。
Swift
能帮助你在类型安全的环境下工作,如果你的代码中需要使用String
类型,Swift
的安全机制能阻止你错误的将Int
值传递过来,这使你在开发阶段就能及时发现并修正问题。
9、Swift中定义常量和Objective-C 中定义常量有什么区别?
0C是这样定义常量的:const int number= 0;
Swift是这样定义常量的:let number= 0;
0C中const
表明的常量类型和数值是在compilation time
时确定的;而Swift
中let
只是表明常量(只能赋值一次) ,其类型和值既可以是静态的,也可以是一个动态的计算方法,它们在runtime
时确定的。
10、闭包是值类型还是引用类型的?
闭包是引用类型。如果一个闭包被分配给一个变量,该变量被复制到另一个变量,它们实际是引用的相同一个闭包并且它里面的参数列表也同样会被复制。
11、Optional(可选型)是怎么实现的?
在Objective-C
中并没有Optional
类型,只有nil
,并且nil
只能用于表示对象类型无值,并不能用于基础类型(int, float)
,枚举和结构体。基础类型需要返回类似NSNotFound
的特殊值来表示无值,所以在Swift
中定义了Optinal
类型来表示各种类型的无值状态。
12、谈谈你对[1,2,3].map{S0 * 2}代码的理解
Trailing Closures
如果一個函數的最後一個參數是閉包時,則可以不寫()。省略閉包參數的類型聲明,其被推斷為Int
。So
表明是陣列中的每一個元素, 省略了閉包參數的命名。省略了閉包回傳值類型的聲明,其被推斷為Int
。若閉包只有一行,則可以省略return
。
13、在Swift语言中,使用扩展可以完成哪些任务?
添加计算属性
定义实例方法和类方法
提供新的构造器
使一个已有类型实现某个接口
14、Swift 逃逸闭包和非逃逸闭包的区别
概念:一个接受闭包作为参数的函数,该闭包可能在函数返回后才被调用,也就是说这个闭包逃离了函数的作用域,这种闭包称为逃逸闭包。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写@escaping
来明确闭包是允许逃逸。一般在多线程使用。
例如:当网络请求结束后调用的闭包。发起请求后过了一段时间后这个闭包才执行,并不一定是在函数作用域内执行的。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getData { (data) in
print("闭包结果返回--\(data)--\(Thread.current)")
}
}
func getData(closure:@escaping (Any) -> Void) {
print("函数开始执行--\(Thread.current)")
DispatchQueue.global().async {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
print("执行了闭包---\(Thread.current)")
closure("345")
})
}
print("函数执行结束---\(Thread.current)")
}
}
逃逸闭包的生命周期长于函数,函数退出的时候,逃逸闭包的引用仍被其他对象持有,不会在函数结束时释放。
非逃逸闭包:一个接受闭包作为参数的函数, 闭包是在这个函数结束前内被调用。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
handleData { (data) in
print("闭包结果返回--\(data)--\(Thread.current)")
}
}
func handleData(closure:(Any) -> Void) {
print("函数开始执行--\(Thread.current)")
print("执行了闭包---\(Thread.current)")
closure("4456")
print("函数执行结束---\(Thread.current)")
}
}
十五、图像音视频
1、谈谈你对Fmmpeg框架的理解?
音视频编解码框架,内部使用UDP协议针对流媒体开发,内部开辟了六个端口来接受流媒体数据,完成快速接受的目的。
2、 Metal是什么?
它是一个支持GPU加速3D
绘图的API。Metal
和OpenGL ES
相似,它也是一个底层API
, 负责和3D绘图硬件交互。它们之间的不同在于,Metal
不是跨平台的。与之相反的,它设计的在苹果硬件上运行得极其高效,与OpenGL ES
相比,它提供了更快的速度和更低的开销。
3、请描述SpriteKit和SceneKit的作用
SpriteKit
使用主机设备提供的图形硬件来高帧速率合成2D
图片 , 因为SpriteKit
支持丰富的渲染结构并接管了所有向OpenGL
发出绘画指令的的底层工作(low-level work)
,你可以将你的重心放在解决高层次的设计问题以及伟大的游戏交互设计上。
SceneKit
可以同时在IOS和OS X下工作,是用来构建3D场景的框架,且可以与Core Animation
和SpriteKit
无缝交互。在SceneKit
中可以直接引入制作好的3D
模型或场景。
4、请概括描述一下Core Audio是什么框架?
CoreAudio
是iOS和MAC的关于数字音频处理的基础,它提供应用程序用来处理音频的一组软件框架,所有关于IOS音频开发的接口都是由Core Audio
来提供或者经过它提供的接口来进行封装的。
按照官方的说法是集播放、音频处理、录制为一体的专业技术,通过它我们的程序可以同时录制,播放一个或者多个音频流,自动适应耳机,蓝牙耳机等硬件,响应各种电话中断,静音,震动等,甚至提供3D效果的音乐播放。
5、Quartz 2D在iOS开发中,可以完成以下哪些任务?
绘制一些系统UIKit框架中不好展示的内容,例如饼图
绘制图形:线条、三角形、矩形、圆、弧等
读取和生成 PDF
截图和裁剪图片
6、使用Core Image可以完成下面哪些工作?
使用内置的滤镜对图片进行快速的艺术处理和加工
对人脸等特征进行检测(CIDetector\CIDetector)
同时使用多个滤镜以产生更加复杂多变的自定义效果
创建运行在GPU上的自定义滤镜提高图像处理的速度
十六、本地缓存
1、沙箱模型的有四个文件夹,分别是什么,永久数据存储一般放在什么位置, 得到模拟器的路径的简单方式是什么?
Documents目录:您应该将所有应用程序数据文件写入到这个目录下。这个目录用于存储用户数据或其它应该定期备份的信息。
AppName. app目录:这是应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动。
Library目录:这个目录下有两个子目录: Caches
和Preferences
Preferences目录:包含应用程序的偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults
类来取得和设置应用程序的偏好。
Caches目录:用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。
Keychain
是独立于每个App的沙盒之外的,所以即使App被删除之后,Keychain
里面的信息依然存在。
2、请解释一下UserDefaults。 就你而言,你会如何在磁盘中对数组对象进行序列化?
UserDefaults
是 系统自带的一个轻量级数据本地化的一个方法,其本质是一个单例。用户名、密码、网址等都适合使用UserDefaults
来储存。对于自定义的对象需要对其归档才能储存。UserDefaults
存储的数据,位于沙盒中的 Library/Preferences
3、请概括描述一下Core Data是什么框架?
Core Data
框架提供了对象——关系映射(ORM)
的功能,即能够将0C对象转化成数据,保存在SQLite3
数据库文件中,也能够将保存在数据库中的数据还原成0C对象。在此数据操作期间,不需要编写任何SQL
语句。
4、设计一套大文件(如上百M的视频)下载方案。
大文件下载的问题:
若不对下载的文件进行转存,会造成内存消耗急剧升高,甚至耗尽内存资源,造成程序终止。
在文件下载过程中通常会出现中途停止的状况,若不做处理,就要重新开始下载,浪费流量。
大文件下载的解决方案:
对下载文件进行处理,每下载一点数据,就将数据写到磁盘中(通常是沙盒中),避免在内存累积数据(NSURLConnection
下载)使用NSFileHandle
类实现写数据,使用NSOutputStream
类实现写数据。当下载任务终止时,记录任务终止时的位置信息,以便下次开始继续下载。
5、归档
归档是一 个过程,即用某种格式来保存一个或多个对象,以便以后还原这些对象。可以使用归档的方法进行对象的深复制。采用归档的形式来保存数据,该数据对象需要遵守NSCoding
协议,并且必须提供encodeWithCoder:
和initWithCoder:
方法。归档的缺点是如果想改动归档数据的某一 小部分,则需要解压整个数据和归档整个数据。
6、应用程序bundle中主要有以下哪几种类型的文件?
可执行文件:每个应用程序必须要有一个可执行文件。
资源文件:是可执行文件以外的数据文件,常用的如图像、图标、音频文件、视图文件、配置文件等
Info.plist:用来配置应用的基本参数信息。包括应用程序唯一标识名、版本号,指向的可执行文件名等。
十七、数据结构和算法
1、请谈谈数组和链表的区别?
首先从逻辑结构上说,两者都是数据结构的一种,但存在区别。
数组是申请的一块连续的内存空间,并且是在编译阶段就要确定空间大小的,同时在运行阶段是不允许改变的,所以它不能够随着需要的改变而增加或减少空间大小,所以当数据量大的时候,有可能超出了已申请好的数组上限,产生数据越界,或者是数据量很小,对于没有使用的数组空间,造成内存浪费。
链表则是动态申请的内存空间,并不像数组一样需要事先申请好大小,链表是现用现申请就OK, 根据需求动态的申请或删除内存空间,对于的是增加或删除数据,所以比数组要灵活。
再从物理存储即内存分配上分析。
数组是连续的内存,对于访问数据,可以通过下标直接读取,时间复杂度为0(1),而添加删除数据就比较麻烦,需要移动操作数所在位置后的所有数据,时间复杂度为0 (N) 。
链表是物理上非连续的内存空间,对于访问数据,需要从头遍历整个链表直到找到要访问的数据,没有数组有效,但是在添加和删除数据方面,只需要知道操作位置的指针,很方便可以实现增删,较数组比较灵活有效率。
所以综合以上,对于快速访问数据,不经常有添加删除操作的时候选择数组实现,而对于经常添加删除数据,对于访问没有很高要求的时候选择链表。
2、查找算法
二分查找(折半查找)算法:
条件:有序数组
原理:查找过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样 从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
时间复杂度: O(logn)
二叉排序树查找算法:
条件:先创建二叉排序树:
采用二叉树链表作为存储结构
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
原理:在二叉查找树b中查找x的过程为:若b是空树,则搜索失败,否则:若x等于b的根节点的数据域之值,则查找成功;否则:若x小于b的根节点的数据域之值,则搜索左子树;否则:查找右子树。
哈希表法(散列表):
条件:先创建哈希表(散列表)
原理:根据键值方式,(Key value)
进行查找,通过散列函数,定位数据元素。
时间复杂度:几乎是0(1),取决于产生冲突的多少。
分块查找算法:
原理:将n
个数据元素"按块有序"划分为m
块(m≤n)
。每一块中的结点不必有序,但块与块之间必须"按块有序";即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素, 然后使用二分查找和顺序查找。
十八、其他系统框架
1、请概括描述一下Core Location是什么框架?
Core Location Framework
的用途是使用户通过移动设备来获取定位信息和方向信息,还有你的范围,当用户走过某些范围边界就能马上监控到。
在iOS上,甚至可以连同iBeacon
基站联动来确定周边信息。通过低功耗蓝牙技术进行十分精确的微定位和室内导航,据悉其定位精度可以以厘米计算。实现iBeacons
精准的微定位功能除了需要在室内、店内或者其他公共环境中部署iBeacon
基站。当用户走进信号覆盖区域内时,用户就会收到相关的提醒和询问。
当用户走到商场某个店面附近时,安装了相应app
的用户就会收到由iBeacons
基站发出的产品信息或者打折信息。对于开发者来说,可以创建一个更加具有交互性的博物馆应用,当用户在博物馆内随意行走时,通过信息提醒用户某些特别的展览。技术还可以用作室内导航,比如在地铁站或者机场这些GPS
信号不大好的地方更好地引导。
2、Game Center针对iOS游戏有哪些功能?
用户管理和验证:你的游戏不用再考虑用户注册,登录,密码找回,存储分数等繁琐的功能实现。
Game Center
为你的游戏提供了一个本地玩家(local player)
,这个对象为所有的游戏共享,你只需要调用即可;
排行榜:你只需要在iTunesConnect中指定排行方式就可以轻松拥有一个或者多个排行榜;
记录成就:你的游戏需要定义里程碑,调用Game Center
就可以保存进度;
挑战:Game Center
提供了让玩家挑战其他玩家的机制。
3、HealthKit是什么?
HealthKit
是关于健康信息的框架。健康app,这就是Apple提供的一个记录用户健康信息的app,可以用它来分享健康和健身数据。
HealthKit
可以与健身设备一起 工作, 在我们的app中使用了HealthKit
框架之后只要经过用户的认证,就可以在我们的app之中给健康分享数据或者从健康中获取数据。
十九、设计模式
1、简要描述观察者模式?
观察者模式(Observer)
是指一个或多个对象对另一个对象进行观察,当被观察对象发生变化时,观察者可以直接或间接地得到通知,从而能自动地更新观察者的数据,或者进行一些操作。
具体到iOS的开发中, 实现观察者模式常用的方式有KV0
和Notification
两种。
两者的不同在于,KV0
是被观察者主动向观察者发送消息;
Notification
是被观察者向NotifcationCenter
发送消息,再由NotificationCenter
通过post
通知到每个注册的观察者。
2、单例模式的作用?
单例模式的作用是解决应用中只有一个实例的一类问题。常见的单例有UIApplication
、 NSUserDefaults
、 NSNotifcationCenter
、NSFileManager
、 NSBundle
dispatch_ _once
函数是由GCD
提供的,它的作用是在整个应用程序生命周期中只执行一次代码块。dispatch_ _once_ _t
是由GCD提供的结构体,使用时需要将GCD
地址传给dispatch _once
函数。dispatch_ _once
函数是线程同步的,不需要再使用诸如@synchronized
之类的语句。
网友评论