趁着开年的空闲时间,找了一些面试题写写,算是回顾总结一下iOS开发方面的知识, 水平渣,答案仅做参考! 欢迎指导讨论,写的不对或不妥的地方望及时提出! 原题在这里html
iOS常见面试题基础篇(附参考答案)看这里ios
Blockgit
block的实质是什么?一共有几种block?都是什么状况下生成的?github
简单的来说,block在OC中的表现能够看做为带有自动变量(局部变量)的匿名函数。面试
C语言函数定义的时候,能够将函数的地址赋值给函数指针类型的变量,以下:数据结构
int func(int count){
return count + 1;
}
// 赋值
int (*funcprt)(int) = &func;
int result = (*funcprt)(10);复制代码
同理,咱们也能够将block语法赋值给声明为block类型的变量中。以下:函数
// 声明一个block类型的变量,其与函数指针类型的变量不一样之处只有'*'改成'^'
int (^blk)(int);
// 赋值
int (^blk)(int) = ^(int count){return count + 1;};
int result = blk(10);复制代码
还能够经过typedef来声明blk_t
类型变量,以下:oop
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1;};
int result = blk(10)复制代码
以上解释了匿名函数,如今来解释一下带有自动变量post
int value = 10;
void(^blk)() = ^{
NSLog(@"value === %d", value);
};
blk();
value = 2;
NSLog(@"%@", value);复制代码
value结果为 10 。在block中,block表达式截获所使用的自动变量的值,即保存该变量的瞬间值,因此在执行了block后,改变block外自动变量的值,并不会影响block执行时自动变量的值。ui
block的本质
关于block的本质, ibireme大神objc 中的 block这篇博客里,有详细的分析。
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};复制代码
根据block的数据结构能够发现,它是含有*isa
指针的,在OC中根据对象的定义,凡是首地址是*isa的结构体指针,均可以认为是对象(id)。这样在objc中,block实际上就算是对象。
既然OC处理Block是按照对象来处理的。在iOS中,常见的就是_NSConcreteStackBlock
,_NSConcreteMallocBlock
,_NSConcreteGlobalBlock
这3种,还有另外几种,暂不作讨论。
ARC下:
void(^blockA)() = ^{
NSLog(@"just a block");
};
NSLog(@"%@", blockA);
int value = 10;
void(^blockB)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockB);复制代码
ARC下打印结果以下:
<__NSGlobalBlock__: 0x10c81c308> <__NSMallocBlock__: 0x60400025d160>
MRC下打印结果以下: <__NSGlobalBlock__: 0x1056bf308> <__NSStackBlock__: 0x7ffeea540a48>
咱们对栈上的block作copy操做:
NSLog(@"%@", [blockB copy])
结果为: <__NSMallocBlock__: 0x600000444b60>
由此咱们能够得出如下结果:
当block没有引用外部局部变量的时候,block为全局block
当block引用外部局部变量的时候,ARC下为堆block
,MRC下为栈block
,此时对MRC下的栈block进行copy,栈block
就变为堆block
。在ARC下,编译器会把block从栈拷贝到堆。
经验证,当block只引用静态变量,全局变量的时候,block均为全局block
为何在默认状况下没法修改被block捕获的变量? __block都作了什么?
__block
的自动变量,通过编译后会变成一个结构体,经过结构体中的__forwarding
指针能够访问到变量,天然就能够修改变量了。模拟一下循环引用的一个状况?block实现界面反向传值如何实现?
循环引用场景
// 当block做为属性时:
@property(nonatomic, copy) void(^block)();
self.block = ^{
NSLog(@"%@",self);
}
复制代码
此时会出现警告:
界面反向传值
//SecondViewController.h
#import < UIKit/UIKit.h>
typedef void(^CallBackBlock) (NSString *string);
@interface SecondViewController : UIViewController
@property (nonatomic,strong)UItextField *textField;
@property (nonatomic,copy)CallBackBlcok callBackBlock;
@end
// 在implementation中添加一个点击事件:
- (IBAction)click:(id)sender {
self.callBackBlock(_textField.text);
[self.navigationController popToRootViewControllerAnimated:YES];
}
复制代码
在FirstViewController中:
// FirstViewController.m
- (IBAction)push:(id)sender {
SecondViewController *secondVC = [[SecondViewController alloc]init]
secondVC.callBackBlock = ^(NSString *string){
NSLog(@"string is %@",string);
self.label.text = string;
};
[self.navigationController pushViewController:secondVC animated:YES];
}复制代码
思考一下这个问题: ARC下会发生什么? MRC呢?若blockA
并无引用自动变量val
的话状况又是什么样?
@property(nonatomic, weak) void(^block)();
- (void)viewDidLoad {
[superviewDidLoad];
int val = 10;
void(^blockA)() = ^{
NSLog(@"val:%d", val);
};
NSLog(@"%@", blockA);
_block = blockA;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"%@", _block);
}复制代码
Runtime
objc在向一个对象发送消息时,发生了什么?
众所周知,在objc中方法调用的本质是发消息,例如:
[obj message];
// 运行时会转化为:
objc_msgSend(obj, selector)
// 当有参数时:
[obj message:(id)arg...];
// 运行时会转化为:
objc_msgSend(obj, selector, arg1, arg2, ...)复制代码
消息在运行时才会与方法绑定
当向一个对象发送消息时:
1.首先检测这个 selector 是否是要忽略。好比 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数。
2.检测这个 selector 的 target 是否是 nil,Objc 容许咱们对一个 nil 对象执行任何方法不会 Crash,由于运行时会被忽略掉。
3.若是上面两步都经过了,那么就开始查找这个类的实现 IMP,先从 cache 里查找,若是找到了就运行对应的函数去执行相应的代码。
4.若是 cache 找不到就找类的方法列表中是否有对应的方法。 若是类的方法列表中找不到就到父类的方法列表中查找,一直找到 NSObject 类为止。
5.若是还找不到,就要开始进入消息转发流程
以下图所示:
何时会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
当调用某对象上某个方法,而该对象并无实现这个方法的时候, 能够经过消息转发进行解决。
消息转发步骤以下:
objc运行时会调用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,让咱们有机会提供一个函数实现。若是你添加了函数,那运行时系统就会从新启动一次消息发送的过程,不然,运行时就会移到下一步,消息转发(Message Forwarding)。
调用forwardingTargetForSelector:
方法,尝试找到一个能响应该消息的对象。若是获取到,则直接转发给它。若是返回了nil,继续下面的动做。
调用methodSignatureForSelector:
方法,尝试得到一个方法签名。若是获取不到,则直接调用doesNotRecognizeSelector抛出异常。若是返回了一个方法签名,Runtime就会建立一个NSInvocation对象,而后继续进行第四步
调用forwardInvocation:
方法,将地3步获取到的方法签名包装成Invocation
传入,如何处理就在这里面了。
如图所示:
可否向编译后获得的类中增长实例变量?可否向运行时建立的类中添加实例变量?为何?
runtime如何实现weak变量的自动置nil?
给类添加一个属性后,在类结构体里哪些元素会发生变化?
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;
/* Use `Class` instead of `struct objc_class *` */复制代码
instance_size
和属性列表:objc_ivar_list *ivars
会发生改变。RunLoop
runloop是来作什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
runloop字面的意思就是跑圈,实际上App能一直不停的运行下去,runloop功不可没!咱们来分析一下main函数:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}复制代码
若是没有runloop,线程执行完以后就会退出,就不能再执行任务了。这时咱们就须要采用一种方式来让线程可以处理任务,并不退出。因此,咱们就有了RunLoop,其中UIApplicationMain
函数内部帮咱们开启了主线程的RunLoop,UIApplicationMain
内部拥有一个无线循环的代码。
runloop与线程对应关系以下:
runloop的mode是用来作什么的?有几种mode?
在ibireme大神的深刻理解 Runloop一文中,详细的介绍了一个Runloop包含了哪些东西,以下图所示:
常见的Mode以下:
1.kCFRunLoopDefaultMode
:App的默认运行模式,一般主线程是在这个运行模式下运行 2.UITrackingRunLoopMode
:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其余Mode影响) 3.UIInitializationRunLoopMode
:在刚启动App时第进入的第一个 Mode,启动完成后就再也不使用 4.GSEventReceiveRunLoopMode
:接受系统内部事件,一般用不到 5.kCFRunLoopCommonModes
:伪模式,不是一种真正的运行模式
为何把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环之后,滑动scrollview的时候NSTimer却不动了?
苹果是如何实现Autorelease Pool的?
autorelease
一个被autorelease修饰的对象会被加到最近的autoreleasePool
中,当这个autoreleasePool
自身drain的时候,其中的autoreleased对象会被release
autoreleasePool是怎么实现的?
1.AutoreleasePool
并无单独的结构,而是由若干个AutoreleasePoolPage
以双向链表的形式组合而成
2.AutoreleasePoolPage
对象会记录autorelease
对象地址
3.AutoreleasePool的操做时经过如下这几个函数实现的:objc_autoreleasepoolPush
,objc_autoreleasepoolPop
,objc_autorelease
4.Autorelease
对象是在当前的runloop
迭代结束时释放的,而它可以释放的缘由是系统在每一个runloop
迭代中都加入了autorelease
的Push和Pop
推荐你们阅读这篇黑幕背后的Autorelease
类结构
isa指针?(对象的isa,类对象的isa,元类的isa都要说)
isa
指针,在OC中类也是一种对象,它属于元类metaClasss
,对象的isa
指针指向类,类的isa
指针指向元类,元类的isa
指针指向父类的元类,一直到根元类,最后根元类的isa
指针指向了自身,如图:
类方法和实例方法有什么区别?
methodLists
里面,而实例方法位于类结构体的methodLists
中。介绍一下分类,能用分类作什么?内部是如何实现的?它为何会覆盖掉原来的方法?
分类通过编译后也会成为一个结构体:
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; // 属性列表
};复制代码
在运行时,category
会被附加到类上面,包括把category
的实例方法、协议以及属性添加到类上和category
的类方法和协议添加到类的metaclass
上。
category
的方法没有彻底替换掉原来类已经有的方法,也就是说若是category
和原来类都有methodA,那么category
附加完成以后,类的方法列表里会有两个methodA,category
的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是咱们日常所说的category
的方法会“覆盖”掉原来类的同名方法,这是由于运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,却不知后面可能还有同样名字的方法。
推荐你们阅读美团出品的深刻理解Objective-C:Category,我是知识的搬运工~ 也欢迎你们给我推荐高质量的文章!独乐乐不如众乐乐~
运行时能增长成员变量么?能增长属性么?若是能,如何增长?若是不能,为何?
class_addIvar
给指定的类添加成员变量,可是不能为已经生成的类添加,运行时规定,只能在objc_allocateClassPair
与objc_registerClassPair
两个函数之间为类添加变量。缘由在上面的题目中有过解释。class_addProperty
给指定的类添加属性,能够成功添加了属性可是不能用点调用法调用,能够利用KVC/关联方式来该表这个属性的值runtime
的objc_setAssociatedObject
和objc_getAssociatedObject
方法来实现关联,给分类添加属性就是利用这个方法实现的。objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)
[NSNull null]
对象发送消息时,是会crash的。参考博客: