技术基础
1、我们说的Objective-C是动态运行时语言是什么意思?
答:
OC可以通过Runtime这个运行时机制,在运行时动态的添加变量、方法、类等,所以说oc是一门动态的语言
2、讲一下MVC和MVVM,MVP?
答:
MVC:M->model模型,V->视图View,C->controller。 在iOS中,通常ViewController同时扮演了V和C的角色,M和V通常也会直接通信,产生耦合。
优点:简单易懂,比较传统普遍。缺点:耦合度过高,ViewController过于臃肿,适合小项目
MVP:M->model模型,V->视图View,P->Presenter。MVP将MVC的ViewController进行拆分:视图数据逻辑处理部分为P,ViewController剩余部分与View合并成V。MVP的P包含着元件的事件处理,负责检索 Model 取得数据,和将取得的数据经过格式转换与 View 进行沟通。
1.Model 层应该不仅仅是创建一个数据对象,还应该包含网络请求,以及数据 SQLite 的 CRUD 操作。
2.View 层比较简单明,就是 View 的一些封装、重用。
3.Presenter 层并不涉及数据对象的网络请求和 SQLite 操作,只是 Model 层和 View 层的一个桥梁。
优点:
任务均摊 -- 我们将最主要的任务划分到 Presenter 和 Model,而 View 的功能较少;
可测试性 -- 非常好,由于一个功能简单的 View 层,所以测试大多数业务逻辑也变得简单;
易用性 -- 代码量比 MVC 模式的大,但同时 MVP 的概念却非常清晰。
MVVM:MVVM 即 Modal View ViewModel(模型 视图 视图模型)。在MVP的基础上,将P改成与V双向绑定的VM就变成了MVVM。搭配RXSwift框架方便使用。
1.在 MVVM 里,view 和 view controller 正式联系在一起,我们把它们视为一个组件。视图 view 仍然不能直接引用模型 Model,当然 controller 也不能。相反,他们引用视图模型 View Model。
2.View Model 是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。有一件事情不应归入 View Model,那就是任何视图本身的引用。View Model 的概念同时适用于于 iOS 和 OS X(换句话说,不要在 View Model 中使用 #import UIKit.h)。
3.由于展示逻辑(presentation logic)放在了 View Model 中(比如 Model 的值映射到一个格式化的字符串),视图控制器本身就会不再臃肿。当然你开始使用 MVVM 的最好方式时可以先将一小部分逻辑放入视图模型,然后当你逐渐习惯于使用这个范式的时候再迁移更多的逻辑到视图模型中。使用 MVVM 会轻微的增加代码量,但总体上减少了代码的复杂性。
优点:
任务均摊 -- MVVM 的 View 要比 MVP 中的 View 承担的责任多。因为前者通过 ViewModel 的设置绑定来更新状态,而后者只监听 Presenter 的事件但并不会对自己有什么更新。
可测试性 -- ViewModel 不知道关于 View 的任何事情,这允许我们可以轻易的测试 ViewModel。同时 View 也可以被测试,但是由于属于 UIKit 的范畴,对他们的测试通常会被忽略。
易用性 -- 在实际开发中必须把 View 中的事件指向 Presenter 并且手动的来更新 View,如果使用绑定的话,MVVM 代码量将会小的多。
3、为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
答:
1.代理用weak是为了防止循环引用。例如View有一个协议,需要一个代理实现回调。一个Controller添加这个View,并且遵守协议,成为View的代理。如果不用weak,用Strong,Controller->View->delegate->Controller,就循环引用了。
2.代理的delegate偏重于与用户的用户交互的回调,有哪些方法可以供我使用,例如UITableviewDelegate;DataSource偏重于数据的回调,view里面有什么东西,属性都是什么,例如UITableViewDataSource。
3.block和代理作为消息传递的两种方式,在本质上是不同的,block其实是一个对象,而代理是一种设计模式。
4、属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
1.属性的实质是@property = ivar + getter + setter
2.包括实例变量+get方法+set方法。
3.关键字有strong,weak,assign,copy,nonatomic,atomic...等
4.@dynamic这个关键词,通常是用不到的。它与@synthesize的区别在于:使用@synthesize编译器会生成getter和setter方法,而@dynamic仅仅是告诉编译器这两个方法在运行期会有的,无需产生警告。
5、属性的默认关键字是什么?
ARC下,不显式指定任何属性关键字时默认的关键字是strong, atomic, readwrite
6、NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
答:
因为父类指针可以指向子类对象,使用copy的目的是为了让对象的属性不受外界影响,使用copy无论给我传入的是一个可变对象还是不可变对象,我本身的持有就是一个不可变的副本。如果使用的是strong,那么这个属性就有可能指向一个可变对象,如果这个对象在外部被修改了,那么会影响该属性。
7、如何令自己所写的对象具有拷贝功能?
答:
需要该类遵从NSCopying协议,并实现NSCopying协议方法- (id)copyWithZone:(NSZone *)zone。
8、简述NotificationCenter,KVC,KVO,delegate,并说明他们之间的区别?
答:
Notification:多对多的观察者模式,通常发送者和接收者的关系是间接的多对多关系。消息的发送者告知接受者事件接受者已经发送或者将要发送,仅此而已,接受者并不能反过来影响发送者的行为。
KVO:一对多的观察者模式,键值对观察机制,它提供了观察某一属性变化的方法,极大简化了代码。
KVC:键值编码,即NSKeyValueCoding。我们可以通过以字符串为Key对对象属性进行操作。
Delegeate:把某个对象要做的事情委托给别的对象去做。那么别的对象就是这个对象的代理,代替它来打理要做的事。
它们之间的区别:delegate比NSNOtification效率高。delegate比notification更加直接,需要关注返回值,所以delegate方法往往包含should这个很传神的词。而notification则相反,它不关心结果,所以往往用did这个词汇。KVO是直接通知到观察对象,并且逻辑非常清晰,实现步骤简单。
使用方面:两个模块联系不上很紧密,用notification,例如多线程之间传值。反之用delegate。
9、include与#import的区别?#import与@class的区别?#import<>跟#import""的区别?
答:
1.#import 是OC对@include的改进版本,能确保引用的文件只会被引用一次,不会陷入递归包含的问题中。
2.#import是引用该头文件的全部信息,包含实体变量和方法等;@class只是告诉编译器,其后面的声明是类的名称,至于这些类是如何定义的,暂时不用考虑。
3.#import<>用来包含系统自带的文件,#import""用来包含自定义的文件
10、nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
答:
nonatomic:表示非原子性,不安全,但效率高;
atomic:表示原子性,线程安全,效率低;
atomic的线程安全仅仅是指set get方法的线程安全,并不保证整个对象是线程安全。
使用@synchronized
11、Objective-C与C、C+++之间的联系和区别?
答:
联系:OC和C++都是从C语言演化而来的面向对象语言,两者都兼容标准C语言
区别:C是面向过程的语言,OC提供了运行时动态绑定机制,而C++是编译静态绑定
12、UICollectionView自定义layout如何实现?
答:
首先创建一个类继承于UICollectionViewLayout,然后重写一部分方法,在这些方法里去实现你想要的布局方式。
///每次更新layout布局都会首先调用此方法
-(void)prepareLayout
///返回collectionView的内容的尺寸
-(CGSize)collectionViewContentSize
///返回对应于indexPath的位置的cell的布局属性,返回指定indexPath的item的布局信息。子类必须重载该方法,该方法只能为cell提供布局信息,不能为补充视图和装饰视图提供。
-(UICollectionViewLayoutAttributes)layoutAttributesForItemAtIndexPath:(NSIndexPath)indexPath
13、进程和线程的区别?同步异步的区别?并行和并发的区别?
答:
每个应用(app)就是一个进程,每个进程的任务都是在线程中执行,所以每个进程至少都有一个线程,也就是主线程。
同步和异步决定是是否要开启新线程,同步不开启,异步开启。
并发是多个事件在同一时间段切换执行(单个cpu),而并行是多个事件在同一时间点同时执行(多个cpu)。
14、线程间通信?
答:
1.一个线程传递数据给另外一个线程。
2.在一个线程中执行完特定任务后,转到另一个线程执行任务
分为两种 1.主线程进入子线程 2.子线程回到主线程
15、GCD的一些常用的函数?(group,barrier,信号量,线程同步)
答:
延迟执行任务函数:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ <#code to be executed after a specified delay#> });
只执行一次的函数
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{<#code to be executed once#> });
栅栏函数
dispatch_barrier_sync 同步函数,会阻塞线程
dispatch_barrier_async 异步函数,不会阻塞线程
dispatch_barrier_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
dispatch_barrier_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
队列的使用
dispatch_group_async(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, <#dispatchQueue#>);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, <#intervalInSeconds#> * NSEC_PER_SEC, <#leewayInSeconds#> * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{<#code to be executed when timer fires#>});
dispatch_resume(timer);
信号量
假设现在系统由两个空闲资源可以被利用,但是同一时间却有三个线程进行访问。这个时候就可以方便的利用信号量来解决。
信号量:就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问资源之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。类似锁机制。
//创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)
//等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)
//提高信号量
dispatch_semaphore_signal(信号量)
注:正常的使用顺序是先降低然后再提高。两个函数通畅是成对使用。
代码示例:
16、如何访问并修改一个类的私有属性?
答:
可以通过KVC和runtime两种方法实现
17、数据持久化的几个方案(fmdb用没用过)
答:
plist文件(属性列表)
NSUserDefaults
NSKeyedArchiver(归档)
SQLite 3
CoreData
FMDB是iOS平台的SQLite数据库框架,它是以OC的方式封装了SQLite的C语言API。
18、说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?
答:
1.点击App图标(App没有在后台运行)
1.didFinishLaunchingWithOptions
2.applicationDidBecomeActive
2.从后台到前台
1.applicationWillEnterForeground
2.applicationDidBecomeActive
3.从前台到后台
1.applicationWillResignActive
2.applicationDidEnterBackground
4.通过URLScheme打开App
1.applicationWillEnterForeground
2.- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
3.applicationDidBecomeActive
19、NSCache优于NSDictionary的几点?
答:
1.NSCache是线程安全的,我们可以在不同的线程中添加、删除和查询缓存中的对象
2.NSCache是可以自动释放内存的
3.一个缓存对象不会拷贝key对象
20、知不知道Designated Initializer?使用它的时候有什么需要注意的问题?
答:
初始化函数,需要子类指定了新的初始化器,那么子类初始化器内部必须动用父类的Designated Initializer。并且需要重写父类的Designated Initializer,并指向子类新的初始化器
21、实现description方法能取到什么效果?
答:
一般情况下,我们在使用NSLog和%@输出某个对象时,就会调用这个对象的description方法,它的返回值就是NSString字符串类型,所以description默认实现返回的格式是<类名:对象的内存地址>。而一般情况下,我们需要的是类的成员变量的值,所以我们可以重写description方法,返回类的成员变量的值,当再次输出这个对象的时候,就会打印相应的成员变量的值。
22、objc使用什么机制管理对象内存?
答:
MRC 手动内存管理
ARC 自动引用计数内存管理
通过 retainCount 的机制来决定对象是否需要释放。每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。
23、runloop是什么/runloop的概念?
答:
Run loops是线程相关的的基础框架的一部分。一个run loop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。其实内部就是do-while循环,这个循环内部不断地处理各种任务(比 如Source,Timer,Observer)。使用run loop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。
中级
Block
1、block的实质是什么?一共有几种block?都是什么情况下生成的?
答:
block对象就是一个结构体,里面有isa指针指向自己的类(global malloc stack),有desc结构体描述block的信息,_forwarding指向自己或堆上自己的地址
一共有3种block,_NSConcreteGlobalBlock 全局静态 _NSConcreteStackBlock 保存在栈中,出函数作用域就销毁 _NSConcreteMallocBlock 保存在堆中
代码区:不访问栈区的变量(如局部变量),且不访问堆区的变量(alloc创建的对象),此时block存放在代码区。
堆区:访问了处于栈区的变量,或者堆区的变量,此时block存放在堆区。–需要注意实际是放在栈区,在ARC情况下会自动拷贝到堆区,如果不是ARC则存放在栈区,所在函数执行完毕就回释放,想再外面调用需要用copy指向它,这样就拷贝到了堆区,strong属性不会拷贝、会造成野指针错区。
2、使用系统的某些block api,是否考虑引用循环问题?
答:一般情况下,UIView的block版本写动画时不需要考虑。但如果你使用一些参数中可能含有 ivar(成员变量) 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用。
3、谈谈block的理解?并写出一个使用block执行UIVew动画?
答:
Block是可以获取其他函数局部变量的匿名函数,其不但方便开发,并且可以大幅提高应用的执行效率。block对象就是一个结构体,一共有3种block,全局block,栈block,堆block,在ARC下其实就只有全局和堆两种block。
Block可以访问局部变量,但是不能修改,如果要修改就要加关键字:__block。
[UIView transitionWithView:self.view duration:0.2
ptions:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ [[blueViewController view] removeFromSuperview]; [[self view] insertSubview:yellowViewController.view atIndex:0]; }
completion:NULL];
Runtime
1、runtime如何实现weak属性?
答:weak此特质表明该属性定义了一个[非拥有关系](nonowning relationship)。为这种属性设置新值时,设置方法不持有新值(新指向的对象),也不释放旧值(原来指向的对象)。
runtime对注册的类,会进行内存布局,从一个粗粒度的概念上讲,这时候会有一个hash表,这是一个全局表,表中是weak指向的对象内存地址座位key,用所有指向该对象的weak指针表作为value。当此对象的引用计数为0的时候会dealloc,假如改对象的内存地址是a,那么就会以a为key,在这个weak表中搜索,找到所有以a为建的weak对象,从而设置为nil。
2、runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
答:
每一个类对象中都有一个对象方法列表(对象方法缓存)
类方法列表是存放在类对象中isa指针指向的元素对象中(类方法缓存)
方法列表中每个方法结构体中记录着方法的名称,方法的实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以再从列表中找到对应的方法实现
当我们发送一个消息给一个NSObject对象时,这条消息在对象的类对象方法列表实现
但我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表中查找
3、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
答:
不能向编译后得到的类中增加实例变量,可以向运行时创建的类中添加实例变量
编译后的类以及注册在runtime中,类结构中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime会调用class_setvarLayout或clas_setWeakLayout来处理stong weak引用。所以不能向存在的类中添加实例变量
运行时创建的类是可以添加实例变量,调用class_addlvar函数。但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。
4、runtime如何实现weak变量的自动置nil?
答:
runtime对注册的类,会进行内存布局,从一个粗粒度的概念上讲,这时候会有一个hash表,这是一个全局表,表中是weak指向的对象内存地址座位key,用所有指向该对象的weak指针表作为value。当此对象的引用计数为0的时候会dealloc,假如改对象的内存地址是a,那么就会以a为key,在这个weak表中搜索,找到所有以a为建的weak对象,从而设置为nil。
5、在开发中如何使用runtime?什么应用场景?
答:
1.将某些OC代码转化为运行时代码,探究底层。比如block的实现原理
2.拦截系统自带的方法调用,替换为想要实现的方法,比如拦截imageNmamed:、ViewDidLoad、alloc等
3.给分类增加属性
4.实现NSCoding的自动归档解档
5.实现字典和模型的自动转换
6、什么是method swizzling(俗称黑魔法)?
答:
简单来说就是进行方法交换
类结构
1、isa指针?(对象的isa,类对象的isa,元类的isa都要说)
答:
对象的isa指针指向所属的类
类的isa指针指向了所属的元类
元类的isa指向了根元类,根元类指向了自己。
类的superClass 指向父类最终到根类
根类的superclass指向nil
元类的superclass 指向父元类,最终到根元类
根元类的superclass 指向根类
2、类方法和实例方法有什么区别?
答:
1.在类方法中不能调用实例方法,只能访问和自己一样的类方法,但实例方法可以访问类方法和实例方法。
2.在类方法中不能引用实例变量(用static修饰的变量),但实例方法可以引用成员变量和实例变量。
3.在类方法中不能使用super、this关键字。
4.类方法不能被覆盖,但实例方法可以被覆盖。
5.类方法的调用是:类名.类方法,而实例方法的调用必须new出一个对象,即:对象.实例方法。
6.类方法常驻内存,实例方法不是,所以类方法效率高但占内存。
7.类方法在堆上分配内存,实例方法在堆栈上。
3、介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
答:
category:我们可以给类或者系统类添加实例方法方法。我们添加的实例方法,会被动态的添加到类结构里面的methodList列表里面。
4、运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
答:
如果未被编译,可以使用runtime增加
5、objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)
答:
会返回nil,0,0.0等数据,根据返回值类型。如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。
高级
1、UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染)
答:
缓存高度:当我们创建frame模型的时候,计算出来cell的高度的时候,我们可以将cell的高度缓存到字典里面,以cell的indexpath和Identifier作为为key。
异步绘制、减少层级:目前还不是很清楚
hide:个人理解应该是hidden吧,把可能会用到的控件都创建出来,根据不同的情况去隐藏或者显示出来。
避免离屏渲染:只要不是同时使用边框/边框颜色以及圆角的时候,都可以使用layer直接设置。不会造成离屏渲染。
2、有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)
答:
3、看过哪些第三方框架的源码?都是如何实现的?(如果没有,问一下多图下载的设计)
4、SDWebImage的缓存策略?
答:
SDWebImage 的图片缓存采用的是 Memory 和 Disk 双重 Cache 机制,将图片储存到内存和硬盘上
5、AFN为什么添加一条常驻线程?
答:
AFN 的做法是把网络请求的发起和解析都放在同一个子线程中进行,但由于子线程默认不开启 runloop,它会向一个 C语言程序那样在运行完所有代码后退出线程。而网络请求是异步的,这会导致获取到请求数据时,线程已经退出,代理方法没有机会执行。因此,AFN 的做法是使用一个 runloop 来保证线程不死~
然而频繁的创建线程并启动runloop肯定会造成内存泄露(runloop 无法停止.线程无法退出)
所以AFN就创建了一个单例线程,并且保证线程不退出
6、KVO的使用?实现原理?(为什么要创建子类来实现)
答:
KVO 的实现依赖于 Objective-C 强大的 Runtime,当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
7、KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)
答:
键值赋值,使用最多的即使字典转模型。利用runtime获取对象的所有成员变量, 在根据kvc键值赋值,进行字典转模型
setValue: forKey: 只查找本类里面的属性
setValue: forKeyPath:会查找本类里面属性,没有会继续查找父类里面属性。