iOS知识点解答
随身带上mac与源码进行学习与介绍
参考:
http://www.infocool.net/kb/IOS/201609/185104.html
http://www.infocool.net/kb/IOS/201609/185061.html
属性
- atomic,nonatomic
参考:http://www.jianshu.com/p/7288eacbb1a2
atomic会让编译器在setter、getter方法中创建lock锁;保证读写安全,但不能保证线程其他操作安全;
nonatomic不就行安全处理,不生成互斥枷锁代码,效率比较高; - assign, weak 修饰对象有啥区别
assign只是指针赋值,不做安全处理,若是对象释放再次访问,相当于访问野指针,导致crash;
weak的对象若是释放就会置为nil,再次访问不会出现问题,比较安全;
函数直接调用与performSelector的区别
本事都是发送消息调用一个方法;
直接调用编译区间如果不存在会报错,performSelector不会报错
performSelector可以调用运行时动态进入的method,但是如果调用不存的selector会报unrecognized selector sent to instance导致crash
isKindOfClass 与 isMemberOfClass的区别
isKIndOfClass 返回一个实例是否是一个类或者其继承类;
isMemberOfClass 返回一个实例是否是一个类,不包括其继承类;
iOS object 原理?
What this says is: any structure which starts with a pointer to a Class
structure can be treated as an objc_object.
一个OC的类其实也是一个对象;
-
meta-class怎么理解?
meta class是一个类对象的类,metaClass的isa全指向root class;meta-class像Class一样,也是一个对象;
当向一个对象发送消息时,runtime会在这个对象所属的那个类的方法列表中查找;
当向一个类发送消息时,runtime会在这个类的meta-class的方法列表中查找;
Paste_Image.png -
isa指针有什么作用
查看NSObject对象的定义,可以看出它有一个‘struct objc_class’的isa变量,struct objc_class中包含有isa指针,成员变量,方法列表等,如下:
typedef struct objc_class *Class;
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
可以看出,每一个类对象中都有一个方法列表,方法列表中记录着方法的名称,实现及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以再方法列表中找到对应的方法实现;
@property后面可以有哪些修饰符?
readwrite, atomic, assign是默认修饰;
--> 读写属性:readwrite readonly
--> setter属性:assign retain copy weak strong
--> 原子属性:atomic nonatomic
--> getter/setter修饰符:@property (setter=setName, getter=getName) NSString *name;
参考:Apple 官方文档说明
消息转发流程
- resolveInstanceMethod 可给类动态添加方法,返回YES后再次调用resolve
- forwardingTargetForSelector 转发给其他的一个对象
-
forwardInvocation 可以将anInvocation转发给多个对象
Paste_Image.png
hitTest / pointInside
先调用hitTest检查窗口是否响应事件,然后调用pointInside检测是否在当前窗口内;
hitTest:withEvent:在内部首先会判断该视图是否能响应触摸事件,如果不能响应,返回nil,表示该视图不响应此触摸事件。然后再调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内)。如果pointInside:withEvent:返回NO,那么hiteTest:withEvent:也直接返回nil。
如果pointInside:withEvent:返回YES,则向当前视图的所有子视图发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历。直到有子视图返回非空对象或者全部子视图遍历完毕;
一个触摸事件 多个响应者同时处理该事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchBegan---%@", [self class]);
[super touchesBegan:touches withEvent:event];
}
怎么用copy关键字?
copy的使用比较智能:
假若一个对象是不可变的,copy返回的是自身,计数器加1;
假若是可变的对象,那么是内存拷贝,返回的是不可变对象;
使用案例:
@property (nonatomic, copy) NSString *pStr;
如果设置这个变量的属性为strong,那么如果外部赋值给它的对象是mutable string,外部mutable string改变的话,这个变量也会跟着变化;此时要使用copy属性,外部mutable string变化也不会影响当前变量;
所以使用NSString属性时候最好声明为copy而不是strong,否则可能外部导致变量内容发生变化;
When you declare a NSString property it is best to use copy instead of strong. In fact this is true for any immutable class that conforms to the NSCopying protocol like NSNumber, NSArray, NSSet and others. All these classes I mentioned also have a mutable version. You want to use copy because your NSString property can be passed either a NSString or a NSMutableString instance. If you’re being passed a NSMutableString instance then that means the value of your string may change behind your back. Let’s consider this example:
- NSMutableArray类型属性变量为什么不能用copy?
因为copy属性表示要使用NSMutableArray 的 copy函数,这个copy返回的是NSArray类型变量,如果对这个属性变量进行可变操作就会导致程序崩溃,因为实际操作的是NSArray;
明白copy属性到底做了什么?一个属性标记了coy,当你调用他的setter方法,他会建立一个索引计数器为1的对象,然后释放就对象;
copy只是浅复制,NSMutableArray的copy返回的是一个NSArray对象;要想深copy可以使用mutableCopy;
参考:https://segmentfault.com/q/1010000004359262?_ea=606650
实际测试(ARC模式下):可以给赋值,但是改变会崩溃,因为实际上copy的是NSArray对象
@property (nonatomic, copy) NSMutableArray *mArr;
self.mArr = [NSMutableArray new];
[self.mArr addObject:@"1"]; //报错
如何让自己的类用copy修饰符?如何重写带copy关键字的setter?
重写带copy关键字的setter只需要在setter函数里调用对象的copy即可;
若想要自己所写的对象具有拷贝功能,需要实现NSCopying协议,有必要也实现NSMutableCopying协议;NSCopying的协议方法为copyWithZone,内容实现为:
- (id)copyWithZone:(NSZone *)zone {
// 需调用allocWithZone
MyClass *copy = [[self class] allocWithZone:zone];
// copy class中的属性数据等
return copy;
}
PS: copy定义在NSObject中,要实现NSCopying协议才能调用,copy的文档说明如下:
This is a convenience method for classes that adopt the NSCopying protocol. An exception is raised if there is no implementation for copyWithZone:.
NSObject does not itself support the NSCopying protocol. Subclasses must support the protocol and implement the copyWithZone: method.
property的本质是什么?
@property = ival + getter + setter, 由编译器自动生成;
property的只是给编译器看的一种指令,可以帮助我们管理引用计数器,可以编译之后为你生成ivar变量以及响应的getter与setter方法;如果指定了retain/copy/assign,也会做出相应处理;
protocol与category中如何使用@property
protocol中定义了属性,说明需要代理实现setter与getter方法,否则会报unrecognized selector错误;
category中使用属性,需要使用runtime函数set或者get变量:
- (NSString *)name {
objc_getAssociateObject(self, @"name");
}
- (void)setName:(NSString *)str {
objc_setAssociateObject(self, @"name", str,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
runtime如何实现weak?
参考:《objc高级编程》
当我们初始化一个weak变量时,runtime会调用objc_initWeak函数将weak变量存储在weak表中, key是weak赋值对象的内存地址;
weak表:一个弱引用表,实现为一个weak_table_t结构体,存储了对象相关的所有的弱引用信息,weak_entries成员变量负责维护和存储指向一个对象的所有弱引用hash表;
对象释放流程如下:
1.调用objc_release;
2.计数器变为0,执行dealloc;
3.在dealloc中,调用_objc_rootDealloc函数;
4.在_objc_rootDealloc中,调用了object_dispose函数;
5.调用objc_destructInstance;
6.最后调用objc_clear_deallocating -->
objc_clear_deallocating这个函数首先找出对象的weak_entry_t链表,然后挨个将弱引用置为nil,最后清理对象的记录;
load 与 initialize
PS:在Compile Sources中,文件的排放顺序就是起装载顺序,自然也就是load方法调用的顺序;
initialize方法是在第一次给某个类发送消息时调用,并且只会调用一次;
Paste_Image.png
UITableView优化?
UITableView原理
UITableViewCell的重用机制能够提高cell的使用效率,减少了内存消耗;
UITableview是先计算cell的高度确定将要显示的cell(所有cell的height都会刷新),再显示cell(只刷新显示的cell),所以主要优化在 tableView:heightForRowAtIndexPath 与 tableView:cellForRowAtIndexPath 方法;
heightForRowAtIndexPath是调用最频繁的方法;
- height计算与cell填充分离开,不要重叠;
- 缓冲数据直接读取,数据不需要动态更新的,不需要每次计算;
- 直接绘绘制控件(在cell上添加系统控件的时候,实质上系统都需要调用底层的接口进行绘制,当我们添加大量控件时,对资源的开销很大,所以可以直接绘制,提高效率);
//异步绘制
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGRect rect = [_data[@"frame"] CGRectValue];
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
//整个内容的背景
[[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];
CGContextFillRect(context, rect);
//内容如果是图文混排,就添加View,用CoreText绘制
//图文混排,可以查阅Google,研究下CoreText,内容比较多
[self drawText];
}
- 滑动很快时,按需加载目标范围内的cell,可提高流畅度;在大量图片展示时,网络加载比较有效(SDWebImage已经实现异步加载);
- 尽量使所有的view opaque(不透明),包括cell自身;
- 尽量少用或不用透明图层(非常耗时);
- 避免使用圆角、阴影、遮罩等属性;
- 如果cell内显示的内容矮子web,使用异步加载,缓存请求结果;
- 减少subviews的数量;
- 在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果;
- 尽量少用addView给Cell动态添加View,开始初始化时添加,通过hide来控制显示;
- 想提高效率,还是手动写cell,不要用xib或storyboard(看情况而论);
- 将GPU的部分渲染转给CPU,GPU与CPU的合理分配使用(了解gitbook上图像渲染的知识点);
- 在drawRect中使用Core Graphics 绘制控件;
参考:
10 tips to speed up your table view
stack overflow
GCD 任务控制?
Mac OS X 10.6, iOS4 以上可以使用;
- 完成一些任务后再完成另一个任务?
使用 dispatch_group_async / dispatch_group_notify; - dispatch_barrier_async使用?
是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行; - dispatch_apply使用?
执行某个代码块N次: dispatch_apply(N, queue ^(){ /* code */ }); - 与NSOperation的区别?
Paste_Image.png
参考:
https://www.appcoda.com/ios-concurrency/
https://cocoacasts.com/choosing-between-nsoperation-and-grand-central-dispatch/
http://www.jianshu.com/p/fe1fec3d198f
Core Data 操作及多线程处理
CoreData实际上是对SQLite的封装,提供了更高级的持久化方式;
CoreData操作简单方便,但是存储性能一般,批处理数据不好,而且对于多线程的支持也不太好;
Core Data 堆栈
对象图管理
持久化
-
相关类
Paste_Image.png
参考:
Apple CoreData
Core Data 概述
一个完整的Core Data应用
objc.io core-data
ios8 swift core data tutorial
NSOperation 自定义?
参见AFNetWorking的 AFHTTPRequestOperation.h
重写 main 函数:处理耗时操作后,返回回调处理;
运行时添加变量?
不能向编译后得到的类中增加实例变量,能向运行时创建的类中添加实例变量;
because:
- 编译后的类已经注册在runtime中,类结构中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,所以不能向存在的类中添加实例变量;
- 运行时创建的类可以添加实例变量,调用class_addIvar函数但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上;
runloop与线程?
runloop是为了线程而生,没有线程就没有存在的必要;内部是一个do while循环类似的结构,比如一个event消息事件处理线程;
- 主线程的runloop默认是启动的;
- 其他线程,runloop默认是没有启动的,如果需要更多的线程交互可以手动配置和启动,只是执行一个长时间的已确定任务则不需要;
- 任何一个cocoa程序的线程中,都可以通过 [NSRunLoop currentRunLoop] 获取到当前线程的runloop;
《Objective-C之run loop详解》
《深入理解RunLoop》
runloop的mode作用?
mode主要用来指定事件在运行循环中的优先级;
- NSTimer 与 ScrollView 的问题
RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的;ScrollView滚动过程中会从NSDefaultRunLoopMode切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动;如果同时有NSTimer,那么就会在滑动的时候因为切换mode导致NSTimer将不再被调用;所以需要将timer设置为common mode:
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSRunLoopCommonModes]; - NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
- UITrackingRunLoopMode:ScrollView滑动时
- UIInitializationRunLoopMode:启动时
- NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
autoreleasepool
autoreleas对象释放机制?
- 指定autoreleasepool结束时(当前作用域大括号结束时),对象释放;
- 系统自动释放:autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的runloop迭代结束时释放;
main中的autoreleasepool如果不释放,迟早会被撑满,所以在一次完整的运行循环结束之前,对象会被销毁;
Paste_Image.png
如果在一个vc的viewdidload中国创建一个autorelease对象,那么对象在viewdidapper方法执行之前就被销毁了;
当自动释放池被销毁或耗尽时,会向自动释放池的所有对象发送release消息,释放自动释放池中的所有对象;
autoreleasepool的实现:
主要通过三个函数:
- objc_autoreleasepoolPush
- objc_autoreleasepoolPop
- objc_autorelease
《黑幕背后的Autorelease》
BAD_ACCESS发生在什么情况?
- 访问量了野指针
- 对已经释放的对象执行了release
- 访问已经释放对象的成员变量或发消息
- 死循环
block内修改外部变量
原则:block不允许修改外部变量的值,是指占中指针的内存地址;栈区是红灯区不可修改,堆区才是绿灯区可修改;
使用__block修饰外部变量,实际上是把变量从栈区修改到堆区中了,可使用例子验证:
__block int a = 1;
NSLog(@"%p", &a); // 在栈区
void (^foo)(void) = ^ {
a = 2;
NSLog(@"%p", &a); //在堆区 1
};
NSLog(@"%p", &a); //在堆区 2
foo();
如何手动触发KVO
- KVO原理
当你观察一个对象时,一个新的类会被动态创建。
这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法;
重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改;
最后通过 isa 混写(isa-swizzling)把这个对象的 isa 指针
( isa 指针告诉 Runtime 系统这个对象的类是什么 )
指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例;
键值观察通知依赖于NSObject的两个方法:willChangeValueForKey和didChangeValueForKey;
系统会在setValue(成员变量赋值)或者setter(属性赋值)中以某种方法在中间插入wilChangeValueForKey:
、 didChangeValueForKey:
和 observeValueForKeyPath:ofObject:change:context:
的调用;
什么方式?通过isa-swizzling;
Manual Change Notification---Apple 官方文档
- 如果需要主动触发kvo,手动调用这两个函数包裹赋值,就可以实现手动触发了;
kvo只能观察基本数据类型的内容或者对象的指针变化,如果NSMutableArray 或者 NSMutableString 内容变化是无法观察到的,所以当内容变化时需要手动触发;
《如何自己动手实现 KVO》
IBOutlet连出来的视图属性为什么可以被设置成weak?
Should IBOutlets be strong or weak under ARC?
视图本身已经对它有一个强引用了;
如何调试BAD_ACCESS?zombie object
- Enable Zombie Objects;
- 全局断点开启;
- xcode7的 Address Sanitizer;
- 重写object的respondsToSelector方法,显示出现bad access前最后访问的一个object;
LLVM原理及优化?
LLVM:Low Level Virtual Machine 底层虚拟机
LLVM是架构编译器的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间、链接时间、运行时间及空闲时间,对开发者保值开放;LLVM计划启动于2000年,2006年加盟Apple Inc;Apple也是LLVM计划的主要资助者;
Clang是LLVM的一个编译器前端,它目前支持C/C++/Object-C以及Object-C++等变成余元;clang对源程序进行词法分析和语义分析,并将分析结果转换为抽象语法树,最后使用LLVM作为后端代码生成器;
meituan 面试部分
-
函数式编程的定义,ios怎么用?
Paste_Image.png
参考:https://www.zhihu.com/question/28292740
函数式语言:scala 、erlang
函数式编程是一种逻辑式编程,关心数据的映射(对应关系的函数),而不是解决问题的步骤;严格意义的函数式编程意味着不使用可变的变量,赋值,循环和其他命令式控制结构进行编程;
函数式语言是面向数学的抽象,更接近人的语言,而不是机器语言,代码会比较简洁,也更容易被理解;描述做什么,而不是怎么做;
特点:变量不可变性,函数作为主体;
不变性好处是:不共享状态不会造成资源征用,也不需要用锁来保护可变状态,也不会出现思索,可以更好的并发起来;
因为不可变,所以for while循环(需要可变状态跳出循环)不可用;因此函数式语言里就只能使用递归来解决迭代问题,使得函数式编程严重依赖递归;
Masonry库使用的语法就是函数式编程,可查看源码实现;
实际调用函数返回的是一个block函数,再对block函数进行参数调用,通过block函数式可实现链式编程,如:
-
MVVM你怎么组织,RAC的核心方法是什么?
Paste_Image.png
参考: https://www.objccn.io/issue-13-1/
MVC的使用会使controller中集中大量代码,臃肿不堪;
什么样的内容才应该放到controller中?不便复用的代码放在其中;
如何对viewcontroller瘦身?《Lighter View Controllers》
MVVM将controller里面过于臃肿的逻辑抽取出来,形成新的可复用模块或架构;
那么,把原来viewcontroller层的业务逻辑和页面逻辑等剥离出来放到viewmodel层,就形成了 model-view-viewmodel结构;保证了业务与UI分离;
viewmodel层处理表示逻辑,将controller中的逻辑提取出来;
model只用来表示数据模型;
view只用来进行数据显示及交互;
与MVC兼容带来了更轻量级的 View Controller;
kvo可以监听model的变化,时而更新view;
***RAC**** Reactivecocoa
参考:http://www.jianshu.com/p/3beb21d5def2
https://zhuanlan.zhihu.com/p/22959809
结合了函数式编程和响应式编程的框架
将ios的delegate、kvo、通知等异步处理方式都用 signal 代替来统一处理; -
ReactNative还有jspatch这种js转原生的实现原理是什么?
ReactNative原理
jspatch原理:利用OC消息转发机制,替换原有的IMP指针,实现替换增加方法处理;class_addMethod, class_replaceMethod
jspatch核心是根据运行时修改函数实现来处理的? -
运行时机制中,@selector这样的东西是用汇编写的,为什么?
Paste_Image.png
保证 objc_msgSend 最快的执行速度;
objc_msgSend 核心是查找IMP,为了性能的优化还是得使用一些汇编代码;
@selector 会在运行时通过obj_msgSend查到对应的IMP指针;
参考:http://www.cocoachina.com/ios/20150812/12992.html
因为没有方法可以将传入C函数的泛参传给另一个函数,你可以使用变参,但是变参和普通参数的传递方法不同,而且慢,所以这不适合普通的C参数;
在apple runtime中,为了最大化速度,objc_msgSend整个函数是使用汇编实现的;因为一个最简单的动作都会使用objc_msgSend,需要使用汇编来提高执行效率;
部分源码如下(objc-msg-arm.s 中):
-
ismemberofclass和isKindOfClass的底层区别?
查看objc源码,说明ismemberofclass判断是否是同一个类,不包括父类;
Paste_Image.png -
category为什么不能添加成员变量?它什么时候加载?
参考:http://www.tuicool.com/articles/QBbeQzv
美团酒店事业部 category
OC中记录当前类属性的ivars无法动态改变;动态创建的类可添加变量;
调用过程:_objc_init --> map_images --> remethodizeClass --> attachCategoryMethods
category原理:类也是一个对象,在运行时,将category和他的主类注册到哈希表,重构他的方法列表;通过remethodizeClass函数来重新整理类的数据;
PS:无论我们有没有主动引用Category的头文件,其中的方法都会被添加进主类中; -
iOS 锁
@synchronized
NSLock 互斥锁
NSRecursiveLock 能多次锁不造成死锁,但必须多次解锁,可使用在递归函数中防止引起死锁;
NSConditionLock 发送某个条件时解锁
NSCondition lock signal unlock
APP跳转
调用openUrl跳转到指定app;
- 微信跳转流程:
官方使用文档
调用sendReq发送请求,处理完毕后会向第三方程序发送一个处理结果SendMessageToWXResp数据;
在app delegate中,实现WXApiDelegate的onResp函数,用于接收response结果,并弹出对应的提示; - openURL / handleOpenURL
openURL其他: 是你通过打开一个url的方式打开其他应用;
openURL:sourceApplication / handleOpenURL: 是其他应用通过你app中设置的URL scheme打开你的应用;
后台持续运行机制
多米音乐播放器会持续运行
可以持续运行的是:音乐播放、GPS定位服务、voip、后台下载、fetch;
- voip
voip是使用IP进行通信的语音传输协议;
VOIP在iOS运行的原理:当进行后台之后,系统托管NSStream(TCP)的通道,如果服务器有信息要传递到客户端,系统会激活处于后台的程序运行10秒钟(10秒钟之内都可以,可以设置为6秒,或者8秒之类的),这个时候可以通过LocalNotification来提示用户有消息; - 定位服务
进入后台时,调用startUpdatingLocation启动定位,定位服务可用的时候,程序会不断刷新后台时间,达到长久后台的目的; - 音乐播放
开启一段空的语音一直播放 - 后台下载
启动一个后台下载任务, backgroundSessionConfiguration,(AFNetWorking使用的就是这个配置) - fetch和remote-notification
iOS7新增的,可以在适当的时候在后台唤醒app;
PS:静默推送能够让收到推送后先拉取数据,再提醒用户;