续写
iOS 面试题及答案20道1~20(一)iOS 面试题及答案20道21~40(二)

41.谈谈对OC和Swift动态特性的理解

runtime其实就是OC的动态机制。runtime执行的是编译后的代码,这时它可以动态加载对象、添加方法、修改属性、传递信息等。具体过程是,在OC中,对像调用方法时,如[self.tableview reload],经历了两个过程。

  1. 编译阶段: 编译器会将OC代码翻译成objc_msgSend(self.tableview,@slector(reload)),把消息发送给self.tableview。
  2. 运行阶段: 接收者(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所以根据实际情况是调用了JudgeHandlingCases实现方法。同样道理police2也是如此。

但是如果说在Police中没有生命方法HandlingCases,其他不变的情况下,那么police1police2的输出就会不一样了。因为police1的实际类型是PolicePolice中并没有声明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这两个方法是否有其他操作。