问题1.介绍下自己
问题2.介绍下自己做个的项目
这个问题要留意, 自己不熟悉的不要说, 包括我面试时候也会针对面试者项目描述中的问。如果你自己做的项目, 知识点都没弄清, 回答的模糊来就显得很尴尬。
从问题3开始技术面, 由于之前上过b站的当, 详细见ios面试日记 --- b站一面并且我查找了一些其他网上字节的一面题目都有网络, 所以我背了几天网络题, 争取开个好头。结果直接ios技术面, 我: ...你怎么不按套路出牌 ???
问题3. ARC是编译时还是运行时
这个问题我是真的没了解过, 当时想的是, 因为arc会自动处理retain, release和autorelease么。如果在编译时处理的话, 没有写的话肯定会报错, 处理肯定在运行时。
我当时回答是: 运行时, X 错误
。 所以真的不要认为, 还是要尊重科学
正确答案: 编译时
-
ARC
编译器将自动在代码合适的地方插入retain
,release
和autorelease
。 -
ARC
是Objective-C 编译时
特性, 而不是运行时
或者垃圾回收机制
。ARC
做的只是在编译时自动合适位置插入release
或autorelease
, 效率上不比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区别
frame
和bound
都是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
timer
与runloop
一起使用(NSTimer在NSRunLoop中跑), timer
对Target
进行了强引用strong
。
官方解释: https://developer.apple.com/documentation/foundation/nstimer/
NSTimer 流程NSTimer 整个使用流程
- 创建Timer
- 加入runloop
- 执行响应事件
用完timer
之后记得合适位置进行销毁invalidate
。如果repeats
为NO
, 则会在本次调用之后自身invalidate
。
其实这道题面试官更想听到你说一些NSTimer
引起的相关问题, 例如与VC的循环引用
最常见的, 在ViewController
中使用timer
属性,由于VC
强引用timer
,timer
的target
又是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
, timer
的target
又是VC
, 这样就造成了循环引用。当pop返回时, 由于VC
中dealloc
方法中销毁timer
, VC
等timer
释放才走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之间数据共享
配合NSUserDefaults
使用, 可在Home App
和Extension 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了, 一定要了解其原理或者问题所在。 -
算法是大厂必备, 都是面板手写, 最好日常练不要临阵磨枪
网友评论