基础
1.为什么说Objective-C是一门动态的语言?
所谓的动态类型语言,意思就是类型的检查是在运行时做的。运行时和多态决定了OC动态语言的特性,运行时机制将数据类型的确定由编译时推迟到了运行时,使我们可以直到运行时才去决定一个对象的类型以及调用该类型对象指定的方法;多态:不同对象以自己的方式响应同一消息的能力叫做多态,体现在代码中就是协议代理、子类重写父类方法;假设动物类有一个相同的方法-shout,狗属于动物,猫也属于动物,继承动物类后实现了自己叫的方法,它们的叫声是不一样的,也就是不同的对象以自己的方式响应了-shout,因此也可以说运行时机制是多态的基础。
2.讲一下MVC和MVVM,MVP?
MVC:Model(处理数据模型),View(处理数据显示),Controller(处理用户交互)
MVVM:Model(处理数据模型),View(处理数据显示),ViewModel(处理业务逻辑),解决MVC中Controller过于臃肿的问题
MVP:Model(处理数据模型),View(处理数据显示),Presenter(处理业务逻辑),与MVC的区别是,MVP中View并不直接访问Model,他们直接通信是通过Presenter来进行的,所有交互都发生在Presenter中,而MVC中View是可以访问Model的。
3.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
代理使用weak(MRC中使用assign),避免发生循环引用造成内存泄漏,delegate控制的是UI,是上层的东西;而dataSource控制的是数据,它们本质都是回调,只是回调的对象不同(参考UITableViewDateSource & Delegate中的方法)
相同点:代理和Block大多是我们都可以用来做倒序传值的。我们都得注意避免循环引用。
不同点:代理使用weak修饰,代理必须先声明方法。当我们调用代理的时候要判断是否已经实现。
block:使用的是copy来修饰,block保存的是一段代码,其实也就是一个函数。当我们调用block的时候要判断是否已经实现。
4.属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
property 的本质=ivar(实例变量)+ setter(存方法)+ getter(取方法);
默认情况下的关键字:
基本数据类型(atomic,readwrite,assign)
对象(atomic,readwrite,strong)(MRC中strong改为retain)
@dynamic告诉编译器不要自动生成属性的setter & getter方法,由我们手动实现
@synthesize让编译器自动生成属性的setter & getter方法(Xcode4.5以后不需要再写,系统会自动生成)
需要注意的是,如果同时重写了某个属性的setter & getter而没有声明对应的ivar,需要手动声明,否则编译器将找不到属性的实例变量。
5.属性的默认关键字是什么?
基本数据类型(atomic,readwrite,assign)
对象(atomic,readwrite,strong)(MRC中strong改为retain)
6.NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
一般情况下,不可变对象(NSString,NSArray,NSDictionary等)使用copy修饰,可变对象(NSMutableString,NSMutabeArray,NSMutableDictionary等)使用strong修饰;
不可变对象可以接受子类对象,也就是说NSString可以接受NSMutableString,如果使用strong修饰,当传入的string是可变对象且发生改变时,会导致属性跟着改变造成数据错乱,如果可以确定传入的数据是不可变的,可以用strong。
注意,可变对象一定要用strong修饰,因为使用copy修饰后对象是不可变的,如果这时对可变对象进行修改会导致崩溃。
7.如何令自己所写的对象具有拷贝功能?
实现NSCopying协议,如果对象分为可变与不可变版本,需要同时实现NSCopying与NSMutableCopying协议。
8.可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?
不可变容器类:copy=浅拷贝,mutablecopy=单层深拷贝,拷贝后的对象可变
可变容器类:copy=单层深拷贝,拷贝后对象不可变,mutablecopy=单层深拷贝,拷贝后对象可变。
单层深拷贝表示对象本身为深拷贝,内部元素仍然是浅拷贝。
9.为什么IBOutlet修饰的UIView也适用weak关键字?
因为父控件的subviews数组已经对他有一个强引用,不需要再强引用一次。
10.nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
nonatomic:非原子操作,atomic:原子操作,使用atomic会给属性的setter & getter方法加锁,防止读写的时候被另外一个线程读取造成数据错乱,atomic只保证属性的存取方法是线程安全的,并不保证整个对象都是线程安全,需要使用线程锁(这里不太了解)。
11.UICollectionView自定义layout如何实现?
(1). 覆写prepareLayout方法,并在里面事先就计算好必要的布局信息并存储起来。
(2). 基于prepareLayout方法中的布局信息,使用collectionViewContentSize方法返回UICollectionView的内容尺寸。
(3). 使用layoutAttributesForElementsInRect:方法返回指定区域cell、Supplementary View和Decoration View的布局属性。
12.用StoryBoard开发界面有什么弊端?如何避免?
没有用过storyboard,常用的是xib加代码布局。
13.进程和线程的区别?同步异步的区别?并行和并发的区别?
进程有独立的地址空间,一个进程崩溃不会对其他进程产生影响,而线程是进程中不同的执行路径,线程有自己的栈和局部变量,没有单独的地址空间,一个线程死掉就等于整个进程死掉
14.线程间通信?
例子:使用GCD创建一个全局并发队列异步下载图片,下载完成后回到主队列刷新UI(异步任务+并发队列->回主线程刷新UI)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//下载图片
dispatch_async(dispatch_get_main_queue(), ^{
//刷新UI
});
});
15.GCD的一些常用的函数?(group,barrier,信号量,线程同步)
dispath_get_main_queue()主队列 串行
dispath_get_global_queue()全局队列 并行
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)自定义队列 可自定义并行DISPATH_QUEUE_CONCURRENT串行DISPATH_QUEUE_SERIAL
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>) 同步添加任务到队列
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>) 异步添加任务到队列
dispatch_suspend(<#dispatch_object_t _Nonnull object#>)挂起队列
dispatch_resume(<#dispatch_object_t _Nonnull object#>)恢复队列
dispatch_semaphore_create(<#long value#>)创建信号量
dispatch_semaphore_wait(<#dispatch_semaphore_t _Nonnull dsema#>, <#dispatch_time_t timeout#>)等待信号量
dispatch_semaphore_signal(<#dispatch_semaphore_t _Nonnull dsema#>)发出信号量
dispatch_group_create() 创建队列组
dispatch_group_async(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)异步添加队列到组中
dispatch_group_notify(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)队列组中任务完成时回调
dispatch_barrier_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
dispatch_barrier_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)等待所有位于barrier前的函数执行完毕后执行,并在barrier函数执行完毕后执行后面的函数,只在并发自定义队列中可用(区别:dispatch_barrier_sync将自己的任务插入后需要等待自己的任务完成才会执行后面的函数,dispatch_barrier_async不需要等待自己的任务完成,插入后就可以执行后面的函数)
16.如何使用队列来避免资源抢夺?
1)因为不同的线程同时执行任务,同时访问统一资源。如果异步操作要保证线程安全等问题,尽量使用GCD(有些函数默认就是安全的)
2)
1> @synchronized(xx) {...}互斥锁
2> NSLock同步锁
3> dispatch_barrior_async作用是在并行队列中,等待前面两个操作并行操作完成
4> dispatch_semaphore_wait等待信号:等待有线程结束之后会增加一个信号才继续执行
17.数据持久化的几个方案(fmdb用没用过)
plist,NSUserDefault(本质也是plist),归档解档,sqlite(FMDB),CoreData
18.说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?
首次运行:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions —程序启动
- (void)applicationDidBecomeActive:(UIApplication *)application —-程序进入活跃状态
首次进入后台(home):
- (void)applicationWillResignActive:(UIApplication *)application —-程序结束活跃状态
- (void)applicationDidEnterBackground:(UIApplication *)application -—程序已经进入后台
再次运行:
- (void)applicationWillEnterForeground:(UIApplication *)application -—程序将要进入前台
- (void)applicationDidBecomeActive:(UIApplication *)application —-程序进入活跃状态
再次进入后台:
- (void)applicationWillResignActive:(UIApplication *)application —-程序结束活跃状态
- (void)applicationDidEnterBackground:(UIApplication *)application -—程序已经进入后台
19.NSCache优于NSDictionary的几点?
1.nscache 是可以自动释放内存的。
2.nscache是线程安全的,我们可以在不同的线程中添加,删除和查询缓存中的对象。
3.一个缓存对象不会拷贝key对象。
NSCache胜过NSDictionary之处在于,当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出“低内存”通知时手工删减缓存。
NSCache并不会“拷贝”键,而是会“保留”它。此行为用NSDictionary也可以实现,然而需要编写相当复杂的代码。NSCache对象不拷贝键的原因在于:很多时候,键都是不支持拷贝操作的对象来充当的。因此,NSCache不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便。另外,NSCache是线程安全的,而NSDictionary则绝对不具备此优势。
20.知不知道Designated Initializer?使用它的时候有什么需要注意的问题?
便利初始化函数只能调用自己类中的其他初始化方法,指定初始化函数才有资格调用父类的指定初始化函数。当我们为自己创建的类添加指定初始化函数时,必须准确的识别并覆盖直接父类所有的指定初始化函数,这样才能保证整个子类的初始化过程可以覆盖到所有继承链上的成员变量得到合适的初始化。
21.实现description方法能取到什么效果?
NSLog中(或lldb中使用po obj)输出一个对象本质就是输出[obj description]的返回值,重写该方法可以改变输出内容便于调试。
22.objc使用什么机制管理对象内存?
引用计数,分MRC(手动引用计数)和ARC(自动引用计数),对象被强引用时引用计数+1,对象的引用计数为0时释放对象。
中级
Block
1.block的实质是什么?一共有几种block?都是什么情况下生成的?
OC中的block可以看作一个对象,因为block中含有isa指针,这个isa指针被初始化_NSConcreateStackBlock或者NSConcreateGlobalBlock类的地址;block根据在内存中的位置分为三种:NSGlobalBlock,NSStackBlock,NSMallocBlock。block中没有用到局部变量会初始化为NSConcreateGlobalBlock,如果用到局部变量,在MRC中会初始化为NSConcreateStackBlock,ARC中会初始化为NSConcreateMallocBlock。block作为属性时使用copy修饰以保证MRC下将block拷贝到堆中,ARC下不使用copy修饰也会自动拷贝到堆中
2.为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?
block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝。因为是只读的,所以在block内无法改变变量的值。由于block捕获了自动变量的瞬时值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。使用__block修饰的变量可以在block中修改、重新赋值,使用__block修饰的对象在block内不会被强引用一次,从而不会出现循环引用的问题。至于为什么加上__block就可以做到这些不太清楚。
3.模拟一下循环引用的一个情况?block实现界面反向传值如何实现?
某个类使用block作为属性,然后再block内使用了self;delegate使用strong或retain修饰(一般来讲,只要出现self->成员变量->block->self的闭环就会导致循环引用)
block反向传值:
A页面进入B页面,在B中声明一个block属性,当B页面要消失的时候调用block传值给A,A在创建B页面实例时,实现b.block接收传入的参数进行处理。
Runtime
1.objc在向一个对象发送消息时,发生了什么?
方法调用的本质就是向对象发消息,runtime中,[obj xxx]会被转化为objc_msgSend(obj,xxx),这个函数做了如下几件事情:
根据对象的isa指针找到对象所属的类,去对应的类中寻找方法;
先找缓存,找不到再去找方法列表;
如果仍然找不到,就去父类中寻找方法,如此向上传递;
如果在最顶层的父类中还是找不到,程序会崩溃并抛出unrecognized selector异常。
2.什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
根据对象的isa指针找到对象所属的类,去对应的类中寻找方法;
先找缓存,找不到再去找方法列表;
如果仍然找不到,就去父类中寻找方法,如此向上传递;
如果在最顶层的父类中还是找不到,程序会崩溃并抛出unrecognized selector异常。
不过在这之前,runtime会给出三次拯救程序崩溃的机会(Method resolution提供函数实现,Fast forwarding快速转发,转发给其他对象,Normal forwarding一般转发,转发给NSInvocation对象)
3.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类添加实例变量,可以向运行时创建的类中添加实例变量;原理不明
4.runtime如何实现weak变量的自动置nil?
runtime对注册的类,会进行布局,将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。
5.给类添加一个属性后,在类结构体里哪些元素会发生变化?
instance_size :实例的内存大小
objc_ivar_list *ivars:属性列表
RunLoop
1.runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
runloop是用来管理线程的,它是一个事件处理循环,用来不停的调配工作以及处理输入事件;当线程的runloop开启后,线程就会在执行完任务后处于休眠状态,随时等待接受新的任务,而不是退出。每个线程都有一个runloop对象,主线程的runloop是默认开启的,子线程的runloop需要手动创建并开启。
2.runloop的mode是用来做什么的?有几种mode?
runloop的mode用来指定事件在运行循环中的优先级,分为以下几种
公开提供:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
非公开:
UITrackingRunLoopMode:ScrollView滑动时
UIInitializationRunLoopMode:启动时
3.为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
nstime对象是在NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes
4.苹果是如何实现Autorelease Pool的?
Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,将创建的对象,添加到最近的autoreleasePool中,等到autoreleasePool作用域结束的时候,会将里面所有的对象的引用计数器-1. //www.greatytc.com/p/cc3ee2909457
类结构
1.isa指针?(对象的isa,类对象的isa,元类的isa都要说)
在oc中,类也是对象,所属元类。所以经常说:万物皆对象
对象的isa指针指向所属的类
类的isa指针指向了所属的元类
元类的isa指向了根元类,根元类指向了自己。
2.类方法和实例方法有什么区别?
调用的方式不同,类方法必须使用类调用,在方法里面不能调用属性,类方法里面也必须调用类方法。存储在元类结构体里面的methodLists里面
实例方法必须使用实例对象调用,可以在实例方法里面使用属性,实例方法也必须调用实例方法。存储在类结构体里面的methodLists里面
3.介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
category:我们可以给类或者系统类添加实例方法方法。我们添加的实例方法,会被动态的添加到类结构里面的methodList列表里面。categort https://tech.meituan.com/DiveIntoCategory.html
4.运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
可以添加属性的,但必须我们实现它的getter和setter方法。但是没有添加带下滑线同名的成员变量
但是我们使用runtime我们就可以实现添加成员变量方法如下
-(void)setName:(NSString*)name{/**
* 为某个类关联某个对象
*
* @param object#> 要关联的对象 description#>
* @param key#> 要关联的属性key description#>
* @param value#> 你要关联的属性 description#>
* @param policy#> 添加的成员变量的修饰符 description#>
*/objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY_NONATOMIC);}-(NSString*)name{/**
* 获取到某个类的某个关联对象
*
* @param object#> 关联的对象 description#>
* @param key#> 属性的key值 description#>
*/returnobjc_getAssociatedObject(self,@selector(name));}
5.objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)
• 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:Person
motherInlaw = [ aPerson spouse] mother]; 如果spouse对象为nil,那么发送给nil的消息mother也将返回nil。
),float,double,long double 或者long long的整型标量,发送给nil的消息将返回0。
• 如果方法返回值为结构体,正如在《Mac OS X ABI 函数调用指南》,发送给nil的消息将返回0。结构体中各个字段的值将都是0。其他的结构体数据类型将不是用0填充的。
• 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。http://blog.csdn.net/jeffasd/article/details/51615151
高级
1.UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染)
缓存高度:当我们创建frame模型的时候,计算出来cell的高度的时候,我们可以将cell的高度缓存到字典里面,以cell的indexpath和Identifier作为为key。
NSString*key=[[HeightCacheshareHeightCache]makeKeyWithIdentifier:@"YwywProductGradeCell"indexPath:indexPath];if([[HeightCacheshareHeightCache]existInCacheByKey:key]){return[[HeightCacheshareHeightCache]heightFromCacheWithKey:key];}else{YwywProductGradeModelFrame*modelFrame=self.gradeArray[indexPath.row];[[HeightCacheshareHeightCache]cacheHieght:modelFrame.cellHight key:key];returnmodelFrame.cellHight;}
异步绘制、减少层级:目前还不是很清楚
hide:个人理解应该是hidden吧,把可能会用到的控件都创建出来,根据不同的情况去隐藏或者显示出来。
避免离屏渲染:只要不是同时使用边框/边框颜色以及圆角的时候,都可以使用layer直接设置。不会造成离屏渲染。
2.有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)
交换方式:一般写在类的+(void)load方法里面
/** 获取原始setBackgroundColor方法 */MethodoriginalM=class_getInstanceMethod([selfclass],@selector(setBackgroundColor:));/** 获取自定义的pb_setBackgroundColor方法 */MethodexchangeM=class_getInstanceMethod([selfclass],@selector(pb_setBackgroundColor:));/** 交换方法 */method_exchangeImplementations(originalM,exchangeM);
创建类:
ClassMyClass=objc_allocateClassPair([NSObjectclass],"Person",0);
添加方法
/**参数一、类名参数
二、SEL 添加的方法名字参数
三、IMP指针 (IMP就是Implementation的缩写,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP)
参数四、其中types参数为"i@:@“,按顺序分别表示:具体类型可参照[官方文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)i 返回值类型int,若是v则表示void@ 参数id(self): SEL(_cmd)@ id(str)
V@:表示返回值是void 带有SEL参数 (An object (whether statically typed or typed id))
*/class_addMethod(Person,@selector(addMethodForMyClass:),(IMP)addMethodForMyClass,"V@:");
添加实例变量
/**参数一、类名参数
二、属性名称参数
三、开辟字节长度参数
四、对其方式参数
五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (对象 静态类型或者id类型) 具体类型可参照[官方文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)return: BOOL 是否添加成功
*/BOOL isSuccess=class_addIvar(Person,"name",sizeof(NSString*),0,"@");isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");
3.看过哪些第三方框架的源码?都是如何实现的?(如果没有,问一下多图下载的设计)
4.SDWebImage的缓存策略?
sd加载一张图片的时候,会先在内存里面查找是否有这张图片,如果没有会根据图片的md5(url)后的名称去沙盒里面去寻找,是否有这张图片,如果没有会开辟线程去下载,下载完毕后加载到imageview上面,并md(url)为名称缓存到沙盒里面。
5.AFN为什么添加一条常驻线程?
AFN 目的:就是开辟线程请求网络数据。如果没有常住线程的话,就会每次请求网络就去开辟线程,完成之后销毁开辟线程,这样就造成资源的浪费,开辟一条常住线程,就可以避免这种浪费,我们可以在每次的网络请求都添加到这条线程。
6.KVO的使用?实现原理?(为什么要创建子类来实现)
kvo:键值观察,根据键对应的值的变化,来调用方法。
注册观察者:addObserver:forKeyPath:options:context:
实现观察者:observeValueForKeyPath:ofObject:change:context:
移除观察者:removeObserver:forKeyPath:(对象销毁,必须移除观察者)
注意
使用kvo监听A对象的时候,监听的本质不是这个A对象,而是系统创建的一个中间对象NSKVONotifying_A并继承A对象,并且A对象的isa指针指向的也不是A的类,而是这个NSKVONotifying_A对象
7.KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)
kvc:键值赋值,使用最多的即使字典转模型。利用runtime获取对象的所有成员变量, 在根据kvc键值赋值,进行字典转模型
setValue: forKey: 只查找本类里面的属性
setValue: forKeyPath:会查找本类里面属性,没有会继续查找父类里面属性。
项目
1.有已经上线的项目么?
2.项目里哪个部分是你完成的?(找一个亮点问一下如何实现的)
3.开发过程中遇到过什么困难,是如何解决的?
学习
4.遇到一个问题完全不能理解的时候,是如何帮助自己理解的?举个例子?
5.有看书的习惯么?最近看的一本是什么书?有什么心得?
6.有没有使用一些笔记软件?会在多平台同步以及多渠道采集么?(如果没有,问一下是如何复习知识的)
7.有没有使用清单类,日历类的软件?(如果没有,问一下是如何安排,计划任务的)
8.平常看博客么?有没有自己写过?(如果写,有哪些收获?如果没有写,问一下不写的原因)
相关链接
github.com/ChenYilong/iOSInterviewQuestions
www.greatytc.com/p/56e40ea56813