-
熟悉 CocoaPods 么?能大概讲一下工作原理么?
一、什么是CocoaPods
CocoaPods是iOS项目的依赖管理工具,该项目源码在Github上管理。开发iOS项目不可避免地要使用第三方开源库,CocoaPods的出现使得我们可以节省设置和第三方开源库的时间。
在使用CocoaPods之前,开发项目需要用到第三方开源库的时候,我们需要
1.把开源库的源代码复制到项目中
2.添加一些依赖框架和动态库
3.设置-ObjC,-fno-objc-arc等参数
4.管理他们的更新
在使用CocoaPods后,我们只需要把用到的开源库放到一个名为Podfile的文件中,然后执行pod install.Cocoapods就会自动将这些第三方开源库的源码下载下来,并且为我们的工程设置好响应的系统依赖和编译参数。二、CocoaPods的原理
CocoaPods的原理是将所有的依赖库都放到另一个名为Pods的项目中,然后让主项目依赖Pods项目,这样,源码管理工作都从主项目移到了Pods项目中。Pods项目最终会编译成一个名为libPods.a的文件,主项目只需要依赖这个.a文件即可
-
如何实现单例,单例会有什么弊端?
由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
-
单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
-
什么是响应链,它是怎么工作的?
响应者对象指的是有响应和处理事件能力的对象。所有响应者对象都是继承与 All [UIResponder](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIResponder_Class/index.html#//apple_ref/occ/cl/UIResponder) (iOS) or NSResponder (OS X). 响应者对象以一个链的形式串联起来,当第一个响应者对象不能处理事件时,他将事件转发给下一个响应者对象。
-
iOS 的沙盒目录结构是怎样的? App Bundle 里面都有什么?
默认情况下,每个沙盒含有1个应用程序包MyApp.app和3个文件夹:Documents, Library 和 tmp。因为应用的沙盒机制,应用只能在几个目录下读写文件。
-
MyApp.app:
应用程序包,这里面存放的是应用程序的源文件,包括资源文件和可执行文件。
Documents: 苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下
Library:存储程序的默认设置或其它状态信息
Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除
tmp:提供一个即时创建临时文件的地方。
itunes同步时,备份所有的Documents和Library文件(Library/Caches除外)。
iPhone在重启时,会丢弃所有的tmp文件。
-
-
如何访问并修改一个类的私有属性?
### 方法一:KVC(键值编码)
// .h文件 @interface Person : NSObject @end // .m文件 @interface Person () //name为私有属性 @property (nonatomic, copy) NSString *name; @end @implementation Person @end // 某控制器 引用了 Person类 @implementation ViewController - (void)viewDidLoad{ [super viewDidLoad]; Person *p = [Person new]; //修改私有属性的值 [p setValue:@"yyMae" forKey:@"name"]; //访问私有属性的值 NSString *name = [p valueForKey:@"name"]; }
方法二:通过runtime获取或修改一个类私有属性的值
// .h文件 @interface Person : NSObject @end // .m文件 @interface Person () //name为私有属性 @property (nonatomic, copy) NSString *name; @end @implementation Person @end // 某控制器 引用了 Person类 @implementation ViewController - (void)viewDidLoad{ [super viewDidLoad]; Person *p = [Person new]; Ivar m_name = NULL; // IVar是runtime声明的一个宏 unsigned int count = 0; //count记录变量的数量 // 获取类的所有属性变量 Ivar *members = class_copyIvarList([Person class], &count); for (int i = 0; i < count; i++) { Ivar ivar = members[i]; // 取得属性名并转成字符串类型 const char *memberName = ivar_getName(ivar); NSLog(@"%s",memberName); if(strcmp(memberName,"_name")) { m_name = ivar; } } // 修改属性值 object_setIvar(Person, m_name, @"yyMae"); }
-
_objc_msgForward
函数是做什么的,直接调用它将会发生什么?调用
resolveInstanceMethod:
方法 (或resolveClassMethod:
)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend
流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod
。如果仍没实现,继续下面的动作。调用
forwardingTargetForSelector:
方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。调用
methodSignatureForSelector:
方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector
抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:
。调用
forwardInvocation:
方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。-
调用
doesNotRecognizeSelector:
,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。如何调用
_objc_msgForward
?_objc_msgForward
隶属 C 语言,有三个参数 :-- _objc_msgForward
参数类型 1. 所属对象 id类型 2. 方法名 SEL类型 3. 可变参数 可变参数类型
- runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
-
能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 不能向编译后得到的类中增加实例变量;
- 能向运行时创建的类中添加实例变量;
解释下:
- 因为编译后的类已经注册在 runtime 中,类结构体中的
objc_ivar_list
实例变量的链表 和instance_size
实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout
或class_setWeakIvarLayout
来处理 strong weak 引用。所以不能向存在的类中添加实例变量; - 运行时创建的类是可以添加实例变量,调用
class_addIvar
函数。但是得在调用objc_allocateClassPair
之后,objc_registerClassPair
之前,原因同上。
-
run loop了解情况
runloop的mode作用是什么? model 主要是用来指定事件在运行循环中的优先级的,分为:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
- UITrackingRunLoopMode:ScrollView滑动时
- UIInitializationRunLoopMode:启动时
- NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
苹果公开提供的 Mode 有两个:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
- NSRunLoopCommonModes(kCFRunLoopCommonModes)
-
objc使用什么机制管理对象内存?
通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。
-
ARC通过什么方式帮助开发者管理内存?
ARC相对于MRC,不是在编译时添加retain/release/autorelease这么简单。应该是编译期和运行期两部分共同帮助开发者管理内存。
在编译期,ARC用的是更底层的C接口实现的retain/release/autorelease,这样做性能更好,也是为什么不能在ARC环境下手动retain/release/autorelease,同时对同一上下文的同一对象的成对retain/release操作进行优化(即忽略掉不必要的操作);ARC也包含运行期组件,这个地方做的优化比较复杂,但也不能被忽略。
-
不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
分两种情况:手动干预释放时机、系统自动去释放。
1. 手动干预释放时机--指定autoreleasepool 就是所谓的:当前作用域大括号结束时释放。
2. 系统自动去释放--不手动指定autoreleasepool
Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。
释放的时机总结起来,可以用下图来表示:
![image](http://upload-images.jianshu.io/upload_images/3402172-402b79bc96e0803c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop
迭代结束时释放的,而它能够释放的原因是**系统在每个runloop迭代中都加入了自动释放池Push和Pop**
-
BAD_ACCESS在什么情况下出现?
访问了悬垂指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环
-
使用block时什么情况会发生引用循环,如何解决?
一个对象中强引用了block,在block中又强引用了该对象,就会发生循环引用。
解决方法是将该对象使用`__weak`或者`__block`修饰符修饰之后再在block中使用。
-
在block内如何修改block外部变量?
我们都知道:**Block不允许修改外部变量的值**,这里所说的外部变量的值,指的是栈中指针的内存地址。`__block` 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。
```objective-c
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger i = 5;
NSLog(@"block之前 %p",&i);
void (^block)(void) = ^{
NSLog(@"block内部使用中 %p",&i);
};
block();
NSLog(@"block之后 %p",&i);
}
```
打印结果
```objective-c
2017-07-08 12:36:18.255 BLOCKTEST[92308:464948] block之前 0x7fff55ddfa98
2017-07-08 12:36:18.256 BLOCKTEST[92308:464948] block内部使用中 0x60000005af30
2017-07-08 12:36:18.256 BLOCKTEST[92308:464948] block之后 0x7fff55ddfa98
```
-
如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
```objective-c
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
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(), ^{
// 合并图片
});
```
方法二:
~~~objective-c
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i ++)
{
dispatch_group_enter(group);
// 任务代码i 假定任务 是异步执行block回调
// block 回调执行
dispatch_group_leave(group);
// block 回调执行
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程处理
});
~~~
-
dispatch_barrier_async
的作用是什么?
注意:使用 `dispatch_barrier_async` ,该函数只能搭配自定义并行队列 `dispatch_queue_t` 使用。不能使用: `dispatch_get_global_queue` ,否则 `dispatch_barrier_async` 的作用会和 `dispatch_async` 的作用一模一样.
~~~objective-c
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT); // 需要自己创建
dispatch_async(myConcurrentQueue, ^{ // 1.2是并行的
NSLog(@"dispatch test 1");
});
dispatch_async(myConcurrentQueue, ^{
NSLog(@"dispatch test 2");
});
dispatch_barrier_async(myConcurrentQueue, ^{ // 等1.2都执行完便会执行此方法,此时便会将线程延迟直至barrier执行完毕方可
NSLog(@"dispatch barrier");
});
dispatch_async(myConcurrentQueue, ^{ // 这两个是同时执行的
NSLog(@"dispatch test 3");
});
dispatch_async(myConcurrentQueue, ^{
NSLog(@"dispatch test 4");
});
}
~~~
输出结果
~~~
2017-07-08 14:30:29.554 BLOCKTEST[92395:472732] dispatch test 2
2017-07-08 14:30:29.554 BLOCKTEST[92395:472729] dispatch test 1
2017-07-08 14:30:29.555 BLOCKTEST[92395:472729] dispatch barrier
2017-07-08 14:30:29.555 BLOCKTEST[92395:472729] dispatch test 3
2017-07-08 14:30:29.555 BLOCKTEST[92395:472732] dispatch test 4
~~~
-
苹果为什么要废弃
dispatch_get_current_queue
?
`dispatch_get_current_queue`容易造成死锁
-
为什么
dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); });
会发生死锁?
因为sync会阻塞当前线程,去执行队列任务,但是因为主线程队列实在主线程中运行,被阻塞了无法执行相应任务,就发生了死锁。
使用dispatch_sync 同样也是把block放到指定的queue上面执行,但是会等待这个block执行完毕才会返回,阻塞当前queue直到sync函数返回。所以队列是串行、并行 和 同步、异步执行调用block是两个完全不一样的概念。这两个概念清楚了之后就知道为什么死锁了。分两种情况:1、当前queue是串行队列。当前queue上调用sync函数,并且sync函数中指定的queue也是当前queue。需要执行的block被放到当前queue的队尾等待执行,因为这是一个串行的queue,调用sync函数会阻塞当前队列,等待block执行 -> 这个block永远没有机会执行 -> sync函数不返回,所以当前队列就永远被阻塞了,这就造成了死锁。(这就是问题中在主线程调用sync函数,并且在sync函数中传入main_queue作为queue造成死锁的情况
-
怎么获取到用户使用APP的fps
CoreAnimation有一个很好用的类`CADisplayLink`,这个类会在每一帧绘制之前调用,并且可以获取时间戳。于是,我们只要统计出,在1s内的帧数即可。
~~~objective-c
- (void)envokeDisplayLink:(CADisplayLink *)displayLink{
if (_lastTimestamp == -1) {
_lastTimestamp = displayLink.timestamp;
return;
}
_countPerFrame ++;
NSTimeInterval interval = displayLink.timestamp - _lastTimestamp;
if (interval < 1) {
return;
}
_lastTimestamp = displayLink.timestamp;
CGFloat fps = _countPerFrame / interval;
//...
}
~~~
-
若一个类有实例变量
NSString *_foo
,调用setValue:forKey:时,可以以foo还是_foo
作为key?
都可以。
-
apple用什么方式实现对一个对象的KVO?
> 当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 `isa 混写(isa-swizzling)` 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例
-
IBOutlet连出来的视图属性为什么可以被设置成weak?
![image](http://upload-images.jianshu.io/upload_images/3402172-c44e793858be7997.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-
frame,bounds,center,alpha,opaque,hidden
这些都是view的一些基本属性。
> frame是描述该view在其父视图中的一块区域。其坐标系是在其父视图中的坐标。
>
> bounds也是描述该view的大小,是其在自身的坐标系中的位置大小。
>
> center是描述其在父视图的中心位置坐标。
>
> alpha是用来描述改view的透明度从0到1,0表示的是透明,1表示不透明。alpha支持动画(animation),alpha = 0 与 hidden = YES 效果一样都是看不到view,但是后者相比开销大。并且hidden和apaque 不支持动画。alpha并不影响镶嵌在其内部view行为,而hidden会影响。当把view设置为透明背景时,一般把opaque设置为NO,可以减少开销,优化内存.opaque影响图形绘制系统。设置为YES,会优化view的绘制。
>
> opaque也是表示当前的UIView的不透明度,设置是否之后对于UIView的显示并没有什么影响,官方文档的意思简单点说就是opaque默认为YES,如果alpha小于1,那么应该设置opaque设置为NO,但是如果alpha为1,opaque设置为NO,产生的后果是不可预料的~
-
nil,NSNULL,NULL区别
> nil是指向obj-c中对象的空指针,是一个对象,在o-c中ni对象调用方法不会引起crash。
>
> Nil是指向obj-c中的类的空指针,表示的是一个空类。
>
> NULL是指向任何类型的空指针(如c/c++中的空指针),在objective-c中是一个数值。
>
> NSNULL用于集合操作,在集合对象中,表示一个空值的集合对象。
-
iOS Extension 是什么?能列举几个常用的 Extension 么?
> Extension是Category的一个特例,没有分类名字,可以扩展属性,成员变量和方法。
> 常用的扩展是在.m文件中声明私有属性和方法,基本上我们天天都在用。
-
如何把一个包含自定义对象的数组序列化到磁盘?
对自定义对象的类实现NSCoding协议,进行归档解档。
-
iOS 7的多任务添加了哪两个新的 API? 各自的使用场景是什么?
- 后台获取(Background Fetch):后台获取使用场景是用户打开应用之前就使app有机会执行代码来获取数据,刷新UI。这样在用户打开应用的时候,最新的内容将已然呈现在用户眼前,而省去了所有的加载过程。
- 推送唤醒(Remote Notifications):使用场景是使设备在接收到远端推送后让系统唤醒设备和我们的后台应用,并先执行一段代码来准备数据和UI,然后再提示用户有推送。这时用户如果解锁设备进入应用后将不会再有任何加载过程,新的内容将直接得到呈现。
-
iOS 的签名机制大概是怎样的?
假设,我们有一个APP需要发布,为了防止中途篡改APP内容,保证APP的完整性,以及APP是由指定的私钥发的。首先,先将APP内容通过摘要算法,得到摘要,再用私钥对摘要进行加密得到密文,将源文本、密文、和私钥对应的公钥一并发布即可。那么如何验证呢?
验证方首先查看公钥是否是私钥方的,然后用公钥对密文进行解密得到摘要,将APP用同样的摘要算法得到摘要,两个摘要进行比对,如果相等那么一切正常。这个过程只要有一步出问题就视为无效。
-
UIScrollView 大概是如何实现的,它是如何捕捉、响应手势的?
UIScrollView在滚动过程当中,其实是在修改原点坐标。当手指触摸后, scroll view会暂时拦截触摸事件,使用一个计时器。假如在计时器到点后没有发生手指移动事件,那么 scroll view 发送 tracking events 到被点击的 subview。假如在计时器到点前发生了移动事件,那么 scroll view 取消 tracking 自己发生滚动。
-
如何让 Category 支持属性?
- 使用runtime可以实现
头文件
```objective-c
@interface NSObject (test)
@property (nonatomic, copy) NSString *name;
@end
```
.m文件
```objective-c
@implementation NSObject (test)
// 定义关联的key
static const char *key = "name";
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
```
-
NSOperation 相比于 GCD 有哪些优势?
- 提供了在 GCD 中不那么容易复制的有用特性。
- 可以很方便的取消一个NSOperation的执行
- 可以更容易的添加任务的依赖关系
- 提供了任务的状态:isExecuteing, isFinished.
-
如何为 Class 定义一个对外只读对内可读写的属性?
在头文件中将属性定义为`readonly`,在.m文件中将属性重新定义为`readwrite`
-
HTTP协议中POST、GET、HEAD、PUT等请求方法以及一些常见错误
-
block与函数指针有什么区别
> 第一个区别,函数指针是对一个函数地址的引用,这个函数在编译的时候就已经确定了。而block是一个函数对象,是在程序运行过程中产生的
函数指针仅仅是一个地址,不具备函数原型信息,没有类型限制,比如一个指向变量的指针同样可以指向一个函数,但是block作为函数对象,是有部分函数信息的,类型限制更明确。
提高程序的健壮性, 定义函数的代码会位于程序的代码段,如果函数内部出现内存溢出,就会直接导致crash,因为代码段是不可写的;block作为函数对象在运行时生成,位于栈内,即使出现内存溢出,一般也不会直接导致crash.