iOS面试笔试题(2018年8月)

一、第一家面试题
IMG_6615.jpg
IMG_6616.jpg
IMG_6617.jpg
二、第二家面试题
IMG_6632.jpg
IMG_6631.jpg
三、答案
1.1、当使用 weak修饰的对象被释放后,系统是否直接释放掉此对象?其实现原理是什么? (15分)

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

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

1.2、声明可变数组可以用weak修饰吗?和用strong修饰有什么区别? (10 分)

答案:数组怎么能用weak啊,如果用weak,那么数组一创建完毕就被销毁了。只能用strong。强引用,不会被释放。

1.3、反转二叉树。(10 分)
/**
 *  翻转二叉树(又叫:二叉树的镜像)
 *
 *  @param rootNode 根节点
 *
 *  @return 翻转后的树根节点(其实就是原二叉树的根节点)
 */
+ (BinaryTreeNode *)invertBinaryTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return nil;
    }
    if (!rootNode.leftNode && !rootNode.rightNode) {
        return rootNode;
    }
    
    [self invertBinaryTree:rootNode.leftNode];
    [self invertBinaryTree:rootNode.rightNode];
    
    BinaryTreeNode *tempNode = rootNode.leftNode;
    rootNode.leftNode = rootNode.rightNode;
    rootNode.rightNode = tempNode;
    
    return rootNode;
}

参考:https://www.cnblogs.com/menchao/p/5266286.html

1.4、ViewControllerA push到ViewControllerB,再从B pop回A的生命周期.(15分)
#pragma mark --- life circle

// 非storyBoard(xib或非xib)都走这个方法
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSLog(@"%s", __FUNCTION__);
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
    
    }
    return self;
}

// 如果连接了串联图storyBoard 走这个方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
     NSLog(@"%s", __FUNCTION__);
    if (self = [super initWithCoder:aDecoder]) {
        
    }
    return self;
}

// xib 加载 完成
- (void)awakeFromNib {
    [super awakeFromNib];
     NSLog(@"%s", __FUNCTION__);
}

// 加载视图(默认从nib)
- (void)loadView {
    NSLog(@"%s", __FUNCTION__);
    self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.view.backgroundColor = [UIColor redColor];
}

//视图控制器中的视图加载完成,viewController自带的view加载完成
- (void)viewDidLoad {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidLoad];
}


//视图将要出现
- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillAppear:animated];
}

// view 即将布局其 Subviews
- (void)viewWillLayoutSubviews {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillLayoutSubviews];
}

// view 已经布局其 Subviews
- (void)viewDidLayoutSubviews {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidLayoutSubviews];
}

//视图已经出现
- (void)viewDidAppear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidAppear:animated];
}

//视图将要消失
- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillDisappear:animated];
}

//视图已经消失
- (void)viewDidDisappear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidDisappear:animated];
}

//出现内存警告  //模拟内存警告:点击模拟器->hardware-> Simulate Memory Warning
- (void)didReceiveMemoryWarning {
    NSLog(@"%s", __FUNCTION__);
    [super didReceiveMemoryWarning];
}

// 视图被销毁
- (void)dealloc {
    NSLog(@"%s", __FUNCTION__);
}
1.5、UIView和CALayer有什么关系? (10 分)

1.首先UIView可以响应事件,Layer不可以.
2.View和CALayer的Frame映射及View如何创建CALayer.
一个 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer的 frame,同样 View 的 center和 bounds 也是返回 Layer 的一些属性。
3.UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制。

总结
(1)UIView负责处理用户交互,负责绘制内容的则是它持有的那个CALayer,我们访问和设置UIView的这些负责显示的属性实际上访问和设置的都是这个CALayer对应的属性,UIView只是将这些操作封装起来了而已。

(2)CALayer作为一个跨平台框架(OS X和iOS)QuatzCore的类,负责MAC和iPhone(ipad等设备)上绘制所有的显示内容。而iOS系统为了处理用户交互事件(触屏操作)用UIView封装了一次CALayer,UIView本身负责处理交互事件,其持有一个Layer,用来负责绘制这个View的内容。而我们对UIView的和绘制相关的属性赋值和访问的时候(frame、backgroundColor等)UIView实际上是直接调用其Layer对应的属性(frame对应frame,center对应position等)的getter和setter。

1.6. 下面代码的输出是什么?(10分)
@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 这个对象。

1.7、在项目中,若出现以下的代码可能会有什么问题?(10分)
 for(int i=0;i< 1000;i++) {
        NSString *num = [NSString stringWithFormat:@"%d",i];//num是临时变量
    }

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

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

答案:https://www.jianshu.com/p/0f6119115548

2.1、frame和bounds 有什么不同?

答案:frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父亲的坐标系统)bounds指的是:该view在本身坐标系统中的位置和大小。(参照点是本身坐标系统)bounds指的是以自己为原点的坐标系。父视图bouns改变,子视图会移动。

2.2、0bjective-C 的类可以多重继承么?可以实现多个接口么? Category 是什么?重写一个类的方式用继承好还是分类好?为什么?

答案:Objective-c只支持单继承,如果要实现多继承的话,可以通过类别和协议的方式来实现,cocoa中所有的类都是NSObject 的子类,多继承在这里是用protocol委托代理来实现的。
Category是类别,一般用分类比较好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系

2.3、用@property声明的NSString / NSArray / NSDictionary经常使用copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

答案:用@property声明 NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

2.4、Category (类别)、Extension (扩展)和继承的区别?

答案:类扩展 Extension 可以为类添加属性和方法。
类别 Category 只能添加方法不能添加属性,添加属性调用的时候会 Crash,因为并不会为类 Category 的属性生成 Get 和 Set 方法。

2.5、什么时候用delete, 什么时候用Notification?

答案:delegate(委托模式):1对1的反向消息通知功能。
Notification(通知模式):只想要把消息发送出去,告知某些状态的变化。但是并不关心谁想要知道这个。

2.7、如何访问并修改一个类的私有属性?

答案:方式一:通过KVC访问并修改。
方式二:通过runtime访问并修改。获取对象属性列表。

2.8、一个objc对象的isa的指针指向什么?有什么作用?

答案:指向他的类对象,从而可以找到对象上的方法。

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

2.9、isKindOfClass、isMemberOfClass、 selector 作用分别是什么?

答案:isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。
isMemberOfClass:某个对象确切属于某个类型。
selector:通过方法名,获取在内存中的函数的入口地址。

2.10、lldb (gdb)常用的控制台调试命令?

答案:
1). p 输出基本类型。是打印命令,需要指定类型。是print的简写

p (int)[[[self view] subviews] count]

2). po 打印对象,会调用对象description方法。是print-object的简写

po [self view]

3). expr 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。

4). bt:打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈

5). br l:是breakpoint list的简写

2.11、_objc msgForward 函数是做什么的,直接调用它将会发生什么?

答案:_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发
直接调用_objc_msgForward是非常危险
的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。

2.12、 什么是TCP / UDP?

答案:
(1)TCP的全称为传输控制协议。这种协议可以提供面向连接的、可靠的、点到点的通信。,是面向连接的,建立连接需要经历三次握手,保证数据正确性和数据顺序。
TCP传输数据的形式:点对点传输数据 传输过程比较复杂 但是比较安全 不会出现丢包的现象(比如:QQ文件传输)

(2)UDP全称为用户数据报协议,它可以提供非连接的不可靠的点到多点的通信。是非连接的协议
UDP传输数据的形式:点对面传输数据 传输过程非常简单 但是比较不安全 经常出现丢包的现象 传输方只管传输不管接收方是否接受成功(比如:短信群发)
IP 由46位(0255之间的数字组成) 作为设备的唯一标识

(3)TCP 的三次握手
第一次握手:客户端发送 syn 包到服务器,并进入 syn_send状态,等待服务器进行确认;
第二次握手:服务器收到客户端的 syn 包,必须确认客户的SYN,同时自己也发送一个 SYN 包,即 SYN + ACK 包,此时服务器进入 SYN_RECV 状态;
第三次握手:客户收到服务器发送的 SYN+ACK 包之后,向服务器发送确认包, 此包发送完毕,客户端和服务器进入ESTABLISHED 状态,完成第三次握手。

2.13、用伪代码写一个线程安全的单例模式。
static id _instance;
+ (id)allocWithZone:(struct _NSZone *)zone {
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       _instance = [super allocWithZone:zone];
   });
   return _instance;
}

+ (instancetype)sharedData {
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       _instance = [[self alloc] init];
   });
   return _instance;
}

- (id)copyWithZone:(NSZone *)zone {
   return _instance;
}

2.14、HTTP协议中POST方法和GET方法有那些区别?

答案:GET用于向服务器请求数据,POST用于提交数据
GET请求,请求参数拼接形式暴露在地址栏,而POST请求参数则放在请求体里面,因此GET请求不适合用于验证密码等操作
GET请求的URL有长度限制(最多255byte),POST请求不会有长度限制

2.15、如何高性能的给 UIImageView 加个圆角?
(1)不好的解决方案:

使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。
self.view.layer.cornerRadius = 5.0f;
self.view.layer.masksToBounds = YES;

(2)正确的解决方案:使用绘图技术
- (UIImage *)circleImage {
    // NO代表透明
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
    // 获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 添加一个圆
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextAddEllipseInRect(ctx, rect);
    // 裁剪
    CGContextClip(ctx);
    // 将图片画上去
    [self drawInRect:rect];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    // 关闭上下文
    UIGraphicsEndImageContext();
    return image;
}
(3)还有一种方案:

使用了贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的。

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                       cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];

你可能感兴趣的:(iOS面试笔试题(2018年8月))