续写
iOS 面试题及答案20道1~20(一)iOS 面试题及答案20道21~40(二)
41.谈谈对OC和Swift动态特性的理解
runtime其实就是OC的动态机制。runtime执行的是编译后的代码,这时它可以动态加载对象、添加方法、修改属性、传递信息等。具体过程是,在OC中,对像调用方法时,如[self.tableview reload],经历了两个过程。
- 编译阶段: 编译器会将OC代码翻译成objc_msgSend(self.tableview,@slector(reload)),把消息发送给self.tableview。
- 运行阶段: 接收者(self.tableview)会响应这个消息,其间可能会直接执行,转发消息,也可能会找不到方法导致程序崩溃。
所以,整个流程是:编译器翻译->给接收者发送消息->接收者响应消息。
接收者如何去响应消息,就发生在运行时。runtime执行的是编译后的代码,而代码的具体实现是运行时才能够确定,这时它就可以动态加载对象、添加方法、修改属性、传递消息等。runtime的运行机制就是OC的动态特性。
Swift公认的是一个静态语言,它的动态特性都是通过桥接OC来实现的,像一些动态的特性其实是可以用Swift的面向协议的编程那样使用的。
例子:
在OC中我们去动态的调用一个方法,首先需要判断这个方法是否存在然后再去调用它,这就是纯正的动态特性:代码
OC的动态特性
UserModel类的实现
@implementation UserModel
- (void)todoSomething{
NSLog(@"正在做什么事情。");
}
@end
动态调用UserModel的方法。
UserModel *user = [UserModel new];
if([user respondsToSelector:NSSelectorFromString(@"todoSomething")]){
[user performSelector:NSSelectorFromString(@"todoSomething")];
}
Swift的面向协议编程
面向协议的实现代码
protocol TodoProtocol {
func todosomething()
}
class UserModel: TodoProtocol {
func todosomething() {
print("要做的事情")
}
}
调用代码
let user:UserModel = UserModel()
if let user1 = user as? TodoProtocol {
user1.todosomething()
print("实现了这个协议")
}else{
print("没有实现这个协议")
}
这样来使用,这个协议的方法,其实这步骤判断不需要因为swift中的协议的方法都是required的没有optional。
42.谈谈as、as!、as? 含义是什么?
1)as的三种使用方式
- as第一种用法是将派生类转换成基类,也就是说子类直接转换成父类。 向上转型。
代码:
class Animal{
var name:String
init(_ name:String){
self.name = name
}
}
class catanimal : Animal {
}
class doganimal: Animal {
}
//as 向上强转 子转父
let cat = catanimal("汤姆") as Animal
let dog = doganimal("泰克") as Animal
showAnimalName(cat)
showAnimalName(dog)
如果是父类转换成子类,那么这种形式在Swift是会报错的,就算是加上!强转a!运行的时候也会崩溃。
- as第二种用法消除多义性,数据类型转换
比如说
let AnimalAge = 3 as Int //3 有可能是Int 也有可能是CGFloat 也可能是long Int
let AnimalWeight = 20 as CGFloat
let AnimalHeight = (50 / 2) as Double
- as的第三种用法switch语句中进行模式匹配,通过switch愈发检测对象的类型,根据对象类型进行处理。
switch animalType{
case let animal as catanimal:
print("是catanimal类型")
case let animal as doganimal:
print("是doganimal类型")
default: break
}
2)as!的用法
向下转型,强制转换类型,有个缺点就是如果转化失败就会报错误。
一般形式下可以这么使用,
catanimal是Animal的子类。
let animal:Animal = catanimal("汤姆")
let cat = animal as!catanimal
上述代码就是将类型强制转化,首先要注意一点,在强转之前一定要判断animal这个类是用catanimal这个类初始化的, 如果不这样做的话,是用Animal初始化的,那么在第二行强制转化的话就会报错。
3)as?的用法
as?跟as!是一样的用法,只不过as?如果转化不成功的话会直接返回一个nil对象,成功的话返回可选类型。如果说能%100的会成功转化那么请用as?如果说不能的话请用as!
使用例子:
let animal:Animal = catanimal("汤姆")
if let cat = animal as? catanimal {
print("是catanimal类型")
}else{
print("nil")
}
43.查看下述代码输出是什么?为什么?
protocol Police{
func HandlingCases()
}
extension Police{
func HandlingCases(){
print("Police doing Handling cases!" );
}
}
struct Judge:Police{
func HandlingCases(){
print("Judge doing Handling cases!" );
}
}
调用代码
let police1:Police = Judge()
let police2:Judge = Judge()
police1.HandlingCases()
police2.HandlingCases()
两个输出的内容都是Judge doing Handling cases!
,
因为在Swift中protocol声明了某个方法,在没有extension扩展协议的情况下必须在实现类中实现该方法,swift中的协议的方法都是required的,如果extension中实现了该方法,则在实现类Judge
中可以不用去实现,一旦实现,那么还是以实现类Judge
的实现方法为准。HandlingCases
方法在Police
协议中声明了,police1
虽然声明的是Police
但是实际实现还是Judge
所以根据实际情况是调用了Judge
的HandlingCases
实现方法。同样道理police2
也是如此。
但是如果说在Police
中没有生命方法HandlingCases
,其他不变的情况下,那么police1
、police2
的输出就会不一样了。因为police1
的实际类型是Police
,Police
中并没有声明HandlingCases
,但是在类扩展中有实现该方法,实际类调用实际的方法,那么就会调用扩展类中实现HandlingCases
,而police2
的实际类型是Judge
那肯定就会调用Judge
中的实现方法。
所以他们的输出是:
police1
输出 Police doing Handling cases!
police2
输出 Judge doing Handling cases!
44.message send如果找不到对象,则会如何进行后续处理
这种形式一般会有两种情况:1)对象是nil; 2)对象不为nil但是就是找不到对应的方法;
1)对象为空的时候,在OC中向一个nil的对象发送小时是可以的,如果这个方法的返回值是对象那么返回的是nil,如果返回值是结构体,那么就是0.
2)对象不为空,找不到方法,就会崩溃,报错。
45.method swizzling 是什么?
每一个类都会维护一个方法列表,并且方法名跟方法实现是一一对应的,也就是SEL(方法名)和IMP(方法实现的指针)的对应关系,
method swizzling的意义就是运用runtime的特性跟方法来进行SEL和IMP这一对的IMP进行更换操作。如果SELa对应IMPa SELb对应IMPb 使用method swizzling后可以成为SELa对应IMPb SELb对应IMPa。
代码实现
在本类实现2个方法:
- (void)onefunc{
NSLog(@"one");
}
- (void)twofunc{
NSLog(@"two");
}
在本类中调用代码
SEL one = @selector(onefunc);
Method OneMethod = class_getInstanceMethod([self class], one);
SEL two = @selector(twofunc);
Method TwoMethod = class_getInstanceMethod([self class], two);
method_exchangeImplementations(OneMethod, TwoMethod);
这样就可以看到调用onefunc会打印two, 调用twofunc会打印one
46.Swift和OCjective-C的自省(Introspection)有什么不同
自省在Objective-C中就是:判断一个对象是否属于某个类的操作。它有两种形式。
[objc isKindOfClass:[SomeClass class]];
[objc isMemberOfClass:[SomeClass class]];
第一个判断isKindOfClass
是判断objc是否为SomeClass或者其子类的实例对象。
第二个判断isMemberOfClass
是判断objc仅仅是SomeClass这个类(非子类,当前类)的实例对象,并且不能检测任何类都是基于NSObject类。
这两种判断的前提是objc必须是NSObject的子类。
Swift中只有isKindOfClass
类似的方法is
,很多的类并不是继承自NSObject,不过比OC的功能更加强大,is
可以判断enum、struct类型
自省操作一般和动态类型一起出现,比如说OC中的id类型,以及Swift中的可选类型、anyobject。
cat是animal的子类
id animal = catInstance;
if([animal isKindOfClass:[animal class]]){
NSLog(@"是 animal class");
if(animal isMemberOfClass:[cat class]){
NSLog(@"是 cat class");
}
}else if([animal isKindOfClass:[any(其他类) class]]){
NSLog(@"是 其他 class");
}
47.能否通过Category给已有的类添加属性(property)
无论是Swift还是OC都可以用Category来添加属性,只不过添加的方式不一样。
Objective-C:
OC中通过Category中直接添加属性(property)会报错,提示找不到getter和setter方法,那是因为在Category中不会自动生成这两个方法,解决的方法就是运用runtime,关联对象的形式来添加属性,主要涉及到的两个函数是,objc_getAssocicatedObject和objc_setAssociatedObject.
objc_getAssocicatedObject两个参数: 本类实例对象、 关联属性的key
objc_setAssociatedObject中的方法有4个参数,分别是 本类实例对象、关联属性的key、新值、关联策略。
@interface UserModel : NSObject
@end
@implementation UserModel
@end
@interface UserModel(en)
@end
#import "UserModel+En.h"
#import <objc/runtime.h>
static void *EnNameKey = &EnNameKey;
@implementation UserModel(en)
-(void)setEnName:(NSString *)EnName{
objc_setAssociatedObject(self, &EnNameKey, EnName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)EnName{
return objc_getAssociatedObject(self, &EnNameKey);
}
@end
Swift跟Objective-C的使用方式差不多,如下代码
class UserModel {
var Name:String = "小明"
}
var EnNameKey:Void?
extension UserModel{
var EnName:String? {
get{
return objc_getAssociatedObject(self, &EnNameKey) as? String
}
set{
objc_setAssociatedObject(self, &EnNameKey, "你的英文名字", objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
}
48.谈谈看对Xcode你有多少理解。
iOS开的IDE是Xcode,它是apple开发的主流工具,目前Xcode已经更新到了9个版本。功能蕴含了开发、测试、性能分析、文档查询、源代码管理等多方面功能。
C、C++与Objective-C密不可分,自动化用Ruby,熟悉的工具:fastlane、cocoapods、Automation工具的脚本大多是用javaScript,刚发布的coreML采用的模型工具则是用Python开发的。最新的Xcode采用完全由Swift重写的Source Editor,在代码修改、补全、模拟器运行方面有了很大的提升,目前,Xcode最大的缺点是稳定性还不够。
Xcode工具想用的熟练,则必须从Intruments性能分析和LLDB调试,一步一步进行由浅入深、Swift最新的Playground也是一个不错的工具。
49.LLDB中的p和po的区别
- p是exor的缩写。它的工作是把接受到的参数在当前环境下运行编译,人后打印出对应的值。
- Po即expr-o-。操作跟p相同,如果接收的是一个指针,那么它会调用对象的description方法并打印;如果接收到的参数是一个core foundation对象,那么它会调用CFShow方法并打印,如果两个方法都调用失败,那么po打印出和p相同的内容。
- Po相对于p来说可以多打印一些内容,一般用p即可,毕竟打印的东西越少越快,效率越高。如果需要查看详情就用Po。
50.description方法是干什么用的?
这个是iOS对象默认的一个方法,它的输出格式一般为<类名: 对象的内存地址>
也可以自定义这个输出格式,NSObject类所包含。
51.Xcode中的Buildtime issues和Runtime issues指的是什么?
Buildtime issues一般分为三类:编译器识别出的警告(Warning)、错误(Error)和静态分析(Static Code Analysis)。前两者一般经常会遇到,不用多说,静态分析也可以分为三种:1)未初始化变量,未使用数据和API使用错误。
Swift代码:
class CusViewController: UIViewController {
override func viewDidLoad() {
let PeopleList:[UserModel]
let somePeopleList = PeopleList
let otherPeopleList:[UserModel]?
}
}
分析代码:
PeopleList并没有初始化就去赋值给somePeopleList所以是未初始化报错。
otherPeopleList并没有使用,那么就会出现未使用的数据,在viewDidLoad中没有使用super.viewDidLoad()那么就是Api调用错误。
Runtime issues也有三类错误:线程问题、UI布局和渲染问题、内存问题。线程的相关问题最多,最常见的就是数据的竞争。
var num = 0
let addnum1 = 10,addnum2 = 100
DispatchQueue.global().async {
for _ in 0...10{
num += addnum1
}
}
for _ in 0...10 {
num += addnum2
}
两个线程都对num进行写操作,这样的话,谁先操作,那么值就会根据方法+几 因为数据直接就存在了争抢关系,当然最终结果是一样的,但是中间的先后顺序就会打乱了。
UI布局和渲染上面的时候尺寸设定、布局没有给全,渲染设定模糊,因而造成的autolayout无法渲染。
内存上的问题就是内存泄漏,比如循环引用等。
52.App启动时间过长,该怎么去优化?
导致App启动时间过长的原因有多种,从理论上面来讲有两种情况:1)main函数加在之前;2)main还是加载之后。
main 函数加载之前,如果想要分析这块儿的代码,需要去Xcode中添加DYLD_PRINT_STATISTICS环境变量,并将其值设置为1,这样就可以得到如下的启动日志:
还有很多的静态变量,如果想查看的话,在终端man dyld
会打印出你要的静态变量的列表,可以一个一个的去打印看看。
例子:
Total pre-main time: 339.26 milliseconds (100.0%) //总的时间 毫秒
dylib loading time: 154.24 milliseconds (45.4%) //库加载
rebase/binding time: 78.42 milliseconds (23.1%) //重定向/绑定
ObjC setup time: 69.27 milliseconds (20.4%) //对象设置
initializer time: 37.18 milliseconds (10.9%) //初始化
slowest intializers :
libSystem.B.dylib : 8.49 milliseconds (2.5%) //系统库
libMainThreadChecker.dylib : 17.09 milliseconds (5.0%) //系统库
从上面打印的内容来看,大致就是上面的四个方面: 动态库加载、重定位绑定对象、设置对象、对象的初始化。
通过上述打印我们可以通过以下方式来优化App的启动时间:
- 减少动态库的数量,动态库加载时间会减少,apple推荐的动态库数量不超过6个。
- 减少Objective-C的类数量,例如合并和删除,这样可以加快动态链接,重定位/绑定耗费的时间会相应的减少。
- 使用initialize方法替代load方法,或尽量将load方法中的代码延后调用。对象的初始化所耗费的时间会相应减少。
在main之后的app启动时间主要是要优化第一个界面的渲染速度,主要是看进入Viewdidload viewwillAppear这两个方法是否有其他操作。