自答:
说实话,刚刚看到这套面试题,我觉得还是很难的,这是要招聘大牛的节奏啊...好吧,菜鸟一枚的我来试着回答一下.
1.什么是函数式编程?
说起链式编程和函数式编程,小伙伴们千万不要紧张。
听着很高大尚,其实也就那么回事。相信有过swift/C#开发经验的,或者其他编程经验的,只要不是OC,一看就知道。 通过一个小例子来解释.
看两行代码:
Person *person = [[Person alloc] init];
person.run(9.2).eat(@"香蕉").run(1.2).eat(@"面条");
上面的就是链式编程+函数式编程.
来个大白话解释:看到括号里面的参数了吧,跟swift的函数调用是不是很相似,包括别的语言,都用小括号传参,只有OC是冒号传参。
再看方法调用用的是".",而OC用的是[]+空格。
这几个方法调用,如果要按OC的打法,估计要整4行,对象一个一个的调用方法,但链式就是这么一行搞定.
分析一下,因为Block可以通过()来传值,我们推断run(para)和eat(para)这两个方法,肯定返回值是一个Block,而且是带一个参数的Block
2.什么是ABI?
呜呜~~~(>_<)~~~,这个我真的不知道是什么鬼,不知道和iOS有什么关系,百度也了无法理解..... 希望知道的小伙伴们留言告知.
3.什么是MVC,请结合CocoaTouch说明?
M: model V:View C:controller O(∩_∩)O哈哈~ 自行展开,自圆其说吧.
4.什么是MVVM,请设计View moled需要考虑哪些?
M: model V:View VM:ViewModel是View和Model之间的中介
MVVM的出现主要是为了解决在开发过程中Controller越来越庞大的问题,变得难以维护,所以MVVM把数据加工的任务从Controller中解放了出来,使得Controller只需要专注于数据调配的工作,ViewModel则去负责数据加工并通过通知机制让View响应ViewModel的改变。
MVVM是基于胖Model的架构思路建立的,然后在胖Model中拆出两部分:Model和ViewModel。ViewModel本质上算是Model层(因为是胖Model里面分出来的一部分),所以View并不适合直接持有ViewModel,因为ViewModel有可能并不是只服务于特定的一个View,使用更加松散的绑定关系能够降低ViewModel和View之间的耦合度。
其实MVVM是一定需要Controller的参与的,虽然MVVM在一定程度上弱化了Controller的存在感,并且给Controller做了减负瘦身(这也是MVVM的主要目的)。但是,这并不代表MVVM中不需要Controller.严格来说MVVM其实是MVCVM。从中可以得知,Controller夹在View和ViewModel之间做的其中一个主要事情就是将View和ViewModel进行绑定。在逻辑上,Controller知道应当展示哪个View,Controller也知道应当使用哪个ViewModel,然而View和ViewModel它们之间是互相不知道的,所以Controller就负责控制他们的绑定关系,所以叫Controller/控制器就是这个原因。
5.swift相对于OC有哪些优点?
Swift容易阅读
Swift更容易维护
Swift更加安全
Swift代码更少
Swift速度更快
6.什么是泛型,swift在哪些地方使用了泛型?
两个整型数相加和两个浮点数相加的程序看起来应该非常类似,甚至一模一样才对。唯一的区别就是变量的类型不同。
在强类型语言中,你需要去定义诸如addInts, addFloats, addDoubles 等方法来正确地处理参数及返回值.
例如:
func swapTwoValue(a: inout T, b: inout T){
let tempValue = a
a = b
b = tempValue
}
这个函数用 T 占位符来代替实际的类型。并没有指定具体的类型,但是传入的a ,b 必须是同一类型T。在调用这个函数的时候才能指定 T 是那种具体的类型。
7.defer、guard的作用?
defer 译为延缓、推迟之意
比如,读取某目录下的文件内容并处理数据,你需要首先定位到文件目录,打开文件夹,读取文件内容以及处理数据,关闭文件以及文件夹。倘若一切顺利,只需按照设定好的程序流程走一轮即可;不过考虑事情要面面俱到,倘若中间某个环节失败,比如读取文件内容失败、处理数据失败等等,还需要进行一些后续收尾工作,即关闭文件或关闭文件夹(当然就算顺利执行,也是要关闭的)。
func doSomethingWithDefer(){
// 1
openDirectory()
// 2
defer{closeDirectory()}
// 3
openFile()
// 4
defer{closeFile()}
// 做其他杂七杂八事情…
}
guard 有控制、警戒之意,语法简单,只需两个示例代码即可明白。
// 这里使用if 和 guard进行对比 你会懂的更多
if age < 13 {
return //当年龄小于13时 程序返回 不进行之后的操作
}
用 guard 改写
guard age >= 13 else{
return
}
可以看到代码的意思是保证(guard)age值大于等于13 否则(else)返回,不执行下面程序。
8.swift语法糖?!的本质(实现原理)
相信大家在学习和使用Swift的时候,肯定会被 ! 和 ? 搞疯过, 纠结这两个符号到底是个什么鬼 ?鬼知道什么时候使用!,什么时候使用?
? 和 ! 其实分别是Swift语言中对一种可选类型( Optional) 操作的语法糖。 那可选类型是干什么的呢? Swift中是可以声明一个没有初始值的属性, Swift中引入了可选类型(Optional)来解决这一问题。它的定义是通过在类型生命后加加一个 ? 操作符完成的。
例如: var name: String?
Optional其实是个enum,里面有None和Some两种类型。其实所谓的nil就是Optional.None , 非nil就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这也是为什么在使用Optional的时候要拆包(从enum里取出来原始值)的原因。
9.举例swift中模式匹配的作用?
Swift有一个很好的特性,那就是模式匹配的扩展。模式是用于匹配的规则值,如switch语句的case,do语句的catch子句,以及if、while、guard、for-in语句的条件。
假设你想判断一个整数是大于、小于还是等于零,你可以用if-else if-else语句,尽管这并不美观:
let x = 10
if x > 0 {
print("大于零")
} else if x < 0 {
print("小于零")
} else {
print("等于零")
}
用switch语句会好很多,我理想的代码是这样:
// 伪代码
switch x {
case > 0:
print("大于零")
case < 0:
print("小于零")
case 0:
print("等于零")
}
但模式匹配默认并不支持不等式。所以我们要实现我们自己的~=
我们知道这个方法必须返回一个Bool,那正是我们需要的,我们需要知道这个值是否匹配模式。
func greaterThan(a: T)(_ b: T) -> Bool {
return b > a
}
func lessThan(a: T)(_ b: T) -> Bool {
return b < a
}
这样我们有了第一个版本的switch语句:
switch x {
case greaterThan(0):
print("大于零")
case lessThan(0):
print("小于零")
case 0:
print("等于零")
default:
fatalError("不会发生")
}
10.swift中clousure与OC中block的区别?
11.什么是capture list,举例说明用处?
宝宝心里苦,这个宝宝也不知道,百度了还是不知道,希望大佬留言解答.~~~(>_<)~~~
12.swift中private与fileprivate的区别?
1,private private 访问级别所修饰的属性或者方法只能在当前类里访问。
(注意:Swift4 中,extension 里也可以访问 private 的属性。)
2,fileprivate fileprivate 访问级别所修饰的属性或者方法在当前的 Swift 源文件里可以访问。(比如上面样例把 private 改成 fileprivate 就不会报错了)
3,internal(默认访问级别,internal修饰符可写可不写)
internal 访问级别所修饰的属性或方法在源代码所在的整个模块都可以访问。
如果是框架或者库代码,则在整个框架内部都可以访问,框架由外部代码所引用时,则不可以访问。
如果是 App 代码,也是在整个 App 代码,也是在整个 App 内部可以访问
4,public 可以被任何人访问。但其他 module 中不可以被 override 和继承,而在 module 内可以被 override 和继承。
5,open 可以被任何人使用,包括 override 和继承。
总结
现在的访问权限则依次为:open,public,internal,fileprivate,private。
13.REST、HTTP、JSON是什么?
REST(Representational State Transfer)含状态传输是一种软件架构风格。
HTTP:网络传输协议
JSON:是一种轻量级的数据交换格式
14.delegate解决了什么问题,Notification与它有什么不同?
区别:
- 效率肯定是delegate比nsnotification高。
- delegate方法比notification更加直接,最典型的特征是,delegate方法往往需要关注返回值
1)两个模块之间联系不是很紧密,就用notification传值,例如多线程之间传值用notificaiton。
2)delegate只是一种较为简单的回调,且主要用在一个模块中.例如说 NavgationController 从 B 界面到A 点返回按钮 (调用popViewController方法) 可以用delegate比较好。
15.描述一个ViewController的生命周期
当我们调用UIViewControlller的view时,
系统首先判断当前的 UIViewControlller是否存在view,如果存在直接返回view,
如果不存在的话,会调用loadview方法,
然后判断loadview方法是否是自定义方法,
如果是自定义方法,就执行自定义方法,
如果不是自定义方法,判断当时视图控制器是否有xib、stroyboard。
如果有xib、stroyboard 就加载xib、stroyboard。
如果没有创建一个空白的view。
调用viewDidLoad方法。
最后返回view
16.LLVM与Clang的区别
这两个都是编译器.
简单的说,编译器有两个职责:把 Objective-C 代码转化成低级代码,以及对代码做分析,确保代码中没有任何明显的错误。
17.LLVM与Clang的区别
- 对象方法 [实例对象 方法名]调用
- 代表实例方法,它在类的一个具体实例范围内执行,也就是说,你在调用这个方法之前必须先创建一个类的实例;
- 类方法 [类名 方法名]调用
- 代表类方法,可以通过类名直接调用,不需要创建一个类的实例。
---------------------------------------------------------------------
1、什么是kvc和kvo? 2、kvo的缺陷?
Key value Coding是cocoa的一个标准组成部分,它能让我们可以通过name(key)的方法访问property,不必调用明确的property accesser(set/get方法);
KVC(键-值编码)是一个用于间接访问对象属性的机制(一种使用字符串而不是访问器方法去访问一个对象实例变量的机制。),使用该机制不需要调用set或者get方法以及-》来访问成员变量,它通过setValue:forkey:和valueForkey:方法。
KVC的机制是啥样的呢?它是以字符串的形式向对象发送消息字符串是要关注属性的关键。是否存在setter,getter方法,如果不存在,它将在内部查找名为_key或key的实例变量,如果没有会调用setValueForUndefindedKey:,如果也没有,则会运行时报错;注意: 如果是基本数据类型,则需要封装一下(NSNumber)。
KVC的使用环境:无论是property还是普通的全局属性变量,都可以用KVC;
KVC的优缺点:
优点:
1.主要的好处就是来减少代码量
2.没有property的变量(private)也能通过KVC来设置;
KVC的缺点:如果key写错时,编写时不会报错,运行时会报错;
例子:
@interface LPProduct : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGFloat price;
@property (nonatomic, strong) LPFactory *factory;
@end
@interface LPFactory : NSObject
@property (nonatomic, copy) NSString *name;
@end
// 例1:
LPProduct *product1 = [LPProduct new];
product1.price = 1.0;
NSLog(@"price 1: %@", [product1 valueForKeyPath:@"price"]);
[product1 setValue:@100 forKeyPath:@"price"];
NSLog(@"price 1 change: %@", [product1 valueForKeyPath:@"price"]);
KVCDemo[1148:56717] price 1 change: 100
例2:
product1.factory = [LPFactory new];
[product1 setValue:@"make in japan" forKeyPath:@"[factory.name](http://factory.name)"];
NSLog(@"price 1 factory name: %@", [product1 valueForKeyPath:@"[factory.name](http://factory.name)"]);
KVCDemo[1148:56717] price 1 factory name: make in japan
KVO 是一个对象能够观察另外一个对象的属性值,并且能够发现值的变化。KVO 更加适合任何类型的对象侦听另外一个任意对象的改变,或者是一个对象与另外一个对象保持同步的一种方法,即当另外一种对象的状态发生改变时,观察对象马上作出反应。它只能用来对属性作出反应,而不会用来对方法或者动作作出反应。
KVO的优点:
1. 能够提供一种简单的方法实现两个对象间的同步;
2. 能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SDK对象)的实现;
3. 能够获得观察的属性的最新值以及先前值;
4. 用key path来观察属性,因此也可以观察嵌套对象(也就是可以观察一个对象内部对象的属性的变化,可以无限嵌套观察,前提是对象的属性支持KVO);
5. 完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察(不需要像通知一样还需要发送通知,KVO属性的改变,外部可以直接观察)。KVO的注意事项:
我们注册KVO的时候,要观察哪个属性,在调用注册方法的时候,addObserver:forKey:options:context: forKey处填写的属性是以字符串形式,万一属性名字写错,因为是字符串,编译器也不会出现警告以及检查;KVO的使用: 被观察者发出addObserver:forKey:options:context:方法来添加观察者。然后只要被观察者的keyPath的值变化(注意:单纯改变其值不会调用此方法,只有通过getters和setters来改变值才会触发KVO),就会在观察者里调用方法observeValueForKeyPath:ofObject:change:context: 因此观察者需要实现方法observeValueForKeyPath:ofObject:change:context; 来对KVO发出的通知做出响应。
这些代码只需要在观察者里进行实现,被观察者不用添加任何代码,所以谁要监听谁注册,然后对响应进行处理即可,使得观察者与被观察者完全解藕,运用很灵活很简便;但是KVO只能检测类中的属性,并且属性名都是通过NSSTring来查找,编译器不会帮你检错和补全,纯手敲所以比较容易出错。
例子:
// 3.kvo:添加当前控制器为键路径major.majorName的一个观察者,如果major.majorName的值改变会通知当前控制器从而自动调用下面的observeValueForKeyPath函数,这里传递旧值和新值
[_student addObserver:self forKeyPath:@"major.majorName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
// 3s后改变major.majorName的值
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[_student setValue:@"Software Engineer" forKeyPath:@"major.majorName"];
});
/// 监听keyPath值的变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqual:@"major.majorName"]) {
// 获取变化前后的值并打印
NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"major.majorName value changed:oldValue:%@ newValue:%@", oldValue,newValue);
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
控制台输出:oldValue:Computer Science newValue:Software Engineer
/// 移除观察者释放资源,防止资源泄漏
- (void)dealloc {
[_student removeObserver:self forKeyPath:@"major.majorName"];
}
3、Swfit和Objective-C的联系,Swift比Objective-C有什么优势?
两者各有优缺点。但是有一点是客观存在的!——Swift生于Objective-C,长于Objective-C,没有Objective-C,没有Cocoa framework,便也没有Swift.
1.swift句尾不需要分号 ,除非你想在一行中写三行代码就加分号隔开
2.swift不分.h和.m文件 ,一个类只有.swift一个文件
3.swift数据类型都会自动判断 , 只区分变量var 和常量let
......
4、举例说明Swfit里面有哪些是Objective-C中没有的?
1).swift独有的范围运算符
a…b 表示 [a,b] 如3…5 就是范围取3,4,5
2).swift独有的元组类型
var point = (x:15,y:20.2)
就是元组名是 point ,里面有两个元素x和y。 有点类似于结构体.
3).函数的默认参数值
func addStudent (name:string,age:Int = 20) –>string{}
设置了默认的年龄为20 所以再调用时只需要写个名字
addStudent(“james”)
要注意的是,使用了默认参数值, 系统会自动生成一个外部参数名。
想改名字也就要写外部参数名了 即 addStudent(“james”,age:18)
4).swift中使用let定义常量,var定义变量
使用常量,更加安全,不能够被修改,在需要对对象进行修改的时候 只能用var修饰.
5).if let 、 guard let 的用法
缩减代码量,安全处理数据逻辑。
......
5、如何对iOS设备进行性能测试?
Instruments 是应用程序用来动态跟踪和分析 Mac OS X 和 iOS 代码的实用工具。这是一个灵活而强大的工具,它让你可以跟踪一个或多个进程,并检查收集的数据。
6、使用过CocoPods吗?它是什么?CocoaPods的原理?
CocoaPods 是Mac OS X 和 iOS 应用程序开发的一个第三方库依赖的管理工具,你可以用它来 帮助集中导入、配置以及更新所用到的第三方。
CocoaPods 的原理是将所有的依赖库都放到另一个名为Pods的项目中, 然而让主项目依赖Pods项目,
这样,源码管理工作任务从主项目移到了Pods项目中.
1.Pods项目最终会编译成一个名为libPods.a的文件, 主项目只要依赖这个.a文件即可.
2.对于资源文件, CocoaPods提供了一个名为Pods-resources.sh的bash脚步, 该脚本在每次项目
编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中.
3.CocoaPods通过一个名为Pods.xcconfig的文件在编译设置所有的依赖和参数
7、集成三方框架有哪些方法?
1.手动集成
2.cocoapods集成
8、SDWebImage的原理实现机制,如何解决TableView卡的问题?
1). SDWebImage的实现原理
① 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
② 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
③ 从网络上获取,使用,缓存到内存,缓存到沙盒2).如何解决TableView卡的问题
①.复用单元格
②.单元格中的视图尽量都使用不透明的,单元格中尽量少使用动画
③.图片加载使用异步加载
④.滑动时不加载图片,停止滑动时开始加载
⑤.单元格中的内容可以在自定义cell类中的drawRect方法内自己绘制
⑥.如非必要,减少reloadData全部cell,只reloadRowsAtIndexPaths
⑦.如果cell是动态行高,计算出高度后缓存
⑧.cell高度固定的话直接使用cell.rowHeight设置高度
9、一个动画怎么实现?
CoreAnimation(核心动画)
10、iOS中常用的数据存储方式有哪些?(数据持久化)每种存储方式各有什么特点?每种存储方式各自在什么场景下使用?
所有的本地持久化数据存储的本质都是写文件,而且只能存到沙盒中。
沙盒机制是苹果的一项安全机制,本质就是系统给每个应用分配了一个文件夹来存储数据,而且每个应用只能访问分配给自己的那个文件夹,其他应用的文件夹是不能访问的。
数据存储的核心都是写文件。主要有四种持久化方式:属性列表,对象序列化,SQLite 数据库, CoreData属性列表:应用于少量数据存储,比如登陆的用户信息,应用程序配置信息等。只有NSString ,NSArray,NSDictory,NSData,可以WriteToFile;存储的依旧是plist文件,plist文件可以存储的7种数据类型:array,dictory,string,bool,data,date,number。
对象序列化:最终也是存为属性列表文件,如果程序中,需要存储的时候,直接存储对象比较方便,例如有一个设置类,我们可以把设置类的对象直接存储,就没必要再把里面的每一个属性单独存到文件中。对象序列化是将一个实现了NSCoding协议的对象,通过序列化(NSKeydArchiver)的形式,将对象中的属性抽取出来,转化成二进制流,也就是NSData,NSData可以选择write to file 或者存储到NSUserdefault中。 必须实现的两个方法 encodeWithCoder,initWithCoder。对象序列化的本质就是 对象NSData。
SQLite: 适合大量,重复,有规律的数据存储。而且频繁的读取,删除,过滤数据,这种适合使用数据库
CoreData: Sqlite叫做关系型数据库,CoreData 是一中OR-Mapping的思想 ,O代表对象Object,R代表relationship,Mapping代表映射,直译过来就是对象关系映射,其实就是把对象的属性和表中的字段自动映射,简化程序员的负担,以面向对象的方式操作数据库。ORMapping是一种思想,CoreData实现了这种思想,在Java中,hibernate 也是对ORMapping的一种实现,只是利用java实现的。
CoreData 本质还是数据库,只不过使用起来更加面向对象,不关注二维的表结构,而是只需要关注对象,纯面向对象的数据操作方式。我们直接使用数据库的时候,如果向数据库中插入数据,一般是把一个对象的属性和数据库中某个表的字段一一对应,然后把对象的属性存储到具体的表字段中.取一条数据的时候,把表中的一行数据取出,同样需要再封装到对象的属性中,这样的方式有点繁琐,不面向对象。CoreData解决的问题就是不需要这个中间的转换过程,看起来是直接把对象存储进去,并且取出来,不关心表的存在,实际内部帮你做好了映射关系。
11、说一说你对SQLite的认识?(见10)
12、runloop和线程有什么关系?
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。
所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回基本作用:
a 保持程序的持续运行(ios程序为什么能一直活着不会死)
b 处理app中的各种事件(比如触摸事件、定时器事件【NSTimer】、selector事件【选择器·performSelector···】)
c 节省CPU资源,提高程序性能,有事情就做事情,没事情就休息重要说明:
(1)如果没有Runloop,那么程序一启动就会退出,什么事情都做不了。
(2)如果有了Runloop,那么相当于在内部有一个死循环,能够保证程序的持续运行
(3)main函数中的Runloop a 在UIApplication函数内部就启动了一个Runloop 该函数返回一个int类型的值 b 这个默认启动的Runloop是跟主线程相关联的
13、runloop的mode作用是什么?(略)
14、你一般是如何调试Bug的?
1.在运行过程中,如果出现EXC_BAD_ACCESS 异常,往往提示的信息很少或者没有提示,启用NSZombieEnabled后在控制台能打印出更多的提示信息,便于debug,请注意,僵尸模式下的调试工作只能在模拟器中实现,我们无法在物理设备上完成这一诊断流程.
2.异常断点,一般程序crash时Xcode一般会定位到main函数中,得不到详细的crash信息,打上异常断点后就极大可能定位到程序的crash处,利于debug。
3.一般来说,在创建工程的时候,应该在Build Settings启用Analyze During 'Build',这样每次编译时都会自动静态分析。这样的话,写完一小段代码之后,就马上知道是否存在内存泄露或其他bug问题,并且可以修bugs。
4.如果你想在运行的时候查看APP是否存在内存泄露,你可以使用Xcode上instruments工具上的Leaks模块进行内存分析。但是有些内存泄露是很难检查出来,有时只有通过手动覆盖dealloc方法,看它最终有没有调用。
15、描述一个ViewController的生命周期 (同第一波15题)
---------------------------------------------------------------------
1.死锁的四个必要条件
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。
死锁的经典代码:
比如这个最简单的OC命令行程序就会导致死锁,运行后不会看到任何结果。
回到上面的死锁代码中:首先明确的是:执行这个dispatch_get_main_queue队列的是主线程。执行了dispatch_sync函数后,将block添加到了main_queue中,同时调用dispatch_syn这个函数的线程(也就是主线程)被阻塞,等待block执行完成,而执行主线程队列任务的线程正是主线程,此时他处于阻塞状态,所以block永远不会被执行,因此主线程一直处于阻塞状态。因此这段代码运行后,并非卡在block中无法返回,而是根本无法执行到这个block。
修改方法:
dispatch_async(dispatch_get_global_queue(0,0), ^(void){
NSLog(@"这就不死锁了");
});
2.进程与线程
这个其实是操作系统的问题,在网上看到个通俗精辟的例子:
开个QQ,开了一个进程;开了迅雷,开了一个进程。在QQ的这个进程里,传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。所以运行某个软件,相当于开了一个进程。在这个软件运行的过程里(在这个进程里),多个工作支撑的完成QQ的运行,那么这“多个工作”分别有一个线程。所以一个进程管着多个线程。通俗的讲:“进程是爹妈,管着众多的线程儿子”...
3.OC 和 js 交互 OC怎么调用js函数, JS怎么调用OC方法 ?
WebViewJavascriptBridge
4.代理和block的区别 ? block循环引用产生的原因, 以及怎么处理?
在定义一个类的property时候,为property选择strong还是copy特别注意和研究明白的,如果property是NSString或者NSArray及其子类的时候,最好选择使用copy属性修饰。为什么呢?这是为了防止赋值给它的是可变的数据,如果可变的数据发生了变化,那么该property也会发生变化。
代码示例
@interface Person : NSObject
@property (strong, nonatomic) NSArray *bookArray1;
@property (copy, nonatomic) NSArray *bookArray2;
@end
//Person调用
main(){
NSMutableArray *books = [@[@"book1"] mutableCopy];
Person *person = [[Person alloc] init];
person.bookArray1 = books;
person.bookArray2 = books;
[books addObject:@"book2"];
NSLog(@"bookArray1:%@",person.bookArray1);
NSLog(@"bookArray2:%@",person.bookArray2);
}
我们看到,使用strong修饰的person.bookArray1输出是[book1,book2],而使用copy修饰的person.bookArray2输出是[book1]。这下可以看出来区别了吧。
备注:使用strong,则person.bookArray1与可变数组books指向同一块内存区域,books内容改变,导致person.bookArray1的内容改变,因为两者是同一个东西;而使用copy,person.bookArray2在赋值之前,将books内容复制,创建一个新的内存区域,所以两者不是一回事,books的改变不会导致person.bookArray2的改变。
5.类别与扩展的区别?
扩展写法上跟类别一致,只是括号中没有类别描述。
“扩展”可以添加属性、变量,类别不能。分类运用场景举例:想要收集每个页面的启动时间。
问题1:
项目中已经有上百个页面了,如果一个一个的加,浪费时间不说,以后增加了新页面,还需要添加方法
解决方法:
我们可以发现页面都继承了UIViewController,想要在每个页面都执行的代码,可以写在这些页面的父类中。我们可以把代码写在UIViewController中。UIViewController是官方类,我们只能调用期接口,并不能修改他的实现。所以需要使用分类(category).
1.分类(category)的作用
1.1作用:可以在不修改原来类的基础上,为一个类扩展方法。
1.2最主要的用法:给系统自带的类扩展方法。
2.分类中能写点啥?
2.1分类中只能添加“方法”,不能增加成员变量。
2.2分类中可以访问原来类中的成员变量,但是只能访问@protect和@public形式的变量。如果想要访问本类中的私有变量,分类和子类一样,只能通过方法来访问。分类(category)和类扩展(extension)的关系
1.类扩展(extension)是category的一个特例,有时候也被称为匿名分类。他的作用是为一个类添加一些私有的成员变量和方法。
2.类扩展能写点啥?和分类不同,类扩展即可以声明成员变量又可以声明方法。
3.类扩展听上去很复杂,但其实我们很早就认识他了。你记得继承自UIViewController的ViewController吧.
@interface ViewController()//这就是类扩展的写法
@end
严格意义上来说,oc是没有私有变量或者方法这一说的,不过我们可以通过延展来实现这个私有方法或者变量。类扩展可以定义在.m文件中,这种扩展方式中定义的变量都是私有的,也可以定义在.h文件中,这样定义的代码就是共有的,类扩展在.m文件中声明私有方法是非常好的方式。
类扩展中添加的新方法,一定要实现。categorygory中没有这种限制。
6.怎么实现一个精准的Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doAnything) userInfo:nil repeats:NO];
// 将定时器添加到runloop中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 在线程中使用定时器,如果不启动run loop,timer的事件是不会响应的,而子线程中runloop默认没有启动
// 让线程执行一个周期性的任务,如果不启动run loop, 线程跑完就可能被系统释放了
[[NSRunLoop currentRunLoop] run];// 如果没有这句,doAnything将不会执行!!
runloopmode是一个集合,包括监听:事件源,定时器,以及需通知的runloop observers
模式包括:
default模式:几乎包括所有输入源(除NSConnection) NSDefaultRunLoopMode模式
mode模式:处理modal panels
connection模式:处理NSConnection事件,属于系统内部,用户基本不用
event tracking模式:如组件拖动输入源 UITrackingRunLoopModes 不处理定时事件
common modes模式:NSRunLoopCommonModes 这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。
7.当app越做越大时, 会发现App冷启动时, 速度很慢, 你有没有遇到过? 你是怎么优化的?
一般而言,启动时间是指从用户点击 APP 那一刻开始到用户看到第一个界面这中间的时间。我们进行优化的时候,我们将启动时间分为 pre-main 时间和 main 函数到第一个界面渲染完成时间这两个部分。
大家都知道 APP 的入口是 main 函数,在 main 之前,我们自己的代码是不会执行的。而进入到 main 函数以后,我们的代码都是从didFinishLaunchingWithOptions开始执行的,所以很明显,优化这两部分的思路是不一样的。
为了方便起见,我们将 pre-main 时间成为 t1 时间,而将main 函数到第一个界面渲染完成这段时间称为 t2 时间。 然后用profile 工具来分析出哪些代码是耗时的。
8.在适配 iOS 11时, 经常出现的问题
都有哪些? 以及你是怎么解决的
适配点一:项目中使用状态栏中图标判断当前网络的具体状态
此时可以看到运行崩溃了,因为从iPhone X取出来之后只有view层级的信息,所以采用以下方法确定2G/3G/4G
NSArray *typeStrings2G = @[CTRadioAccessTechnologyEdge,
CTRadioAccessTechnologyGPRS,
CTRadioAccessTechnologyCDMA1x];
NSArray *typeStrings3G = @[CTRadioAccessTechnologyHSDPA,
CTRadioAccessTechnologyWCDMA,
CTRadioAccessTechnologyHSUPA,
CTRadioAccessTechnologyCDMAEVDORev0,
CTRadioAccessTechnologyCDMAEVDORevA,
CTRadioAccessTechnologyCDMAEVDORevB,
CTRadioAccessTechnologyeHRPD];
NSArray *typeStrings4G = @[CTRadioAccessTechnologyLTE];
// 该 API 在 iOS7 以上系统才有效
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
CTTelephonyNetworkInfo *teleInfo= [[CTTelephonyNetworkInfo alloc] init];
NSString *accessString = teleInfo.currentRadioAccessTechnology;
if ([typeStrings4G containsObject:accessString]) {
NSLog(@"4G网络");
} else if ([typeStrings3G containsObject:accessString]) {
NSLog(@"3G网络");
} else if ([typeStrings2G containsObject:accessString]) {
NSLog(@"2G网络");
} else {
NSLog(@"未知网络");
}
} else {
NSLog(@"未知网络");
}
适配点二:解决这个问题后项目跑起来发现,整个app界面上下各空出大概40pt的高度.造成这个的原因是启动图使用 Launch Images Source 设置的时候没有勾选并设置对应的图片.
但是即使按照上面的操作进行之后,会发现底部 UITabBar 依旧是高出一些高度,查看层级关系后发现,同样是由于安全区的原因,UITabBar 高度由49pt变成了83pt,因此这里也要对iPhone X 及其模拟器进行适配适配点三:iPhone X 只有 faceID,没有touchID,如果in的应用有使用到 touchID 解锁的地方,这里要根据机型进行相应的适配
适配点四: iPhone X更大的坑是屏幕的适配Safe area.
9.谈谈instancetype和id的异同
1、相同点
都可以作为方法的返回类型
2、不同点
①instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
②instancetype只能作为返回值,不能像id那样作为参数
10.isKindOfClass和isMemberOfClass的区别
isKindOfClass来确定一个对象是否是一个类的成员,或者是派生自该类的成员
isMemberOfClass只能确定一个对象是否是当前类的成员
第四波
这套题目是我在知乎上看到的,是MrPeak大大出的题目,好开森哦,尝试着来回答下,还希望各位大佬多多指教.
一份"有点难"的iOS面试题 .
其它知识点汇总:
iOS面试--GCD常见用法.
iOS面试-Runloop简单介绍.
iOS面试-Runtime简介.