美文网首页iOS面试题
ios面试日记 --- 字节一面

ios面试日记 --- 字节一面

作者: ShawnAlex | 来源:发表于2021-08-05 23:56 被阅读0次

问题1.介绍下自己

问题2.介绍下自己做个的项目

这个问题要留意, 自己不熟悉的不要说, 包括我面试时候也会针对面试者项目描述中的问。如果你自己做的项目, 知识点都没弄清, 回答的模糊来就显得很尴尬。



从问题3开始技术面, 由于之前上过b站的当, 详细见ios面试日记 --- b站一面并且我查找了一些其他网上字节的一面题目都有网络, 所以我背了几天网络题, 争取开个好头。结果直接ios技术面, 我: ...你怎么不按套路出牌 ???

问题3. ARC是编译时还是运行时

这个问题我是真的没了解过, 当时想的是, 因为arc会自动处理retain, release和autorelease么。如果在编译时处理的话, 没有写的话肯定会报错, 处理肯定在运行时。

我当时回答是: 运行时, X 错误。 所以真的不要认为, 还是要尊重科学

正确答案: 编译时
  • ARC编译器将自动在代码合适的地方插入retain, releaseautorelease

  • ARCObjective-C 编译时特性, 而不是运行时或者垃圾回收机制ARC做的只是在编译时自动合适位置插入releaseautorelease, 效率上不比MRC弱, 因为系统自动针对引用计数的维护, 已经部分优化。想必MRC更加安全, 运行效率更高。

  • ARC基本原则: 只要某个对象被任一指针指向, 那么它将不会被销毁。如果对象没有被任何Strong指针指向, 那么就将销毁(ARC默认Strong)


问题4. 扩大按钮button的点击范围怎么实现

这个问题我也是没了解过, 就回答了一个笨方法, 按钮前面覆盖一个透明图层, 用UITapGestureRecognizer点击调用对应按钮方法。

正确答案

这道题其实考察重写以及对bounds的了解

方法1:

  • 自定义一个button类, 重写里面的pointInside:方法
重写 调用 运行结果



方法2:

  • 定义分类category进行处理, 重写里面的pointInside:方法
    建立分类
// 分类.h文件
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIButton (TestButtonC)

// 设置内边距范围
-(void)setEnLargeEdge:(CGFloat)size;

// 设置具体范围, 到上、右、下、左的距离
-(void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left;

@end

NS_ASSUME_NONNULL_END



// 分类.m文件
#import "UIButton+TestButtonC.h"

static char topNameKey;
static char rightNameKey;
static char bottomNameKey;
static char leftNameKey;

@implementation UIButton (TestButtonC)

// 利用 **runtime** 设置

// 设置内边距范围
-(void)setEnLargeEdge:(CGFloat)size
{
    objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);

}

// 设置具体范围, 到上、右、下、左的距离
-(void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left
{
    objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC);

}


-(CGRect)enlargedRect
{
    NSNumber *topEdge=objc_getAssociatedObject(self, &topNameKey);
    NSNumber *rightEdge=objc_getAssociatedObject(self, &rightNameKey);
    NSNumber *bottomEdge=objc_getAssociatedObject(self, &bottomNameKey);
    NSNumber *leftEdge=objc_getAssociatedObject(self, &leftNameKey);
    
    if(topEdge && rightEdge && bottomEdge && leftEdge){
        return CGRectMake(self.bounds.origin.x-leftEdge.floatValue,
                          self.bounds.origin.y-topEdge.floatValue,
                          self.bounds.size.width+leftEdge.floatValue+rightEdge.floatValue,
                          self.bounds.size.height+topEdge.floatValue+bottomEdge.floatValue);
    
    }else{
        return self.bounds;
    }

}

// 从写点击范围事件
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGRect rect=[self enlargedRect];
    if(CGRectEqualToRect(rect, self.bounds))
    {
        return [super pointInside:point withEvent:event];
    }
    return CGRectContainsPoint(rect, point)?YES:NO;

}

@end

调用

问题5.view的frame和bounds区别

framebound都是view的属性。bounds的原点是(0,0)点(view坐标系统,默认(0, 0)点,除非人为设置 setbounds), 而frame的原点却是任意的(相对于父视图中的坐标位置)

位置示意图
  • frame: 子view父view坐标系统中的位置和大小。(参照点是,父亲的坐标系统)
-(CGRect)frame{
    return CGRectMake(self.frame.origin.x,self.frame.origin.y,self.frame.size.width,self.frame.size.height);
}
  • bounds子view本地坐标系统中的位置和大小。(参照点是,本地坐标系统,就相当于ViewB自己的坐标系统,以0,0点为起点)
-(CGRect)bounds{
    return CGRectMake(0,0,self.frame.size.width,self.frame.size.height);
}
  • center子view中心点在父view坐标系统中的位置和大小。(参照电是,父亲的坐标系统)

留意下, 每个view都有一个本地坐标系统, 这个坐标系统作用非常重要。bounds这个属性是参照这个本地坐标系统来的, 而触摸回调函数中坐标值也是参照这个本地坐标系统的。同时而坐标bounds也会影响到子view的位置和大小。所以也能理解为什么在问题4. 扩大按钮button的点击范围怎么实现之后紧接着问这道问题了


问题6.tableview列表高度动态加载

我当时回答把设置一个数组heightArr用来存放每个cell高度, 使用UITableViewDelegate中设置高度方法, 针对每一个cell单独设置

// 参考代码
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return  heightArr[indexPath.row]; 
}

不过这种方法相对比较笨, 相对比较好方法, 设置UITableViewAutomaticDimension

// 设置预估行高
tableView.estimatedRowHeight = 200;
tableView.rowHeight = UITableViewAutomaticDimension; // 自动设置
UILabel *textLab = [[UILabel alloc]init];
textLab.numberOfLines = 0;
[self addSubview:textLab];
self.textLab = textLab;
[textLab mas_makeConstraints:^(MASConstraintMaker *make) {
  make.top.mas_equalTo(self).offset(5);
  make.left.mas_equalTo(self).offset(5);
  make.bottom.mas_equalTo(self).offset(-5);
  make.right.mas_equalTo(self.mas_right).offset(-5);
}];

当然也有些第三方, 例如UITableView-FDTemplateLayoutCell我们也可以参考下

#import <UITableView+FDTemplateLayoutCell.h>

static NSString *cellID = @"cell";

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [_tableView fd_heightForCellWithIdentifier:cellID cacheByKey:indexPath configuration:^(TableViewCell *cell) {
        cell.textLab.text = self->arr[indexPath.row];
    }];
}

-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        [self setUpView];
    }
    return self;
}

-(void)setUpView
{
    UILabel *textLab = [[UILabel alloc] init];
    textLab.numberOfLines = 0;
    [self.contentView addSubview: textLab];
    self.textLab = textLab;
    [textLab mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(self.contentView).offset(10);
        make.left.mas_equalTo(self.contentView).offset(5);
        make.bottom.mas_equalTo(self.contentView).offset(-5);
        make.right.mas_equalTo(self.contentView.mas_right).offset(-5);
    }];
}

UITableView-FDTemplateLayoutCell: https://github.com/forkingdog/UITableView-FDTemplateLayoutCell


问题7.NSTimer了解

NSTimer 简介

NSTimer: 是一种计时器,在经过一定的时间间隔后触发,向目标对象发送指定的消息。继承于NSObject

NSTimer 概述

timerrunloop一起使用(NSTimer在NSRunLoop中跑), timerTarget进行了强引用strong

官方解释: https://developer.apple.com/documentation/foundation/nstimer/

NSTimer 流程

NSTimer 整个使用流程

  • 创建Timer
  • 加入runloop
  • 执行响应事件

用完timer之后记得合适位置进行销毁invalidate。如果repeatsNO, 则会在本次调用之后自身invalidate

其实这道题面试官更想听到你说一些NSTimer引起的相关问题, 例如与VC的循环引用

最常见的, 在ViewController中使用timer属性,由于VC强引用timertimertarget又是VC这样就会造成循环引用, 例如

循环引用
#import "SecViewController.h"

@interface SecViewController ()

@property (nonatomic, assign) NSInteger testIndex;
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation SecViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.testIndex = 0;
    
    CGFloat w = [[UIScreen mainScreen] bounds].size.width;
    CGFloat h = [[UIScreen mainScreen] bounds].size.height;
    
    UIButton *btn = [UIButton buttonWithType: UIButtonTypeCustom];
    btn.frame = CGRectMake(w/2 - 100, h/2 - 50, 200, 100);
    [btn setBackgroundColor:[UIColor brownColor]];
    [btn setTitleColor:[UIColor whiteColor] forState: UIControlStateNormal];
    [btn setTitle:@"点击返回" forState: UIControlStateNormal];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(back) forControlEvents: UIControlEventTouchUpInside];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

    
}

- (void)timerAction {
    NSLog(@"当前index: %ld", self.testIndex++);
}

- (void)back {
    [self.navigationController popViewControllerAnimated:YES];
}

- (void)dealloc {
    
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"走了dealloc方法");
}

@end

这种情况, 我们可发现

  • 进入SecViewController计时器timer正常运作, 但是pop返回, 会发现timer还在计数, 并且dealloc没有执行

  • 第二次进入SecViewController会形成2个计时器

原因
循环引用原因

VC中使用timer, 由于VC强引用timer, timertarget又是VC, 这样就造成了循环引用。当pop返回时, 由于VCdealloc方法中销毁timer, VCtimer释放才走dealloc, 而timer是释放需要执行dealloc, 所以就引起了循环引用。

解决方法
  • 方法1: 提前销毁, 我们可以在btn点击方法中写入invalidate方法
[self.timer invalidate];
 self.timer = nil;
  • 方法2: 使用block, 其实也是NSTimer的自带block的方法, 使用weak处理。 强引用timer弱引用target
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf timerAction];
    }];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

如果是我的话会追问直接使用weak能否解决循环引用, 例如

    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(timerAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

留意下这种方式并不能解决循环引用, 原因是:

虽然target参数传递的是weak指针,但是timer还是强持有weak指针指向的对象,同样导致无法释放。

  • timer强持有target
  • runloop强持有timer
  • repeat的timer只有在调用了invalidate之后才会被runloop释放
  • 为了timer和target的生命周期一致,我们在target的dealloc中invalidate timer
    target被强持有了,不会走dealloc,就内存泄漏了
  • 方法3: 通过runtime进行处理
  • 方法4: 创建一个集成自NSProxy的类处理

问题7.1 追问 主线程有个滑动控件, 当滑动时, NSTimer停止计时, 怎么处理?

其实这也是NSTimer使用不当发生的问题, 因为我之前遇到过这种情况, 大致例子是这样

页面上一个UIScrollView, UILabel, NSTimer改变UILabel的值

例子

其实问题发生将NSTimer 直接放在viewDidLoad里面, NSTimer被添加在mainRunLoop中, 模式是NSDefaultRunLoopMode。因为mainRunLoop负责所有主线程事件, 那么当进行UI界面的操作, 例如UIScrollView滑动, 复杂的运算使当前RunLoop持续的时间超过了定时器的间隔时间,那么下一次定时就被延后,这样就会造成timer的阻塞。

解决方法

定义一个runloop, 在里面去执行就可以, 不在mainRunLoop中去执行

    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

问题8 对AppGroup的了解

这个问题选看, 因为我们项目用到了AppGroup, 所以面试官让我稍微讲一下。

AppGroup主要用来处理APP之间数据共享

AppGroup AppGroup

配合NSUserDefaults使用, 可在Home AppExtension App之间实现数据分享/数据读写

// 初始化一个供App Groups使用的NSUserDefaults对象
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.XXX"];

// 写入数据
[userDefaults setValue:@"value" forKey:@"关键字key"];

// 读取数据
NSLog(@"%@", [userDefaults valueForKey:@"关键字key"]);

我之后百度了一下, AppGroup好像可以在两个APP之间进制数据分享, 不依赖第三方库就可以实现。


问题9 Swift安全性在哪里

这道题, 考察是否对官方文档有所了解

Swift

官方解释: https://swift.org/about/#swiftorg-and-open-source

安全性: Swift 大部分的编程方法是安全的。针对于一些不确定的情况, Swift的开人员可以在编译过程中就可以发现其问题所在。虽然Swift这种安全检测可能有点严格, 但是从长远角度来看还是利大于弊。

可看出Swift 是一门注重安全性的语言, 严格的代码检查也强调了这一点。


问题10 Swift与OC对比

考察对两种语言的理解, 开放性问题

Swift是苹果新推出的一门语言, 旨在代替OC。

Swift的优势
  • Swift容易阅读,语法和文件结构简易化。
  • Swift代码更少,简洁的语法,可以省去大量冗余代码
  • Swift更加安全,它是类型安全的语言。
  • Swift速度更快,运算性能更高。
  • Swift有严格的代码检查
OC的优势
  • 相比Swift稳定
  • 使用人数较多, 目前大部分还是以OC为主
  • Runtime支持更加完善

问题11 算法题

算法题比较幸运, 我之前做到过这个算法

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
1 <= nums.length <= 100
0 <= nums[i] <= 109

输入:nums = [10,2]
输出:"210"

输入:nums = [3,30,34,5,9]
输出:"9534330"

重点是处理怎么" 排序 "

答案可我之前写的简书: IOS 算法(中级篇) ----- 最大数


问题12 你还有什么要问我的吗?

这个问题我在ios面试日记 --- b站一面写过, 可参考其答案


总结

  • 首先字节老师面试官人很好, 针对面试者经验履历准备相应的面试问题, 同时也扩展出一些面试问题, 展示了一个大厂面试官应有风度。

  • 字节整体面试给我感觉, 实战 > 理论, 会针对开发项目中一些问题进行提问。具体到某个UI控件怎么使用, UI控件使用中会产生什么问题等等。面试字节最好还是在充分了解自己项目, 遇到问题不要百度解决方法, 复制粘贴解决完就OK了, 一定要了解其原理或者问题所在。

  • 算法是大厂必备, 都是面板手写, 最好日常练不要临阵磨枪

相关文章

网友评论

    本文标题:ios面试日记 --- 字节一面

    本文链接:https://www.haomeiwen.com/subject/tbbzmltx.html