【2019年4月】iOS研发工程师面试笔试题

某专业IT技术社区的iOS面试中遇到题目,记录一下。


(一)为什么说OC是一门动态语言?

答:Objective-C 是C 的超集,在C 语言的基础上添加了面向对象特性,并且利用Runtime 这个运行时机制,为Objective-C 增添了动态的特性。Objective-C 使用的是 “消息结构” 而非 “函数调用”,使用消息结构的的语言,其运行时所执行的代码由运行期间决定;而使用函数调用的语言,则由编译器决定。其次,动态和静态是相对的。这里的动态语言是指不需要在编译时确定所有的东西,在运行时还可以动态添加变量、方法和类。Objective-C 可以通过Runtime这个运行时机制,在运行时动态的添加变量、方法、类等。Objective-C 的动态特性可从三方面描述:

1.动态类型识别(Dynamic typing)——最终判定该类的实例类型是在运行期间:

    (1)OC中有一个可以表示任何实例对象类型的关键字(id),将对象声明为id类型,可根据需要,赋予不同类型的实例对象。

    (2)父类指针同样也可以指向子类实例对象,编译期指针类型为父类,运行后可判断为具体的某个子类。

    (3)NSData *test = [[NSString alloc] init]; 这段代码在编译期test被认定为NSData类型,运行后则为NSString类型,其值为空字符串(""),可以很好的解释OC的动态类型识别。

2.动态绑定(Dynamic binding):在运行时确定调用的方法:即运行时决定调用方法(消息发送)。动态绑定是实现OC多态的基础,所谓多态指的是不同对象对同一方法(函数)有着不同实现,常见于子类继承父类,重写父类方法,不同的子类实现该方法不同,通过父类指针指向子类来完成。

3.动态加载(Dynamic loading)——在运行期间可添加模块(类、方法):

    (1)动态添加属性:在分类(category)中添加属性,只会生成setter和getter,不会生成变量,如果想实现添加变量需要使用对象关联方法,objc_getAssociatedObject()和objc_setAssociateObject()

    (2)动态添加方法:通过class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)各个参数解释:cls 类名;name SEL类型的方法名(通过@selector()得到);imp,函数指针,如果是C方法的话可直接写方法名,如果是OC方法的话,可通过class_getMethodImplementation(Class cls, SEL name)方法获得;types,返回值类型,是个C字符串,"V@:"表示返回值类型为空,无参,"i@:"返回值为int,无参,"i@:@",返回值为int,一个参数。

    (3)动态添加类:通过objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,size_t extraBytes)添加要动态创建的类,然后添加方法和变量,最终使用objc_registerClassPair(Class _Nonnull cls) 将创建的类注册到runtime中。



(二)frame 和 bounds 的区别?


定义:

(CGRect)frame CGRectMake(self.frame.origin.x,self.frame.origin.y,self.frame.size.width,self.frame.size.height);

frame是相对于父视图的坐标系统而言,其原点(origin)可以任意设置,表示当前视图原点距离父视图原点的位置。

(CGRect)bounds CGRectMake(0,0,self.frame.size.width,self.frame.size.height);

bounds是相对于本地坐标系统而言,它的原点(origin)默认是(0,0),可通过view.bounds.origin进行修改,修改后意味着将本地坐标系的原点进行移动,会影响到子视图原点的位置。



(三)UIView和CALayer的区别与联系

区别:

UIView 继承于UIResponder(UIResponder继承于NSObject):UIResponder定义了各种可以响应系统事件的接口,它侧重对于显示内容的管理。

CALayer 继承于NSObject:不具备响应事件的功能,它侧重对于显示内容的绘制。

联系:

1.UIView初始化时会自动创建layer,layer会自动指定代理为view。所以UIView和CALayer是代理和被代理的关系。UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法让UIView使用不同的CALayer来显示。

2.UIView本身不具备显示功能,拥有显示功能的是它内部的图层即CALayer属性。当UIView需要显示到屏幕上时,会调用DrawRect:方法进行绘图,并且将所有的内容绘制在自己的图层上,绘图完成后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示。

3.view中对于图片的渲染和一些动画操作都是由layer完成的,view用于呈现layer的工作结果和响应系统事件。

4.UIView和CALayer相互依赖。UIView的显示依赖于CALayer提供的内容。CALayer依赖UIView的容器来显示绘制的内容。

5.UIView来自CALayer,高于CALayer,是CALayer的高层实现与封装。UIView的所有特性来源于CALayer的支持。



(四)Category和Extension的区别

Category(分类、类别):可以在不修改原类的基础上,为一个类扩展方法;  主要用于给系统自带的类扩展方法。

1.分类中只能添加“方法”,不能增加成员变量。

2.分类中可以访问原类中的成员变量,但是只能访问@protect和@public形式的变量。如果想要访问本类中的私有变量,分类和子类一样,只能通过方法来访问。

3.如果一定要在分类中添加成员变量,可以通过getter,setter手段进行添加,或者使用@dynamic(Runtime运行时机制)来弥补这种不足。

4.如果Category中的方法和类中原有方法同名,运行时会优先调用Category中的方法。

5.如果多个Category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。

6.Category是运行时属性

Extension(类扩展、匿名分类):可以声明方法,属性和成员变量,一般用于声明私有的方法、属性和成员变量,大多数直接写到.m文件中。

1.类扩展定义在.m文件中时,这种扩展方式中的变量都是私有的,定义在.h文件中时,其中定义的代码就是公有的。

2.Extension是编译时属性

区别:

· Category拥有.h文件和.m文件,但Extension只存在于一个.h文件或者Extension只能寄生于一个类的.m文件中。

· Extension是编译时属性,它就是类的一部分,而Category是运行时属性的;Extension在编译时和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,Extension伴随类的产生而产生,亦随之一起释放。

· Extension一般用来隐藏类的私有信息,必须有一个类的源码才能为一个类添加Extension,所以无法为系统类比如NSString添加Extension,除非创建子类后再添加Extension。而Category不需要有类的源码,我们可以给系统提供的类添加Category。

· Extension可以添加实例变量,而Category不可以添加实例变量。

· Extension和Category都可以添加属性,但是Category的属性不能生成成员变量和getter、setter方法的实现。


(五)如何写一个完整的单例?

定义:单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。大部分单例模式的作用是共享信息,管理中心。单例在传值方面也是可以使用的。

iOS中的单例模式



(六)当使用weak修饰的对象被释放后,系统是否直接释放掉此对象?其实现原理是什么?

weak指针不会增加所引用对象的计数,在引用对象被回收时自动被设置为nil。

实现原理:runtime会对注册的类进行布局。对于 weak修饰的对象会放入一个hash表中,用 weak 指向的对象的内存地址作为 key,当此对象的引用计数为0时执行dealloc。假如 weak 指向的对象的内存地址是x,那么就会以x为键, 在整个weak表中搜索,找到所有以x为键的weak对象,并设置为nil。




(七)(1)声明可变数组可以用weak修饰吗?和用strong修饰有什么区别?(2)weak和assign的区别?(3)用@property声明的NSString、NSArray和NSDictionary经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?(4)可以用copy修饰可变数组吗?(5)说说深拷贝与浅拷贝?

I:声明数组如果使用weak修饰,则数组在创建完毕会立即被销毁。使用strong(强引用)修饰时,不会被释放。

IIweak只可以修饰对象,如果修饰基本数据类型,编译器会报错(Property with ‘weak’ attribute must be of object type)。

     assign可以修饰对象和基本数据类型,但修饰基本数据类型时可能会出现野指针。而weak不会产生野指针。

     原因:weak不会带来野指针问题,当weak修饰的对象被释放后(引用计数为0),其指针会自动置为nil。之后再向该对象发送消息也不会报错,所以weak是安全的,同时避免了循环引用;对于assign,如果用来修饰基本数据类型则不会产生问题。如果用来修饰对象,那么释放后指针不会自动置空,这时再向对象发送消息程序就会Crash。

     结论:在ARC模式下编程时,指针变量一定要用weak修饰,只有基本数据类型和结构体需要用assgin,例如delegate一定要用weak修饰。

III:使用copy关键字的原因

IV:不可以;NSMutableArray用copy修饰之后,在调用addObjectsFromArray方法时会崩溃(unrecognized selector sent to instance)。

       原因:OC是一门动态语言,在编译阶段不会做类型检测。OC的内存管理是引用计数,在ARC环境下,属性@property的内存管理语义关键字有copy,weak,strong,asssin。在编译阶段,默认情况下编译器会生成一个成员变量、一个setter方法、一个getter方法。而在setter方法中,会根据内存管理语义做相应的引用计数相关的操作。当使用copy修饰属性时,在setter中的实际操作是拷贝了一份不可变的类型对象。这样的话,即使是可变类型,在被赋值后得到的却是不可变类型的对象。

V    定义:

                   图文并茂理解深拷贝与浅拷贝

                   浅拷贝:拷贝后只是指向了该对象的地址,这两个对象指向了同一个内存地址,通过任何一个对象修改值,这两个对象再次获取值都是获取修改后的值。

                    深拷贝:拷贝了对象的内容然后重新开辟了新的内存空间,这是两个互相独立的对象,对任意一个修改值,另一个的值都不会改变。

          官方文档:Shallow Copies & Deep Copies



(八)实现一个反转二叉树

iOS实现反转二叉树


(九)下面代码的输出是什么?

@implementation Son:Father

-(id)init{

     if(self = [super init]){

          NSLog(@”%@”,NSStringFromClass([self class]));

          NSLog(@”%@”,NSStringFromClass([super class]));

             return self;

}

@end

答案:两个都打印出:Son。

self 是类的隐藏参数,指向当前调用方法的类的实例。

super是一个Magic Keyword,它本质是一个编译器标示符,和self指向的是同一个消息接收者。

不同的是:super会告诉编译器,调用class这个方法时,要去调用父类里的方法而不是本类里的。

上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *obj 这个对象。


(十)在项目中若出现以下的代码可能会有什么问题?

for(inti=0;i<1000;i++) {

       NSString*num = [NSStringstringWithFormat:@"%d",i];//num是临时变量

}

问题:大次数循环导致内存暴涨。

解释:该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏。解决方法为在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。例子如下:

for(int i =0; i <100000; i++) {

        @autoreleasepool {

               NSString *string= @"Abc";

               string= [stringlowercaseString];

               string= [stringstringByAppendingString:@"xyz"];

               NSLog(@"%@",string);

        }

}


(十一)内存泄漏可能会出现的几种原因,聊聊你的看法?如果是非OC对象如何处理?若常用框架出现内存泄漏如何处理?

第一种可能:第三方框架不当使用;

第二种可能:block循环引用;

第三种可能:delegate循环引用;

第四种可能:NSTimer循环引用

第五种可能:非OC对象内存处理

第六种可能:地图类处理

第七种可能:大次数循环内存暴涨

详见 https://www.jianshu.com/p/0f6119115548


(十二)(1)Objective-C的类可以多重继承么?(2)可以实现多个接口么?(3)Category是什么?(4)重写一个类的方式用继承好还是分类好?

I:Objective-C 不支持多继承,但可以通过类别或协议的方式来实现。

II:OC可以实现多个接口。

III:category是类别,可以为类添加新方法。

IV:重写一个类用分类的方式好,仅对本Category有效,不会影响到其他类与原有类的关系。

详见 http://www.cnblogs.com/fkdd/archive/2012/03/14/2396284.html


(十三)一个objc对象的isa指针指向什么?有什么作用?

isa 指的就是“是个什么”,对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。isa帮助一个对象找到它的方法。

isa是一个Class 类型的指针,每个实例对象都有一个isa指针,它指向对象的类,而Class里也有个isa的指针,指向metaClass(元类),元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(metaClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

详解 https://www.cnblogs.com/loying/p/5080429.html


(十四)isKindOfClass、isMemberOfClass、selector 作用分别是什么?

isKindOfClass:Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.(判断是否是这个类或者这个类的子类的实例)

isMemberOfClass: Returns a Boolean value that indicates whether the receiver is an instance of a given class.(判断是否是这个类的实例)

selector:通过方法名,获取在内存中的函数的入口地址。详见:https://blog.csdn.net/yasi_xi/article/details/46811803


(十五)_objc msgForward 函数是做什么的,直接调用它将会发生什么?

https://blog.csdn.net/songchunmin_/article/details/51452003



以上答案均搜集自网络,部分答案结合个人理解作出改动。如有错误,欢迎指正。

你可能感兴趣的:(【2019年4月】iOS研发工程师面试笔试题)