logo头像
Snippet 博客主题

ios面试题

面试准备

以下是面试官经常问的问题,都是自己个人总结,写的不是很详细

  1. 什么是运行时?

答:运行时是消息转发机制,其实是调用objc_sendMessage去调用底层的c语言函数,通过运行时可以拿到类的很多信息,比如类的方法,名称,成员变量、协议等,MJExtension就是运用runtime的,我们经常setvalue forkey也是,我们可以用运行时做很多事,比如给分类添加方法,属性等,可以动态添加方法及方法交换等

  1. 会用多线程吗?同步锁呢?
    答:多线程主要先搞清楚什么是同步和异步,串行和并发的概念,常用的NSThread,NSOperation和GCD,NSthread比较轻量级,需要自己去管理生命周期,创建线程比较简单。
    NSOperation主要是通过他的子类NSBlockOperation和NSInvocationOperation两个去封装任务,然后加入的NSOperationQueue中去执行,可以添加依赖,设置最多并发数等,
    GCD常用的有dispatch_sync,dispatch_async去封装任务,可以创建队列也可以直接获取全局队列,我之前用的较多的有一个界面请求多个接口是等所有接口请求完再去刷新界面,用到的是dispatch_group

  2. 怎么做数据缓存吗?缓存的注意事项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      NSURLCache做get缓存 两行代码搞定 不够灵活
    1.plist文件存储
    2.偏好设置
    3.归档
    4.coreData
    5.FMDB
    (1)经常更新:不能用缓存!比如股票、彩票数据  
    (2)一成不变:果断用缓存
     (3)偶尔更新:可以定期更改缓存策略 或者 清除缓存
    提示:如果大量使用缓存,会越积越大,建议定期清除缓
  3. 做过推送吗?
    答:做过,最近公司是集成的小米推送,极光推送也都差不多,需要先在项目中background中配置 打开pushNoti开关按钮,还得配置推送证书,模拟器不能调试,只有在真机上调试 主要代码都在Appdelegate中,我们主要做的是向苹果服务器注册远程推送拿到deceiveToken然后发给后台,极光sdk帮我们做的就是会将我的deceiveToken和需要发的消息给Apns,Apns再推送给我们,其次就是集成了应用内推送。

  4. 百度地图的几个主要的api?
    答:1.点击查看一个点区域内的基础设施
    2.对地图上.的点添加图标,可用于对我们常用的
    3.添加折线。弧线、多边形、圆等

  5. 不依赖后台生成二维码的话你是怎么生成二维码的?
    答:ios7之后原生的比第三方好 导入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
      #import <CoreImage/CoreImage.h>
    创建一个滤镜实例,将我们要给的信息以data形式传给滤镜再生产图片就可以了
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 1. 创建一个二维码滤镜实例(CIFilter)
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    // 滤镜恢复默认设置
    [filter setDefaults];

    // 2. 给滤镜添加数据
    NSString *string = @"are you ok?"
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    // 使用KVC的方式给filter赋值
    [filter setValue:data forKeyPath:@"inputMessage"];

    // 3. 生成二维码
    CIImage *image = [filter outputImage];

    // 4. 显示二维码
    self.imageView.image = [UIImage imageWithCIImage:image];
    }
    此外, 我们经常看到中间带有图片的二维码, 其实只需要在二维码的imageView上再添加一个imageView即可, 当然图片不能太大, 否则会导致扫描不到二维码中的信息
    我已经封装过工具类专门用于二维码生成扫描识别的
  6. 谈谈你对MVC和MVVM的理解?
    答:mvc代表什么不用多说,通常我们的controllr都和Model和VIew绑定了,Model和View并不能直接的进行通信,都必须通过Controller。那这样Model和View就是相互独立的。View只负责页面的展示,Model只是数据的存储,那么也就达到了解耦和重用的目的。有点开发经验的都知道,如果业务复杂起来,再加上其他乱七八糟的验证,controller就会变得很大,越来越难以维护。这个也是MVC比较明显的缺点。
    这时候我们的MVVM就是用来解决这些问题,controller将不再直接和真实的model进行绑定了,而通过ViewModel,viewModel进行持有真实的Model。我们就将业务逻辑放到了viewModel里面,但是这样的缺点就是类会更多,而且viewModel也会越来越大,所以我觉得没有必要严格按照哪一种模式来写,对我来说只要代码号理解就好,看个人对代码的要求。

  7. 做过蓝牙开发吗?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     蓝牙连接可以大致分为以下几个步骤
    蓝牙连接可以大致分为以下几个步骤
    1.建立一个Central Manager实例进行蓝牙管理
    2.搜索外围设备
    3.连接外围设备
    4.获得外围设备的服务
    5.获得服务的特征
    6.从外围设备读数据
    7.给外围设备发送数据 其他:提醒
    首先我们先导入系统的BLE的框架#import
    必须遵守2个协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* 中心管理者 /@property (nonatomic, strong) CBCentralManager *cMgr;
/* 连接到的外设 /@property (nonatomic, strong) CBPeripheral *peripheral;
1.建立一个Central Manager实例进行蓝牙管理
-(CBCentralManager *)cmgr
{
    if (!_cmgr) {
        _cMgr = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    }
    return _cMgr;
}

//只要中心管理者初始化 就会触发此代理方法 判断手机蓝牙状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    switch (central.state) {
        case 0:
            NSLog(@"CBCentralManagerStateUnknown");
            break;
        case 1:
            NSLog(@"CBCentralManagerStateResetting");
            break;
        case 2:
            NSLog(@"CBCentralManagerStateUnsupported");//不支持蓝牙
            break;
        case 3:
            NSLog(@"CBCentralManagerStateUnauthorized");
            break;
        case 4:
        {
            NSLog(@"CBCentralManagerStatePoweredOff");//蓝牙未开启
        }
            break;
        case 5:
        {
            NSLog(@"CBCentralManagerStatePoweredOn");//蓝牙已开启
              // 在中心管理者成功开启后再进行一些操作
            // 搜索外设
            [self.cMgr scanForPeripheralsWithServices:nil // 通过某些服务筛选外设
                                              options:nil]; // dict,条件
            // 搜索成功之后,会调用我们找到外设的代理方法
            // - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外设
        }
            break;
        default:
            break;
    }
}
2.搜索外围设备 (我这里为了举例,采用了自己身边的一个手环)
// 发现外设后调用的方法
- (void)centralManager:(CBCentralManager *)central // 中心管理者
 didDiscoverPeripheral:(CBPeripheral *)peripheral // 外设
     advertisementData:(NSDictionary *)advertisementData // 外设携带的数据
                  RSSI:(NSNumber *)RSSI // 外设发出的蓝牙信号强度
{
    //NSLog(@"%s, line = %d, cetral = %@,peripheral = %@, advertisementData = %@, RSSI = %@", __FUNCTION__, __LINE__, central, peripheral, advertisementData, RSSI);

    /*
     peripheral = , advertisementData = {
     kCBAdvDataChannel = 38;
     kCBAdvDataIsConnectable = 1;
     kCBAdvDataLocalName = OBand;
     kCBAdvDataManufacturerData = <4c69616e 0e060678 a5043853 75>;
     kCBAdvDataServiceUUIDs =     (
     FEE7
     );
     kCBAdvDataTxPowerLevel = 0;
     }, RSSI = -55
     根据打印结果,我们可以得到运动手环它的名字叫 OBand-75

     */

    // 需要对连接到的外设进行过滤
    // 1.信号强度(40以上才连接, 80以上连接)
    // 2.通过设备名(设备字符串前缀是 OBand)
    // 在此时我们的过滤规则是:有OBand前缀并且信号强度大于35
    // 通过打印,我们知道RSSI一般是带-的

    if ([peripheral.name hasPrefix:@"OBand"]) {
        // 在此处对我们的 advertisementData(外设携带的广播数据) 进行一些处理

        // 通常通过过滤,我们会得到一些外设,然后将外设储存到我们的可变数组中,
        // 这里由于附近只有1个运动手环, 所以我们先按1个外设进行处理

        // 标记我们的外设,让他的生命周期 = vc
        self.peripheral = peripheral;
        // 发现完之后就是进行连接
        [self.cMgr connectPeripheral:self.peripheral options:nil];
        NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
    }
}
3.连接外围设备
// 中心管理者连接外设成功
- (void)centralManager:(CBCentralManager *)central // 中心管理者
  didConnectPeripheral:(CBPeripheral *)peripheral // 外设
{
    NSLog(@"%s, line = %d, %@=连接成功", __FUNCTION__, __LINE__, peripheral.name);
    // 连接成功之后,可以进行服务和特征的发现

    //  设置外设的代理
    self.peripheral.delegate = self;

    // 外设发现服务,传nil代表不过滤
    // 这里会触发外设的代理方法 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    [self.peripheral discoverServices:nil];
}
// 外设连接失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%s, line = %d, %@=连接失败", __FUNCTION__, __LINE__, peripheral.name);
}

// 丢失连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%s, line = %d, %@=断开连接", __FUNCTION__, __LINE__, peripheral.name);
}
4.获得外围设备的服务 & 5.获得服务的特征
// 发现外设服务里的特征的时候调用的代理方法(这个是比较重要的方法,你在这里可以通过事先知道UUID找到你需要的特征,订阅特征,或者这里写入数据给特征也可以)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);

    for (CBCharacteristic *cha in service.characteristics) {
        //NSLog(@"%s, line = %d, char = %@", __FUNCTION__, __LINE__, cha);

    }
}
6.从外围设备读数据
// 更新特征的value的时候会调用 (凡是从蓝牙传过来的数据都要经过这个回调,简单的说这个方法就是你拿数据的唯一方法) 你可以判断是否
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{

}
7.给外围设备发送数据(也就是写入数据到蓝牙)这个方法你可以放在button的响应里面,也可以在找到特征的时候就写入,具体看你业务需求怎么用啦
[self.peripherale writeValue:_batteryData forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];//第一个参数是已连接的蓝牙设备 ;第二个参数是要写入到哪个特征; 第三个参数是通过此响应记录是否成功写入
// 需要注意的是特征的属性是否支持写数据
- (void)yf_peripheral:(CBPeripheral *)peripheral didWriteData:(NSData *)data forCharacteristic:(nonnull CBCharacteristic *)characteristic
{
    /*
     typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
     CBCharacteristicPropertyBroadcast                                                = 0x01,
     CBCharacteristicPropertyRead                                                    = 0x02,
     CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
     CBCharacteristicPropertyWrite                                                    = 0x08,
     CBCharacteristicPropertyNotify                                                    = 0x10,
     CBCharacteristicPropertyIndicate                                                = 0x20,
     CBCharacteristicPropertyAuthenticatedSignedWrites                                = 0x40,
     CBCharacteristicPropertyExtendedProperties                                        = 0x80,
     CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)        = 0x100,
     CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)    = 0x200
     };

     打印出特征的权限(characteristic.properties),可以看到有很多种,这是一个NS_OPTIONS的枚举,可以是多个值
     常见的又read,write,noitfy,indicate.知道这几个基本够用了,前俩是读写权限,后俩都是通知,俩不同的通知方式
     */
//    NSLog(@"%s, line = %d, char.pro = %d", __FUNCTION__, __LINE__, characteristic.properties);
    // 此时由于枚举属性是NS_OPTIONS,所以一个枚举可能对应多个类型,所以判断不能用 = ,而应该用包含&
}
其他:提醒有那么多的特征,我们怎么知道哪些特征是用来读数据的,哪些是用来写入的,哪些是需要订阅之后再读的呢?如果你们硬件工程师事先告诉你了,或者有完成的开发文档,那么就可以直接知道了,否则你就需要自己去查看特征的属性,推介可以使用下第三方的app——LightBlue,让你更能清楚的看到你蓝牙里面的服务,特征,特征的属性。
  1. 为什么选择现在离职?
    答:这个就实际情况回答

  2. 图片轮播会写吗?如果让你写你会怎么写?collectionView怎么写?
    答:UICollectionView。UIPageControl。NSTimer,这只是一种,也可以用3张图就实现无限循环滚动,甚至有些博客上很多2张图写的也有

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    //自动滚动
    - (void)nextPage{

    NSInteger currentNumber = self.pageControl.currentPage;
    CGFloat x = ((currentNumber + 1)%5) * self.collectionView.bounds.size.width;

    if (currentNumber <= 5) {
    [self.collectionView setContentOffset:CGPointMake(x, 0) animated:YES];

    }else{
    [self.collectionView setContentOffset:CGPointMake(x, 0) animated:NO];
    }

    self.pageControl.currentPage = x;

    }
    //当用户开始拖拽的时候就调用
    - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{

    [self removeNSTimer];
    }


    //当用户停止拖拽的时候调用
    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    [self addNSTime];
    }

    //设置页码
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    int page = (int)(scrollView.contentOffset.x/scrollView.frame.size.width + 0.5)%5;

    self.pageControl.currentPage = page;

    }
  1. 会用storyboard吗?如果让你快速搭建不等高cell的时候要注意什么?你是手动计算还是自动计算
    答:自动计算主要注意竖向的约束
  1. 你封装过什么控件吗?
    答: UIButton,多控制器切换 ,其他的一些控件也是一些常用的类似于base类的控件

  2. 你对blcok理解多少?你通常怎么传值?

  3. runloop了解多少?

  4. 对http和https的了解,会长连接吗?
    答:HTTP与HTTPS的区别:
    (1)HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费
    (2)HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议
    (3)HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
    (4)HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全
    HTTPS解决的问题: 信任主机的问题:采用HTTPS的服务器必须从CA申请一个用于证明服务器用途类型的证书,该证书只有用于对应的服务器的时候,客户端才信任此主机
    通讯过程中的数据的泄密和被篡改
    HTTPS的优点:
    (1)使用HTTPS协议可认证用户和服务器,保证数据发送到正确的客户机和服务器
    (2)HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTPS协议安全,可防止数据在传输过程中不被窃取,改变,确保数据的完整性
    (3)HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本
    (4)谷歌曾在2014年8月调整搜索引擎算法,并称”比起同等HTTPS网站,采用HTTPS加密的网站在搜索结果中的排名将会更高“

HTTPS的缺点:
(1)HTTPS协议的握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电
(2)HTTPS连接缓存不如HTTPS高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响
(3)SSL需交费,功能越强大的证书费用越高,个人网站、小型网站没有必要一般不会用
(4)SSL证书通常需要绑定IP,不能在同一IP上绑定多个于域名,IPV4资源不可能支撑这个消耗
(5)HTTPS协议的加密范围比较有限,在黑客攻击,拒绝服务攻击、服务器劫持等方面几乎起不到什么作用,最关键的,SSL证书的信用链体系并不安全,特别是在某些可以控制CA根证书的情况下,中间人攻击一样可行

Socket连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

Socket连接与HTTP连接

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

如何适配https
答:需要买一个ca证书或者搞一个自签名证书,直接在AF里面设置一下代码就可以搞定

  1. socket的理解
    socket是抽象层基于tcp
  2. 创建客户端socket
  3. 客户端连接服务器
  4. 客户端发请求
  5. 客户端接收数据

http是请求-响应

  1. iPhone X是怎么适配的,有适配过吗?ios11适配了哪些?
    答:
    1.iPhone X安全区域问题
    如果你用@1x 来做设计稿:iPhone X 安全区域是375×734px。
    如果你用@2x 来做设计稿:iPhone X 安全区域是750×1468px。
    2.如何计算 iPhone X 安全区域
    @1x 的 iPhone X 安全区域:
    安全区域=812px—Status Bar (44px) +Home Indicator(34px)
    @2x 的 iPhone X 安全区域:
    安全区域=1624px—Status Bar (88px) +Home Indicator(68px)

其实只需要将主要是导航栏高度增加了24,然后安全区域可以按照等比缩放来,具体一下细节再研究

  1. 图片上传和带进度上传怎么做的

    1
    2
    3
    4
    5
    6
    7
    8
    9
       NSURLSessionDataTask *task = [manager POST:url parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {   
     NSData *imageDatas = imageData;  
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];     formatter.dateFormat = @"yyyyMMddHHmmss";
         NSString *str = [formatter stringFromDate:[NSDate date]];
      NSString *fileName = [NSString stringWithFormat:@"%@.jpg", str];      //上传的参数(上传图片,以文件流的格式)
        [formData appendPartWithFileData:imageDatas                                 name:@"photo"                          fileName:fileName                              mimeType:@"image/jpeg"];  } progress:^(NSProgress * _Nonnull uploadProgress) {    //打印下上传进度     
     NSLog(@"上传进度");
      NSLog(@"%@",uploadProgress); 
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {       //上传成功        NSLog(@"上传成功");       NSLog(@"%@",responseObject);   } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {        //上传失败        NSLog(@"上传失败");   }];
  2. H5交互怎么做的
    答:JavaScriptcore来写,定义好协议,跟后台约定好处理事件的方法,点击H5直接调用oc的方法,这个我早已封装好了WebView的代码

  3. 内购和广告
    答:1.创建app 并填写app信息
    2.创建需要销售的商品
    3.创建需要销售的商品-选择商品类型
    4.填写商品信息
    5.添加需要销售的商品到app
    6.填写银行卡、税务信息等
    还需要添加沙箱技术测试员
    以上都是由运营人员做
    导入
    #import <StoreKit/StoreKit.h>
    遵循SKProductsRequestDelegate、SKPaymentTransactionObserver两个协议
    SKProduct

广告:
导入#import <iAd/iAd.h>框架
遵循ADBannerViewDelegate协议

  1. 第三方登录
    答:拿到yingsheId,昵称,头像等信息传给后台

  2. 组件化

  3. 性能调优

  4. 控件封装

  5. 数据库存储

  6. block有3种、全局、堆、栈
    只要被赋值copy了就会从栈区复制到堆区
    block反向传旨再b界面申明block就可以了

  7. 什么是响应链?
    答:从上往下,第一个是从uiwindow开始执行hit test方法然后是controller.view一直找到能响应的

微信打赏

赞赏是不耍流氓的鼓励