一、iOS初级知识点
1.MVC、MVP、MVVM的区别?
MVC:
View上面显示什么东西,取决于Model。
只要Model数据改了,View的显示状态也会跟着更改。
Controller负责初始化Model,并将Model传递给View去解析展示。
MVVM:
M:模型,负责数据的存储。
V:View和ViewController
VM:ViewModel,专门负责数据请求、业务逻辑等业务。
MVP:
M、V与上相同。
Presenter:作为model和view的中间人,从model层获取数据之后传给View,使得View和model有更强的复用性。
2.自动布局有哪些?
(1)Autolayout
1)约束和参照
2)代码实现
要先禁止autoresizing功能,设置view的下面属性为NO.
view.translatesAutoresizingMaskIntoConstraints = NO;
// 添加高度约束
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:buleView relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:40];
自动布局核心计算公式
obj1.property1 = (obj2.property2*multiplier)+constant value
3)VFL语言
H:[cancelButton(72)] - 12 - [acceptButton(50)]
H:[wideView(>=60@700)]
V:[redBox][yellowBox(==redBox)]
(2)Masonry:
约束的类型:
1.尺寸:width\height\size
2.边界:left\leading\right\trailing\top\bottom
3.中心点:center\centerX\centerY
4.边界:edges
[blueView mas_makeConstraints:^(MASConstarintMake *make){
// 宽度约束
make.width.equalTo(@100);
// 宽度约束
make.height.equalTo(@100);
// 右边
make.right.equalTo(self.view.mas_right).offset(-20);
// 顶部
make.top.equalTo(self.view.mas_top).offset(20);
}
// 这个方法会将以前的所有约束删除,添加新的约束
[blueView mas_remakeConstraints^(MASConstraintMaker *make){
}
// 这个方法将会覆盖以前的某些特定的约束
[blueView mas_updateConstraints^(MASConstraintMaker *make){
}
(3)SDAutolayout
_view.sd_layout.leftSpaceToView(self.view,10).topSpaceToView(self.view,80),heightIs(130).widthRatioToView(self.view,0.4);
_label.sd_layout.autoHeightRadio(0);
tableView高度自适应设置3步:
1)cell布局设置之后调用以下方法就可以实现高度自适应(注意:如果用高度自适应则不要再以cell的底边去参考布局其子view)
[cell setupAutoHeightWithBottomView:_view4 bottomMargin:10];
2)获取自动计算出cell的高度
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
id model = self.modelsArray[indexPath.row];
// 获取cell高度
return [self.tableView cellHeightForIndexPath:indexPath model:model keyPath:@"model" cellClass:[DemoVC9Cell class] contentViewWidth:cellContentViewWidth];
}
3.切换分支
git checkout -b branch1 origin/branch1
4.递归函数
(1)必须有一个明确的结束标志
(2)自己调用自己
// 设置一个函数用来计算B的n次方
int myPow2(int base,int n)
{
int result = 1;
if(n<0){
//结束标志
return result;
}else{
return myPow2(base,n-1)*base;
}
}
5.代码冲突解决方法
(1)代码文件冲突
当代码文件冲突时,是可以打开xcode的,然后逐个解决就行。
<<<<<<<<<<<<HEAD
================
>>>>>>>>>>>>masterDv
<<<<<<<<<<<<HEAD与================之间是本地的代码。
================与>>>>>>>>>>>>masterDv之间是对方的代码
根据情况来删除
(2)配置文件冲突
当配置文件冲突时,打不开xcode,这时需要进行项目文件夹xxx.xcodeproj打开project.pbxproj,然后逐个解决即可。
6.九宫格的思路
核心点就是算x,y。控制在第index/cols行,第index%cols列。(cols列数)。
// 每一列之间的间距
CGFloat colMargin = (self.shopView.frame.size.width - cols*shopW)/(cols - 1);
// 每一行的间距
CGFloat rowMargin = 10;
X值:NSUInterger col = index%cols;
CGFloat shopX = col*(shopW +colMargin);
Y值:NSUInteger row = index/cols;
CGFloat shopY = row*(shopH +rowHeight);
7.通知、代理、block
(1)发通知
// 发通知
NSDictionary *dic = [NSDictionary dictionaryWithObject:@"userInfo消息" forKey:@"param"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti3" object:nil userInfo:dic];
// 监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:(noti3:) name:@"noti3" object:nil];
// 调用方法
- (void)noti3:(NSNotification *)noti
{
//使用userInfo处理消息
NSDictionary *dic = [noti userInfo];
NSString *info = [dic objectForKey:@"param"];
}
最后,记得在监听通知消息的页面,在dealloc方法里面移除观察者。
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
(2)代理设计模式的开发步骤
1)拟一份协议(协议名字的格式:控制名+Delegate),在协议里面生命一些代理方法。(一般代理方法都是@optional)
2)声明一个代理属性
@property(nonatomic,weak)id<代理协议>delegate;
3)在内部发生某种行为时,调用代理对应的代理方法,通知代理发生什么事。
4)设置代理:xxx.delegate = yyy;
5)yyy对象准守协议,实现代理方法。
(3)block逆向传值
A文件(用来保存block代码)
addVc.block = ^(Contact *contact){
//1.联系人添加到数组
[self.contacts addObject:contact];
//2.刷新表格
[self.tableView reloadData];
}
B文件:(逆传)
.h文件
typedef void(^AddViewControllerBlock)(Contact *contact);
@property(nonatomic,strong)AddViewControllerBlock block;
.m文件
//0.把文本框的值包装包装成模型人模型
Contact *c = [Contact contactWithName:_nameField.text phone:_phoneField.text];
// 1.把联系人添加到联系人控制器的数组,让联系人刷新表格
if(_block){
_block(c);
}
// 2.回到联系人控制器
[self.navigationController popViewControllerAnimated:YES];
8.支付的过程
(1)首先从自己的服务器获取商品列表
(2)通过商品数据请求生成订单接口。
(3)根据生成订单接口返回的数据,调用ping++的api
(4) 在支付宝或微信进行支付
(5)API调用成功之后再通过服务器返回的数据判断交易状态。
9.瀑布流
XMGWaterflowLayout.h
#import <UIKit/UIKit.h>
@interface XMGWaterflowLayout : UICollectionViewLayout
@end
XMGWaterflowLayout.m
#import "XMGWaterflowLayout.h"
/** 默认的列数 */
static const NSInteger XMGDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat XMGDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat XMGDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets XMGDefaultEdgeInsets = {10, 10, 10, 10};
@interface XMGWaterflowLayout()
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
@end
@implementation XMGWaterflowLayout
- (NSMutableArray *)columnHeights
{
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
/**
* 初始化
*/
- (void)prepareLayout
{
[super prepareLayout];
// 清除以前计算的所有高度
[self.columnHeights removeAllObjects];
for (NSInteger i = 0; i < XMGDefaultColumnCount; i++) {
[self.columnHeights addObject:@(XMGDefaultEdgeInsets.top)];
}
// 清除之前所有的布局属性
[self.attrsArray removeAllObjects];
// 开始创建每一个cell对应的布局属性
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < count; i++) {
// 创建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 获取indexPath位置cell对应的布局属性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
/**
* 决定cell的排布
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
/**
* 返回indexPath位置cell对应的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
// 创建布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// collectionView的宽度
CGFloat collectionViewW = self.collectionView.frame.size.width;
// 设置布局属性的frame
CGFloat w = (collectionViewW - XMGDefaultEdgeInsets.left - XMGDefaultEdgeInsets.right - (XMGDefaultColumnCount - 1) * XMGDefaultColumnMargin) / XMGDefaultColumnCount;
CGFloat h = 50 + arc4random_uniform(100);
// 找出高度最短的那一列
// __block NSInteger destColumn = 0;
// __block CGFloat minColumnHeight = MAXFLOAT;
// [self.columnHeights enumerateObjectsUsingBlock:^(NSNumber *columnHeightNumber, NSUInteger idx, BOOL *stop) {
// CGFloat columnHeight = columnHeightNumber.doubleValue;
// if (minColumnHeight > columnHeight) {
// minColumnHeight = columnHeight;
// destColumn = idx;
// }
// }];
// 找出高度最短的那一列
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i < XMGDefaultColumnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (minColumnHeight > columnHeight) {
minColumnHeight = columnHeight;
destColumn = i;
}
}
CGFloat x = XMGDefaultEdgeInsets.left + destColumn * (w + XMGDefaultColumnMargin);
CGFloat y = minColumnHeight;
if (y != XMGDefaultEdgeInsets.top) {
y += XMGDefaultRowMargin;
}
attrs.frame = CGRectMake(x, y, w, h);
// 更新最短那列的高度
self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
return attrs;
}
- (CGSize)collectionViewContentSize
{
CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i < XMGDefaultColumnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (maxColumnHeight < columnHeight) {
maxColumnHeight = columnHeight;
}
}
return CGSizeMake(0, maxColumnHeight + XMGDefaultEdgeInsets.bottom);
}
@end
ViewController.m
#import "ViewController.h"
#import "XMGWaterflowLayout.h"
@interface ViewController () <UICollectionViewDataSource>
@end
@implementation ViewController
static NSString * const XMGShopId = @"shop";
- (void)viewDidLoad {
[super viewDidLoad];
// 创建布局
XMGWaterflowLayout *layout = [[XMGWaterflowLayout alloc] init];
// 创建CollectionView
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
collectionView.dataSource = self;
[self.view addSubview:collectionView];
// 注册
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:XMGShopId];
}
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 50;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:XMGShopId forIndexPath:indexPath];
cell.backgroundColor = [UIColor orangeColor];
NSInteger tag = 10;
UILabel *label = (UILabel *)[cell.contentView viewWithTag:tag];
if (label == nil) {
label = [[UILabel alloc] init];
label.tag = tag;
[cell.contentView addSubview:label];
}
label.text = [NSString stringWithFormat:@"%zd", indexPath.item];
[label sizeToFit];
return cell;
}
@end
10.oc与js的交互
(1)在oc中执行js代码:
webViewDidFinishLoad方法中
[webView stringByEvaluatingJavaScriptFromString:str1];
(2)在网页中执行OC的方法
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSString *str = request.URL.absoluteString;
NSRange range = [str rangeOfString:@"xmg://"];
if (range.location != NSNotFound) {
NSString *method = [str substringFromIndex:range.location + range.length];
NSLog(@"%@", method);
SEL sel = NSSelectorFromString(method);
[self performSelector:sel];
}
return YES;
}
11.本地化存储
(1)属性列表
(2)偏好设置
(3)归档
遵循NSCoding协议
- (void)encodeWithCoder:(NSCoder *)encode{
[encode encodeObject:self.name forKey:@"name"];
}
- (id)initWithCoder:(NSCoder*)decode{
self.name = [decode decoderObjectForkey];
}
(4)FMDB
1) 获取数据库对象
2)打开数据库,如果不存在,则创建并且打开。
3)创建表
4)插入数据
5)查询数据
12.性能优化
(1)首页启动速度
1)启动过程中做的事情越少越好
2)不在UI线程做耗时的操作
(2)页面浏览速度
1)数据的分页
2) 内容缓存
3)延时加载tab(比如app有5个tab,可以先加载第一个显示的tab,其他的在显示时候加载)
4)算法的优化(联系人姓名用汉语拼音的首字母排序)
二、iOS中高级知识点
1.你在项目中用过 runtime 吗?举个例子。
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
2.你在项目中用过 GCD 吗?举个例子。
异步并发执行任务1、任务2
等任务1、任务2都执行完毕后,再回到主线程执行任务3
// 创建组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列组
dispatch_queue_t queue = dispatch_queue_create("WYQueue", DISPATCH_QUEUE_CONCURRENT);
// 创建 异步任务组
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 2; i++) {
NSLog(@"任务1 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 3; i++) {
NSLog(@"任务2 - %@", [NSThread currentThread]);
}
});
// 等前面的任务执行完毕后,会自动自行这个任务
// notify: 唤醒
dispatch_group_notify(group, queue, ^{
// 回到主线程做
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 0 ; i < 3; i++) {
NSLog(@"任务3 - %@", [NSThread currentThread]);
}
});
});
3.Category 的实现原理,以及 Category 为什么只能加方法不能加属性
分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。
4.block 的原理,block 的属性修饰词为什么用 copy,使用 block 时有哪些要注意的?
block是封装了函数调用以及函数调用环境的OC对象。
block一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题
5.iOS 的热更新方案有哪些?介绍一下实现原理。
JSPatch 能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言,OC 上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,通过Apple在iOS7中发布的javeScriptCore.framework来解析JavaScript脚本,与Objective-C的代码进行桥接,利用运行时的特性,让app具备hit code push能力
6.class A 继承 class B,class B 继承 NSObject。画出完整的类图
7.细致地讲一下事件传递流程
(1)点击一个UIView或产生一个触摸事件A,这个触摸事件A会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。
(2)UIApplication会从事件对列中取出最前面的事件(此处假设为触摸事件A),把事件A传递给应用程序的主窗口(keyWindow)。
(3)窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。
(4)找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理touchesBegan…touchesMoved…touchedEnded…
(5)这些touches方法的默认做法是将事件顺着响应者链条向上传递(也就是touch方法默认不处理事件,只传递事件),将事件交给上一个响应者进行处理
应用如何找到最合适的控件来处理事件?
(1)首先判断主窗口(keyWindow)自己是否能接受触摸事件
(2)触摸点是否在自己身上
(3)从后往前遍历子控件,重复前面的两个步骤(首先查找数组中最后一个元素)
(4)如果没有符合条件的子控件,那么就认为自己最合适处理
#import "WSWindow.h"
@implementation WSWindow
// 什么时候调用:只要事件一传递给一个控件,那么这个控件就会调用自己的这个方法
// 作用:寻找并返回最合适的view
// UIApplication -> [UIWindow hitTest:withEvent:]寻找最合适的view告诉系统
// point:当前手指触摸的点
// point:是方法调用者坐标系上的点
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 1.判断下窗口能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2.判断下点在不在窗口上
// 不在窗口上
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历子控件数组
int count = (int)self.subviews.count;
for (int i = count - 1; i >= 0; i--) {
// 获取子控件
UIView *childView = self.subviews[i];
// 坐标系的转换,把窗口上的点转换为子控件上的点
// 把自己控件上的点转换成子控件上的点
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) {
// 如果能找到最合适的view
return fitView;
}
}
// 4.没有找到更合适的view,也就是没有比自己更合适的view
return self;
}
// 作用:判断下传入过来的点在不在方法调用者的坐标系上
// point:是方法调用者坐标系上的点
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
//{
// return NO;
//}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
@end
8.main()之前的过程有哪些?
整个事件由 dyld 主导,完成运行环境的初始化后,配合 ImageLoader 将二进制文件按格式加载到内存,
动态链接依赖库,并由 runtime 负责加载成 objc 定义的结构,所有初始化工作结束后,dyld 调用真正的 main 函数。
9.Category 中有 load 方法吗?load 方法是什么时候调用的?load 方法能继承吗?
Category 中是有 load 方法的。
在运行时时期,将 Category 中的实例方法列表、协议列表、属性列表添加到主类中后,会递归调用所有类的 load 方法,递归调用确保以下几点:
父类的 load 方法先调用
主类中的 load 方法先调用,分类中的 load 方法后调用
分类之间的 load 方法调用顺序,看文件编译的顺序
在运行时时期,循环调用所有类的 +load 方法。直接使用函数内存地址的方式 (*load_method)(cls, SEL_load); 而不是使用发送消息 objc_msgSend 的方式。
这个就会导致:类、父类和分类中的 +load 方法的实现是被区别对待的。也就是说如果子类没有实现 +load 方法,那么当它被加载时 runtime 是不会去调用父类的 +load 方法的。同理,当一个类和它的分类都实现了 +load 方法时,两个方法都会被调用。
10.讲一下你对 iOS 内存管理的理解
首先解释ARC: automatic reference counting自动引用计数。
ARC几个要点:
在对象被创建时 retaincount +1,在对象被release时 retaincount -1.当retain count 为0 时,销毁对象。
程序中加入autoreleasepool的对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。
那么ARC是为了解决什么问题诞生的呢?这个得追溯到MRC手动内存管理时代说起。
MRC下内存管理的缺点:
(1)当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放)
(2)释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)
(3)模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
(4)多线程操作时,不确定哪个线程最后使用完毕
11.你在项目中是怎么优化内存的?
(1)重用问题:如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置正确的reuseIdentifier,充分重用。
(2)如果你有透明的Views你应该设置它们的opaque属性为YES。如果设为YES,渲染系统就认为这个view是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。默认值是YES。
(3)不要使用太复杂的XIB/Storyboard:载入时就会将XIB/storyboard需要的所有资源,包括图片全部载入内存,即使未来很久才会使用。那些相比纯代码写的延迟加载,性能就差了很多。
(4)选择正确的数据结构:学会选择对业务场景最合适的数组结构是写出高效代码的基础。比如,数组: 有序的一组值。使用索引来查询很快,使用值查询很慢,插入/删除很慢。字典: 存储键值对,用键来查找比较快。集合: 无序的一组值,用值来查找很快,插入/删除很快。
(5)gzip/zip压缩:当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。
(6)延迟加载:对于不立刻使用的数据,使用延迟加载,对于不需要马上显示的视图,使用延迟加载方式。比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。
(7)数据缓存:对于cell的行高要缓存起来,使得reload数据时,效率也极高。而对于那些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以通过plist文件存储。
(8)使用Autorelease Pool:在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存。
(9)正确选择图片加载方式
(10)处理内存警告:一般在基类统一处理内存警告,将相关不用资源立即释放掉
(11)重用大开销对象:一些objects的初始化很慢,比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它们。通常是作为属性存储起来,防止反复创建。
(12)避免反复处理数据:许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要。
12.讲讲 RunLoop,项目中有用到吗?
(1)控制线程生命周期(线程保活)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSTimer * timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
static int count = 0;
[NSThread sleepForTimeInterval:1];
//休息一秒钟,模拟耗时操作
NSLog(@"%s - %d",__func__,count++);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 子线程需要手动开启Runloop
[[NSRunLoop currentRunLoop] run];
});
(2)解决NSTimer在滑动时停止工作的问题
NSTimer * timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
static int count = 0;
NSLog(@"%s - %d",__func__,count++);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
3.监控应用卡顿
平时所说的“卡顿”主要是因为在主线程执行了比较耗时的操作
可以添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的
4.性能优化
案例:tableView的Cell中有多个ImageView,同时加载大图,导致UI卡顿。
解决思路:使用Runloop每次循环址添加一张图片。
工具:这里我们需要使用到CFRunloop
实现过程:
1、把加载图片等代码保存起来,先不执行 (保存一段代码,block)
2、监听Runloop循环(CFRunloopObserver)
3、每次都从任务数组中取出一个加载图片等代码执行(执行block代码)
13.列表卡顿的原因可能有哪些?你平时是怎么优化的?
卡顿产生的主要原因:按照60FPS的刷帧率,每隔16ms就会有一次VSync信号,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
卡顿解决的主要思路:尽可能减少CPU、GPU资源消耗
CPU优化:
(1)尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
(2)不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
(3)尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
(4)Autolayout会比直接设置frame消耗更多的CPU资源
(5)图片的size最好刚好跟UIImageView的size保持一致
(6)控制一下线程的最大并发数
(7)尽量把耗时的操作放到子线程
GPU优化:
(1)尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张图片进行显示
(2)GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
(3)尽量减少视图数量和层次
(4)减少透明的视图(alpha<1),不透明的就设置opaque为YES
(5)尽量避免出现离屏渲染
14.项目有没有做过组件化?或者你是否调研过?
组件的存在形式是cocoaPods私有库,组件间的通信一般有两种:一种是蘑菇街那种,一种是运行时,我用的是运行时。给每个组件做一个中间件分类,在分类里把参数全部转化成字典,在对应组件中在把字典转成参数。
1.建立私有索引库
pod repo add xxxSpecs url地址
2. 创建Pod私有库所需要的项目工程文件,并上传到私有仓库
2.0. 添加代码
2.1. 提交到本地仓库
2.2. 添加远程仓库关联
2.3. 提交到远程代码仓库
2.4 git tag -m "初始版本" "0.1.0"
2.5 git push --tags #推送tag到远端仓库
3. 创建Pod所对应的podspec文件, 并进行验证/测试
pod spec create XXX
pod lib lint
创建Podfile文件
pod 'XXX', :podspec => 'path/XXX.podspec' #指定podspec文件
执行pod install
4.把私有库添加到索引库里
pod repo push xxxSpecs XXX.podspec
5. 使用pod库
pod search XXXLib
6. 测试
source 'git@git.coding.net:wangshunzi/XMGFMSpecs.git'
source 'https://github.com/CocoaPods/Specs.git' #官方仓库的地址
pod 'XXXLib/XXXSub'
中间件的设计:
#import "MediatorManager.h"
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
#import <objc/runtime.h>
@interface MediatorManager ()
@end
@implementation MediatorManager
// 本地组件调用入口
+ (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(id)params isRequiredReturnValue: (BOOL)isRequiredReturnValue {
// XMGMainModelTarget://log
// 1. 获取目标
Class targetCls = NSClassFromString(targetName);
if (targetCls == nil) {
NSLog(@"目标不存在");
return nil;
}
// 2. 获取行为
SEL sel = NSSelectorFromString(actionName);
if (sel == nil) {
NSLog(@"行为不存在");
return nil;
}
//NSObject *obj = [[targetCls alloc] init];
if (![targetCls respondsToSelector:sel]) {
NSLog(@"目标不存在该方法");
return nil;
}
if (isRequiredReturnValue) {
SuppressPerformSelectorLeakWarning(return [targetCls performSelector:sel withObject:params]);
}else {
SuppressPerformSelectorLeakWarning([targetCls performSelector:sel withObject:params]);
}
return nil;
}
@end
15.讲一下 OC 的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
16.ARC 都帮我们做了什么?
MRC,是通过alloc retaincount+1,release retaincount-1,retainCount为0时释放。
ARC把逻辑变成只要有强指针引用着就不会释放。
17.实现 isEqual 和 hash 方法时要注意什么?
对象 isEqual 和 hash 方法需要同时重写
很多时候为了图方便,只会重写 isEqual 方法,忽略 hash 方法,这里我们看看下面这种情况:
重写 isEqual 方法,hash 方法没重写
这个时候会出现 isEqual 判断两个对象相同,但是 hash 值不同,但是这两个对象在 Set 集合中可以同时存在,这个在业务逻辑上是不合理的。
对关键属性的hash值进行位或运算作为hash值
- (NSUInteger)hash {
return [self.name hash] ^ [self.birthday hash];
}
重写对象 isEqual 方法代码
- (BOOL)isEqual:(id)object {
//1. == 判断地址
if (self == object) return YES;
//2.isKindOfClass 判断对象类型
if (![object isKindOfClass:[self class]]) return NO;
//3. 进行业务逻辑判断
return [self isEqualToFather:(Father *)object];
}
- (BOOL)isEqualToFather:(Father *)object {
//业务逻辑
if ([self.name isEqualToString:object.name]) {
return YES;
}else {
return NO;
}
}
18.property 的常用修饰词有哪些?weak 和 assign 的区别?weak 的实现原理是什么?
(1)属性常用修饰词分为四类:
原子性--- nonatomic/atomic 特质
读/写权限---readwrite(读写)、readonly (只读)
内存管理语义---assign、strong、 weak、unsafe_unretained、copy
方法名---getter=<name> 、setter=<name>
(2)assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。
assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。
而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。
(3)Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
19.线程安全的处理手段有哪些?把你想到的都说一下。
(1)OSSpinLock
OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
目前已经不再安全,可能会出现优先级反转问题
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
(2)os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
(3)pthread_mutex
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
(4)NSLock、NSRecursiveLock
NSLock是对mutex普通锁的封装
NSRecursiveLock也是对mutex递归锁的封装
(5)NSCondition
NSCondition是对mutex和cond的封装
(6)NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
(7)dispatch_semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
(8)dispatch_queue
直接使用GCD的串行队列,也是可以实现线程同步的
(9)@synchronized
@synchronized是对mutex递归锁的封装
性能从高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
20.说一下 OperationQueue 和 GCD 的区别,以及各自的优势
1> GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装.
2> GCD仅仅支持FIFO队列,只可以设置队列的优先级,而NSOperationQueue中的每一个任务都可以被重新设置优先级(setQueuePriority:),从而实现不同操作的执行顺序调整.
3> GCD不支持异步操作之间的依赖关系设置。如果某个操作的依赖另一个操作的数据,使用NSOperationQueue能够设置依赖按照正确的顺序执行操作.(addDependency:)。GCD则没有内建的依赖关系支持(可以通过Barrior和同步任务手动实现)。
4> NSOperationQueue方便停止队列中的任务(cancelAllOperations, suspended),GCD不方便停止队列中的任务.
5> NSOperationQueue支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld).
6> GCD的执行速度比NSOperationQueue快
7>NSOperationQueue可设置最大并发数量(节电),GCD具有dispatch_once(只执行一次,单例)和dispatch_after(延迟执行)功能
21.Swift 中 struct 和 class 的区别
Swift中类和结构体非常类似,都具有定义和使用属性、方法、构造器等特性,但结构体不具有继承性,也不具备运行时强制类型转换、使用析构器和使用引用计数等能力。Swift中struct是值类型,而class是引用类型,struct内存是分配在栈上,class内存分配在堆上。
struct在func里面需要修改property的时候需要加上mutating关键字,而class就不用。
22.Swift 是如何实现多态的?
子类在同一行为接口下不同实现方式
子类用 override 关键字表达,可以用as? as! 转换
可以重写属性( get/set 方法)/方法/下标
支持实例成员重写和类型成员(隐藏关系)重写
23.Swift 和 OC,各自的优缺点有哪些?
优点:
1、 简洁的语法:我们不得不承认的是swift语言比OC精简,整个项目中丢掉了头文件,以及头文件的引入。
2、报错精准:报错的时候直接显示报错行。
3、定义变量简单:定义变量不用区分整型,浮点型等等,变量使用var,常量使用let。
4、可视化互动效果:开发工具带来了Xcode Playgrounds功能,该功能提供强大的互动效果,能让Swift源代码在撰写过程中实时显示出其运行结果。
5、函数式编程的支持:Swift 语言本身提供了对函数式编程的支持;Objc 本身是不支持的,通过引入 ReactiveCocoa 这个库才可支持函数式编程。
缺点:
1、Swift目前还没有得到全面性的推广:很多大公司,以及一些老的项目,仍然使用OC语言进行开发。老程序员从oc转到swift是一件并不简单的事,所以当你在项目中遇到一些问题的时候,你会发现,身边能帮你解决问题的人几乎没有,网络上的资源也是很稀有的。
2、Swift暂时还不稳定:你会发现,swift2.0,swift3.0,以及现在的swift4.0是有很大的区别的,每次升级开发工具的时候,看到几十甚至上百个错误提示,难免急出一身冷汗。
3、第三方库的支持不够多:我们在做一个项目时,通常会用到一些第三方,但是现在swift版本的第三方是非常稀有的,以至于在开发的过程中,不得不导入OC版本的第三方,这个时候便成了混合开发,有经验的朋友会发现,有的兼容性并不是那么好,而且在混合开发的时候,项目会变大,运行速度真的是太慢了。
4、App体积变大:使用 Swift 后, App 体积大概增加 5-8 M 左右,对体积大小敏感的慎用。(体积变大的原因是因为 Swift 还在变化,所以 Apple 没有在 iOS 系统里放入 Swift 的运行库,反而是每个 App 里都要包含其对应的 Swift 运行库。)
5、上线方式改变:在上线的时候,不能使用application Loader上传包文件,会提示你丢失了swift support files,应该使用xcode直接上传。
24.如果让你实现 NSNotificationCenter,讲一下思路
NSNotificationCenter是一个单例
NSNotificationCenter内部使用可变字典NSMutableDictionary来存储,以通知名称postName作为key,以数组NSAray作为值,该数组存储着每个观察者的信息:观察者对象、观察者处理方法、通知名称等。
当发送通知时,以通知名称为key去获取相应的观察者信息数组,然后遍历这个数组,取出观察者对象和相对应处理方法,进行实例方法调用。
25.如果让你实现 GCD 的线程池,讲一下思路
线程池包含如下8个部分:
线程池管理器(ThreadPoolManager):用于创建并管理线程池,是一个单例
工作线程(WorkThread): 线程池中线程
任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
任务队列:用于存放没有处理的任务。提供一种缓冲机制。
corePoolSize核心池的大小:默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize线程池最大线程数:它表示在线程池中最多能创建多少个线程;
存活时间keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,这是如果一个线程空闲的时间达到keepAliveTime,则会终止直到线程池中的线程数不大于corePoolSize.
具体流程:
当通过任务接口向线程池管理器中添加任务时,如果当前线程池管理器中的线程数目小于corePoolSize,则每来一个任务,就会通过线程池管理器创建一个线程去执行这个任务;
如果当前线程池中的线程数目大于等于corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;
- 你是如何学习 iOS 的?
我的学习过程主要分三个阶段:
第一个阶段,看视频学习,看书和博客,自己写代码,做项目,这个阶段就是有了独立开发的能力。
第二个阶段,就是学会了反思.注意了代码规范,写代码时有意识的考虑空间复杂度和时间复杂度,自己做单元测试,保证质量。
第三个阶段,有了自己的独立意识。在自己知识结构的基础上,做深度的思考,开始扩大自己的认知边界,学习一些新的或者以前不知道的知识。
27.讲一下 HTTPS 密钥传输流程
当客户端第一次发送请求的时候,服务器会返回一个包含公钥的受保护空间(也称为证书),当我们发送请求的时候,公钥会将请求加密再发送给服务器,服务器接到请求之后,用自带的私钥进行解密,如果正确再返回数据。这就是 HTTPS 的安全性所在。
28.讲讲 MVC、MVVM、MVP,以及你在项目里具体是怎么写的?
和初级第一道面试题一样。
29.为什么是三次握手?为什么是四次挥手?三次挥手不行吗?
其实2次握手可以完成连接的建立,但会带来资源浪费的问题.
假设通信道路堵塞,第1次的请求未能在限定时间内收到服务端的请求,可能堵塞也可能丢失,于是发送第2次请求堵塞结束后,2次的请求自然建立了2个TCP连接,但问题是,第1个连接已经被客户端放弃了(因为之前的超时客户端不会在这个连接上传输数据),而服务端开启了2个连接。此时只有第2个连接是有用的,第1个连接自然就造成了资源的浪费。
这里第3次挥手是因为服务端可能存在为传输完的数据
这里服务端在第3次挥手完之后关闭服务端向客户端的连接,第4次挥手后客户端向服务端的连接关闭
所以,为什么要4次挥手?
因为TCP的连接只能由客户端开始放弃,服务端只能被动接受,客户端放弃连接后
服务端的数据可能没传输完,所以需要4次挥手才能确保全双工通信的关闭。
30.你自己用过哪些设计模式?
https://www.jianshu.com/p/28f8ffb2a805
- iOS 系统框架里使用了哪些设计模式?至少说6个。
工厂方法、责任链模式、适配器模式、观察者模式、代理模式、单例模式。
(1)抽象工厂模式(Abstract Factory):
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
(2)适配器模式(Adapter):
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。
(3)桥梁模式(Bridge):
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
(4)建造模式(Builder):
将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。
(5)责任链模式(Chain of Responsibility):
为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
(6)命令模式(Command):
将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
(7)合成模式(Composite):
将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
(8)装饰模式(Decorator):
动态地给一个对象添加一些额外的职责。就扩展功能而言,它能生成子类的方式更为灵活。
(9)门面模式(Facade):
为子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
(10)工厂方法(Factory Method):
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method 使一个类的实例化延迟到其子类。
(11)享元模式(Flyweight):
运用共享技术以有效地支持大量细粒度的对象。
(12)解释器模式(Interpreter):
给定一个语言,定义它的语法的一种表示,并定义一个解释器,该解释器使用该表示解释语言中的句子。
(13)迭代子模式(Iterator):
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示。
(14)调停者模式(Mediator):
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式的内部表示。
(15)备忘录模式(Memento):
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
(16)观察者模式(Observer):
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
(17.)原始模型模式(Prototype):
用原型实例指定创建对象的种类,并且通过拷贝这个原型创建新的对象。
(18)代理模式(Proxy):
为其他对象提供一个代理以控制对这个对象的访问。
(19)单例模式(Singleton):
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
(20)状态模式(State):
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
(21)策略模式(Strategy):
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
(22)模板模式(Template Method):
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
(23)访问者模式(Visitor):
表示一个作用于某对象结构中的各元素的操作。该模式可以实现在不改变各元素的类的前提下定义作用于这些元素的新操作。
32.哪一个项目技术点最能体现自己的技术实力?具体讲一下。
朋友圈的优化。
33.你在项目中遇到的最大的问题是什么?你是怎么解决的?
多个下载,状态的判断。
34.用 Alamofire 比直接使用 URLSession,优势是什么?
(1)使用起来更简单(比如https就不用做处理)
(2)优秀的第三方作者会做一些优化,bug会很少
(3)第三方一般对iOS的各个版本兼容性更好一点。
35.和产品经理、测试产生冲突时,你是怎么解决的?
我们公司的产品经理一般全力更大一点,我一般先和测试沟通,如果不能达成一致,就让产品经理决定该怎么办。
36.手写一下快排
func quickSort(_ array : [Int]) -> [Int]{
guard array.count > 1 else {
return array
}
let middleNum = array[array.count/2]
let left = array.filter{ $0 < middleNum }
let middle = array.filter{ $0 == middleNum }
let right = array.filter{ $0 > middleNum }
return quickSort(left) + middle + quickSort(right)
}
let num = [5,4,3,2,1]
let result = quickSort(num)
print(result)
37.遍历一个树,要求不能用递归
public class TreeNode {
public var val : Int
public var left : TreeNode?
public var right : TreeNode?
public init(_ val : Int){
self.val = val
}
}
// 前序遍历,根-左-右
func preorderTraversal(root : TreeNode?) -> [Int] {
var res = [Int]()
var stack = [TreeNode]()
var node = root
while !stack.isEmpty || node != nil {
if node != nil {
res.append(node!.val)
stack.append(node!)
node = node!.left
} else {
node = stack.removeLast().right
}
}
return res
}
/ 中序遍历 - 左->根->右
func middleOrderTraversal(root : TreeNode?) -> [Int] {
var res = [Int]()
var stack = [TreeNode]()
var node = root
while !stack.isEmpty || node != nil {
if node != nil {
stack.append(node!)
node = node!.left
} else {
node = stack.removeLast()
res.append(node!.val)
node = node?.right
}
}
return res
}
// 层级遍历
public class TreeNode {
public var val : Int
public var left : TreeNode?
public var right : TreeNode?
public init(_ val : Int){
self.val = val
}
}
func tieredTraversal(rootNode:TreeNode?) -> [Int]? {
if rootNode == nil {
return nil
}
// 排序的数组
var traversalArr:[Int] = []
var stack:[TreeNode] = [rootNode!]
while stack.count > 0 {
let headNode:TreeNode = stack[0]
stack.remove(at: 0)
traversalArr.append(headNode.val)
if headNode.left != nil {
stack.append(headNode.left!)
}
if headNode.right != nil {
stack.append(headNode.right!)
}
}
return traversalArr
}
38.找出两个字符串的最大公共子字符串
func commonString(_ str1 :String, _ str2 : String) -> String {
var minString : String
var maxString : String
if str1.count<str2.count {
minString = str1
maxString = str2
}else{
minString = str2
maxString = str1
}
var i = minString.count
while i != 0 {
var j = 0
while j <= minString.count - i {
let range = NSMakeRange(j, j+i)
let childString = (minString as NSString).substring(with: range)
if (maxString.contains(childString as String)) {
print(childString)
return childString
}
j += 1
}
i -= 1
}
return ""
}
let str1 = "abc"
let str2 = "abbd"
commonString(str1, str2)
39.给出一个整形数组和一个目标值,判断数组中是否有两个数之和等于目标值。
func twoSum(nums: [Int], _ target : Int) -> Bool{
var set = Set<Int>()
for num in nums {
if set.contains(target - num) {
return true
}
set.insert(num)
}
return false
}
40.给定一个整形数组中有且仅有两个数之和等于目标值,求这两个数在数组中的序号
func twoSum(nums : [Int],_ target : Int) -> [Int] {
var dict = [Int : Int]()
for (i,num) in nums.enumerated() {
if let lastIndex = dict[target - num] {
return [lastIndex , i]
}else{
dict[num] = i
}
}
fatalError("No valid output!")
}
41.给出一个字符串,要求将其按照单词顺序进行反转
fileprivate func _swap<T>(_ chars : inout [T],_ p : Int,_ q : Int){
(chars[p],chars[q]) = (chars[q],chars[p])
}
fileprivate func _reverse<T>(_ chars : inout [T], _ start : Int, _ end :Int) {
var start = start,end = end
while start < end {
_swap(&chars, start, end)
start += 1
end -= 1
}
}
func reverseWords(s:String?) -> String?{
guard let s = s else {
return nil
}
var chars = Array(s),start = 0
_reverse(&chars, 0, chars.count - 1)
for i in 0 ..< chars.count {
if i == chars.count - 1 || chars[i + 1] == " " {
_reverse(&chars, start, i)
start = i + 2
}
}
return String(chars)
}
42.删除链表中倒数第n个节点,注意给定n的长度小于等于链表的长度
解题思路:依然是快行指针,这次两个指针移动速度相同。但是一开始,第一个指针(在指向头节点之前)就落后第二个指针n个节点。接着两者同时移动,当第二个指针移动到尾结点时,第一个节点的下一个节点就是我们要删除的节点。
class ListNode {
var val : Int
var next : ListNode?
init(_ val : Int) {
self.val = val
self.next = nil
}
}
func removeNthFromEnd(head : ListNode?,_ n : Int) -> ListNode? {
guard let head = head else {
return nil
}
let dummy = ListNode(0)
dummy.next = head
var prev : ListNode? = dummy
var post : ListNode? = dummy
// 设置后一个节点的初试位置
for _ in 0 ..< n {
if post == nil {
break
}
post = post!.next
}
// 同时移动前后结点
while post != nil && post!.next != nil {
prev = prev!.next
post = post!.next
}
// 删除结点
prev!.next = prev!.next!.next
return dummy.next
}
43.给出一个文件的绝对路径,要求将其简化。
“.”代表当前路径。比如“/a/.”实际上就是“/a”,无论输入多少个“.”都返回当前目录。
".."代表上一级目录。比如"/a/b/.."实际上就是“/a”
解题思路:首先输入一个String,代表路径。输出要求也是String,同样代表路径。可以把input根据“/”符号进行拆分,得到一个String数组。建立一个栈,然后遍历拆分后的String数组,对于一般的String,直接将其加入栈中,对于“..”,对栈做pop操作,其他情况不做处理。
func simplifyPath(path : String) -> String {
// 用数组来实现栈的功能
var pathStack = [String]()
// 拆分原路径
let paths = path.components(separatedBy: "/")
for path in paths {
// 对于“.”我们直接跳过
guard path != "." else {
continue
}
// 对于“..”使用pop操作
if path == ".." {
if pathStack.count > 0 {
pathStack.removeLast()
}
} else if path != "" {
pathStack.append(path)
}
}
// 将栈中的内容转化为优化后的新路径
let res = pathStack.reduce(""){
total,dir in "\(total)/\(dir)"
}
// 注意空路径的返回结果是“/”
return res.isEmpty ? "/" : res
}
44.计算树的最大深度
func maxDepth(root : TreeNode?) -> Int {
guard let root = root else {
return 0
}
return max(maxDepth(root: root.left), maxDepth(root: root.right))
}
45.判断一棵树是否为二叉查找树
func isValidBST(root : TreeNode?) -> Bool {
return _helper(node: root, nil, nil)
}
private func _helper(node : TreeNode?,_ min : Int?,_ max : Int?) -> Bool {
guard let node = node else {
return true
}
// 所有右子树结点的值都必须大于跟节点的值
if let min = min,node.val <= min {
return false
}
// 所有左子树结点的值都必须大于根节点的值
if let max = max,node.val >= max {
return false
}
return _helper(node: node.left, min, node.val) && _helper(node: node.right, node.val, max)
}
46.请设计一个可以展示一颗二叉树的APP
按照层级遍历的方式布局整个UITableView
public class TreeNode {
public var val : Int
public var left : TreeNode?
public var right : TreeNode?
public init(_ val : Int){
self.val = val
}
}
func levelOrder(root : TreeNode?) -> [[Int]] {
var res = [[Int]]()
var queue = [TreeNode]()
if let root = root {
queue.append(root)
}
while queue.count > 0 {
let size = queue.count
var level = [Int]()
for _ in 0 ..< size {
let node = queue.removeFirst()
level.append(node.val)
if let left = node.left {
queue.append(left)
}
if let right = node.right {
queue.append(right)
}
}
res.append(level)
}
return res
}
47.有用过什么第三方代码管理工具
CocoaPods和Carthage.
多人协作时,往往用到的.framework库不会上传,在check out工程后,可以通过执行$ carthage bootstrap 命令,来根据 Cartfile.resolved 文件下载和编译依赖库的精确版本。而不应该用 carthage update 命令,这会更新项目中的第三方库的最新的编译版本,改变Cartfile.resolved 文件,可能造成冲突。