iOS开发面试整理之小知识篇(一)
- 当系统出现内存警告时会发生什么?
- 会将不在当前窗口上的view暂时移除
- 如果放任内存警告,最终会导致软件强制被系统关闭
- @property与@synthesize的本质是什么?
- 都是编译器特性
- Xcode4.4之前:
- 自动生成get方法与set方法的声明
@synthesize age;
:默认会访问age
这个成员变量,如果没有age
,就会自动生成@private
类型的age
变量@synthesize age = _age;
: 自动生成age的setter和getter实现,并且会访问_age这个成员变量,如果不存在,就会自动生成@private类型的_age成员变量
- Xcode4.4之后
- 生成get/set方法的声明
- 生成带下划线的成员变量_age(注意这变量是@private)
- 生成get/set方法的实现
- 桥接(Toll-Free Bridging)是什么?什么情况下会使用(注意:这里ARC下需要管理内存)?
- 用于在Foundation对象与Core Foundation对象之间交换数据,俗称桥接
- 在ARC环境下,Foundation对象转成 Core Foundation对象
- 使用
__bridge
桥接以后ARC会自动管理2个对象 - 使用
__bridge_retained
桥接需要手动释放Core Foundation对象
- 使用
- 在ARC环境下, Core Foundation对象转成 Foundation对象
- 使用
__bridge
桥接,如果Core Foundation对象被释放,Foundation对象也同时不能使用了,需要手动管理Core Foundation对象 - 使用
__bridge_transfer
桥接,系统会自动管理2个对象
- 使用
- Push Notification 是如何工作的?
- 推送通知分为两种,一个是本地推送,一个是远程推送
- 本地推送:不需要联网也可以推送,是开发人员在APP内设定特定的时间来提醒用户干什么
- 远程推送:需要联网,用户的设备会与苹果APNS服务器形成一个长连接,用户设备会发送uuid和Bundle idenidentifier给苹果服务器,苹果服务器会加密生成一个deviceToken给用户设备,然后设备会将deviceToken发送给APP的服务器,服务器会将deviceToken存进他们的数据库,这时候如果有人发送消息给我,服务器端就会去查询我的deviceToken,然后将deviceToken和要发送的信息发送给苹果服务器,苹果服务器通过deviceToken找到我的设备并将消息推送到我的设备上,这里还有个情况是如果APP在线,那么APP服务器会于APP产生一个长连接,这时候APP服务器会直接通过deviceToken将消息推送到设备上
- 推送通知分为两种,一个是本地推送,一个是远程推送
- 什么是 Protocol,Delegate 一般是怎么用的?
- 协议是一个方法签名的列表,在其中可以定义若干个方法,遵守该协议的类可以实现协议里的方法,在协议中使用@property只会生成setter和getter方法的声明
- delegate用法:成为一个类的代理,可以去实现协议里的方法,delegate用weak修饰,防止循环引用
- 使用 Block 时需要注意哪些问题?
- block引发循环引用的因素:
- 一个对象拥有一个block属性,然而这个block内部却又直接或间接使用了这个对象或者对象的属性.
- block的循环引用分为2种:
- 第一种: self(控制器)有一个block属性,而在block中却又使用了self的属性
- 解决办法:
__weak typeof(self) weakSelf = self
;
- 解决办法:
- 第二种: 如果不是self呢? 是一个P对象有一个block属性,而在block中却又使用了P的属性呢?
- 解决办法: 创建这个对象时用
__block
修饰,此时的作用是当block
内部使用了自己时,不让block
对自己retain
(计数器加1) -
代码举例:
//如果对象中的block又用到了对象自己, 那么为了避免内存泄露, 应该将对象修饰为__block __block Person *p = [[Person alloc] init]; // 1 p.name = @"lnj"; NSLog(@"retainCount = %lu", [p retainCount]); p.pBlock = ^{ NSLog(@"name = %@", p.name); // 1 }; NSLog(@"retainCount = %lu", [p retainCount]); p.pBlock(); [p release]; // 0 // [p release]; // 2B
- 解决办法: 创建这个对象时用
- 第一种: self(控制器)有一个block属性,而在block中却又使用了self的属性
- 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下:
__strong typeof(self) strongSelf = weakSelf;
- 如果需要在block内部改变外部变量的话,需要在用__block修饰外部变量
- block引发循环引用的因素:
- performSelector:withObject:afterDelay: 内部大概是怎么实现的,有什么注意事项么?
- 创建一个定时器,时间结束后系统会使用runtime通过方法名称(Selector本质就是方法名称)去方法列表中找到对应的方法实现并调用方法
- 注意事项
- 调用
performSelector:withObject:afterDelay:
方法时,先判断希望调用的方法是否存在respondsToSelector:
- 这个方法是异步方法,必须在主线程调用,在子线程调用永远不会调用到想调用的方法(除非子线程手动开启runloop)
- 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。(子线程需要手动开启Runloop,子线程没有runloop执行完会立刻释放线程)
- 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
- 调用
- 有哪些常见的 Crash 场景?
- 访问了僵尸对象
- 访问了不存在的方法
- 数组越界
- 在定时器下一次回调前将定时器释放,会Crash
- block没有实现
- 字典/数组..等集合中存入了nil对象
- KVO监听没有删除
- 你一般是怎么用 Instruments 的?
- Time Profiler:性能分析
- Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能
- Allocations:用来检查内存,写算法的那批人也用这个来检查
- Leaks:检查内存,看是否有内存泄露
- UIView 和 CALayer 之间的关系?
- UIView显示在屏幕上归功于CALayer,通过调用drawRect方法来渲染自身的内容,调节CALayer属性可以调整UIView的外观,UIView继承自UIResponder,CALayer不可以响应用户事件
- UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它内部是由Core Animation来实现的,它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的根绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性
- UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示
@property (nonatomic,copy) NSMutableString *name;
这句话合适吗?- 不合适,因为属性
name
的真实类型是不可变的即NSString
类型 -
该类的
set
方法为:-(void)setName:(NSMutableString *)name{ _name = [name copy]; }
- 由于
copy
方法返回的是一个不可变对象,然而_name
却是NSMutableString
类型修饰!!! - 但是如果用
strong
修饰没有问题,name
是NSMutableString
的
- 不合适,因为属性
- 哪些地方用到copy? 为什么?
- NSString用到
- 对象的属性不受外边的影响,具有保护对象属性值的作用.
-
举例:
@property (nonatomic, strong) NSString *nameStrong; // 用strong修饰 @property (nonatomic, copy) NSString *nameCopy; // 用copy修饰 @property (nonatomic, copy) NSString *normalName; // 原字符串-不可变 @property (nonatomic, strong) NSMutableString *mutableName; // 原字符串-可变 //场景1: self.normalName = @"1111"; self.nameStrong = self.normalName; self.nameCopy = self.normalName; //修改 self.normalName = @"2222"; //结果,self.nameStrong、self.nameCopy仍然为:1111,不变 //场景2: self.mutableName = [NSMutableString stringWithString:@"1111"]; self.nameStrong = self.mutableName; self.nameCopy = self.mutableName; //修改 self.mutableName = [NSMutableString stringWithString:@"222"]; //结果,self.nameStrong、self.nameCopy仍然为:1111,不变 //场景3: self.mutableName = [NSMutableString stringWithString:@"1111"]; self.nameStrong = self.mutableName; self.nameCopy = self.mutableName; //修改 self.mutableName = [self.mutableName appendString:@"aaaa"]; //结果,self.nameStrong 为1111aaaa ; self.nameCopy为1111,改变变
- 从上面来看,只有场景3,实现了外部修改,内部改变
- 从内存来分析:
- 场景1,场景2,从外部变量修改前到修改后,nameStrong、nameCopy指向的内存数据没有发生改变
- 场景3,从外部变量修改前到修改后,是在原来指向的内存修改了数据,因此nameStrong指向也会跟着修改。然而nameCopy 做了深拷贝,指向的是新的内存区,所以原来的内存区改变,不影响nameCopy
- block用到了copy
- copy的作用不是复制,而是将这个block从栈转移到堆中.
- 栈中的block不会对其内部使用的对象自动计数器+1,但是堆中的block会.
- 防止外界对象(block内部使用外界的对象)提前销毁,出现野指针错误.
- block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。
- NSString用到
- HTTP/HTTPS/TCP/UDP/SOCKET等知识区别,看iOS开发网络篇系列
- HTTP是超文本传输协议,是基于TCP传输方式的应用层协议
- http1.1之前是短连接,每次请求之后就会断开,多次请求都需要3次握手,耗时。
- http 1.1 之后可以设置为长连接,提高效率。
- HTTPs是在HTTP与TCP之间加了一层SSL层,用于加密传输的内容,采用混合加密的方式
- HTTPS在真正发送数据之前需要先安装服务器返回的CA证书,安装之后才会正式传输数据,有些网址提示安装,有些不提示(默认强制安装)
- TCP与UDP
- TCP是传输层协议,安全,耗时。需要三次握手,四次挥手。
- UDP也是传输层协议,不安全,快速。不需要三次握手、四次挥手。
- socket层只是在TCP/UDP传输层上做的一个抽象接口层,因此一个socket连接可以基于TCP,也有可能基于UDP。基于TCP协议的socket连接同样需要通过三次握手建立连接,是可靠的;基于UDP协议的socket连接不需要建立连接的过程,不过对方能不能收到都会发送过去,是不可靠的,大多数的即时通讯IM都是后者。
- 什么时候该用HTTP,什么时候该用socket?
- HTTP是短连接,Socket(基于TCP协议的)是长连接。尽管HTTP1.1开始支持持久连接,但仍无法保证始终连接。而Socket连接一旦建立TCP三次握手,除非一方主动断开,否则连接状态一直保持。
- HTTP连接服务端无法主动发消息,Socket连接双方都可以主动发送消息
- HTTP使用场合:双方不需要时刻保持连接在线,比如客户端资源的获取、文件上传等
- socket使用场合:大部分即时通讯应用(QQ、微信)、聊天室、苹果APNs等
- HTTP是超文本传输协议,是基于TCP传输方式的应用层协议
- BAD_ACCESS在什么情况下出现?
- 这种问题在开发时经常遇到。原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。
- lldb(gdb)常用的控制台调试命令?
- LLDB与GDB是什么?
- Xcode里有内置的Debugger(调试器),老版使用的是GDB,Xcode自4.3之后默认使用的就是LLDB了。
- 设备上自带的有debugserver,专门用于对应LLDB的调试
- GDB: UNIX及UNIX-like下的调试工具。
- LLDB: 开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。
- 两个都是调试用的Debugger,只是LLDB是比较高级的版本,或者在调试开发iOS应用时比较好用
- 就是打印窗口
- 调试过程
- Xcode调试窗口通过输入LLDB指令,给设备的debugserver
- debugserver接受到这个指令后执行到相应的APP上
- APP执行调试指令然后将执行的反馈信息返回给debugserver
- debugserver将这个信息反馈给LLDB
- LLDB将这个返回的信息,打印出来
- 如何使用?
- 打局部断点,然后就进入调试器了.
- 一些Xcode调试快捷键:
- command+shift+Y: 打开/关闭调试窗口
- command+Y 调试运行程序(取消/开始所有断点)
- 常用命令:
- p : 输出基本类型。是打印命令,需要指定类型。是print的简写
- po : 打印对象,会调用对象description方法。是print-object的简写
- expr : 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
- bt : 打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈
- br l : 是breakpoint list的简写
- LLDB与GDB是什么?
- iOS中你都用到了那些调试方法?
- NSLog打印调试
- 打断点
- 局部断点:在指定的哪一行打断点,然后在用控制台调试(po命令)
- 全局断点: 定位到代码崩溃的地方,然后用控制台调试(可以设置只检查OC/swift/C++代码等)
- 视图调试也叫拖图层分析(Debug View Hierarchy),通过拖出来的层分析
- EXC_BAD_ACCESS: 打开僵尸对象监控调试
- Zombie模式不能再真机上使用,只能在模拟器上使用, 只能用在模拟器和OC语言。
- LLDB调试(上面已讲)
- instruments(上面已讲)
- block的内存管理
- 无论当前环境是ARC还是MRC,只要block没有访问外部变量,block始终在全局区
- MRC情况下
- block如果访问外部变量,block在栈里
- 不能对block使用retain,否则不能保存在堆里
- 只有使用copy,才能放到堆里
- ARC情况下
- block如果访问外部变量,block在堆里
- block可以使用copy和strong,并且block是一个对象
- iOS的本地存储以及沙盒结构:
- iOS的本地存储方式
-
偏好设置:
NSUserDefaults
//存储 NSUserDefaults *login = [NSUserDefaults standardUserDefaults]; [login setObject:self.passwordField.text forKey:@"token"]; [login synchronize]; //取 NSUserDefaults *login = [NSUserDefaults standardUserDefaults]; NSString *str = [login objectForKey:@"token"];
- 只能存储OC常用数据类型(NSString、NSDictionary、NSArray、NSData、NSNumber等类型)而不能直接存储自定义数据。
- 自定义归档:
NSKeyedArchiver
-
自定义对象准守
NSCoding
协议,并实现协议方法//Person - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { [aCoder encodeObject:self.name forKey:@"name"]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { if (self = [super init]) { self.name = [aDecoder decodeObjectForKey:@"name"]; } return self; } //使用 //归档 //沙盒ducument目录 NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; //完整的文件路径 NSString *path = [docPath stringByAppendingPathComponent:@"person.archive"]; //将数据归档到path文件路径里面 BOOL success = [NSKeyedArchiver archiveRootObject:person toFile:path]; //解档 //沙盒ducument目录 NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; //完整的文件路径 NSString *path = [docPath stringByAppendingPathComponent:@"person.archive"]; Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
-
-
plist存储
NSString *arrayPath = [NSHomeDirectory() stringByAppendingString:@"/Desktop/arrayToPList.plist"]; // 待写入数据 NSArray *array = @[@"bei", @"jing", @"huan", @"ying", @"nin"]; // 数组写入 plist 文件 BOOL bl1 = [array writeToFile:arrayPath atomically:YES]; //读取 NSArray *arrayFromPlist = [NSArray arrayWithContentsOfFile:arrayPath];
- sqlite3数据库存储:
- 源生:
#import <sqlite3.h>
- 第三方:FMDB/BGFMDB
- 源生:
- core data 没有使用
- 钥匙串存储:
Keychain
- 存储到钥匙串中
- 可以多个APP共享、删除APP数据仍然存在
- 相对安全,越狱设备可以拿到
-
- iOS的沙盒结构
- Application
- 存放程序源文件,上架前经过数字签名,上架后不可修改。
- Documents:
- 常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
- Library:
- Caches:存放体积大又不需要备份的数据。(常用的缓存路径)
- Preference:设置目录,iCloud会备份设置信息。
- tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。
- Application
- iOS的本地存储方式
- iOS多线程技术有哪几种方式?
- pthread、NSThread、GCD、NSOperation
- GCD 与 NSOperation 的区别:
- GCD 和 NSOperation 都是用于实现多线程:
- GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。
- NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。
- 线程间通信
-
写出使用GCD方式从子线程回到主线程的方法代码
dispatch_sync(dispatch_get_main_queue(), ^{ });
-
- dispatch_barrier_async(栅栏函数)的作用是什么?
- 函数定义:
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
- 作用:
- 在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。
- 函数定义:
- 描述下SDWebImage里面给UIImageView加载图片的逻辑
SDWebImage
中为UIImageView
提供了一个分类UIImageView+WebCache.h
, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:
,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。- 加载图片的过程大致如下:
- 首先会在
SDWebImageCache
中寻找图片是否有对应的缓存, 它会以url
作为数据的索引先在内存中寻找是否有对应的缓存 - 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
- 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
- 下载后的图片会加入缓存中,并写入磁盘中
- .整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
- 首先会在
SDWebImage
原理:- 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用
- 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
- 从网络上获取,使用,缓存到内存,缓存到沙盒。
- 请简单的介绍下APNS发送系统消息的机制
- 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
- 应用程序接收到设备令牌并发送给自己的后台服务器
- 服务器把要推送的内容和设备发送给APNS
- APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示
- 你是否接触过OC中的反射机制?简单聊一下概念和使用
- class反射
-
通过类名的字符串形式实例化对象。
Class class = NSClassFromString(@"student"); Student *stu = [[class alloc] init];
-
将类名变为字符串。
Class class =[Student class]; NSString *className = NSStringFromClass(class);
-
- SEL反射
-
通过方法的字符串形式实例化方法。
SEL selector = NSSelectorFromString(@"setName"); [stu performSelector:selector withObject:@"Mike"];
-
将方法变成字符串。
NSStringFromSelector(@selector*(setName:));
-
- class反射
- ViewController生命周期
initWithCoder
:通过nib文件初始化时触发。awakeFromNib
:nib文件被加载的时候,会发生一个awakeFromNib
的消息到nib文件中的每个对象。loadView
:开始加载视图控制器自带的view。viewDidLoad
:视图控制器的view被加载完成。viewWillAppear
:视图控制器的view将要显示在window
上。updateViewConstraints
:视图控制器的view开始更新AutoLayout
约束。viewWillLayoutSubviews
:视图控制器的view将要更新内容视图的位置。viewDidLayoutSubviews
:视图控制器的view已经更新视图的位置。viewDidAppear
:视图控制器的view已经展示到window
上。viewWillDisappear
:视图控制器的view将要从window
上消失。viewDidDisappear
:视图控制器的view已经从window
上消失。
- KVC的底层实现?
- 当一个对象调用setValue方法时,方法内部会做以下操作:
- 检查是否存在相应的key的set方法,如果存在,就调用set方法。
- 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值
- 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
- 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法,这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
- 当一个对象调用setValue方法时,方法内部会做以下操作:
- 写一个 setter 方法用于完成 @property (nonatomic, retain) NSString*name,写一个 setter 方法用于完成 @property (nonatomic, copy) NSString *name
-
retain
- (void)setCar:(Car *)car { // 1.先判断是不是新传进来对象 if ( car != _car ) { // 2.对旧对象做一次release [_car release]; // 3.对新对象做一次retain _car = [car retain]; } }
-
copy
- (void)setCar:(Car *)car { // 1.先判断是不是新传进来对象 if ( car != _car ) { // 2.对旧对象做一次release [_car release]; // 3.对新对象做一次retain _car = [car copy]; } }
-
- 谈谈汇编
- 常见的汇编有:16位汇编,win32汇编、win64汇编、arm汇编、A&T汇编
- 汇编的核心知识(win32)
- 通常计算结果放在eax寄存器
- 函数的帧栈平衡的方式
- 外平栈
- 内平栈
-
win32汇编的常识
EBP 原EBP的内容 EBP+4 函数返回地址 EBP+8 参数区 EBP-4 局部变量区域,即函数缓冲区 EAX寄存器用于存放计算结果
- 堆栈段分配内存是从高地址向低地址分配,堆栈未使用前指向栈顶-最高地址处。
- windwos的破解、外挂
- 破解:通过IDA、OD、DT、CE等工具,修改掉PE可执行文件的二进制码。
- 外挂:使用MFC编写C++程序,通过windows api 监听应用进程,修改原程序的操作。
- 谈谈BlE开发
- 使用第三方SDK
- SDK将数据解析部分封装好,接入方实现SDK代理方法,可查看读取硬件结果
- 自己开发
- 条件
- 蓝牙传输协议
- 显示硬件连接、读、写的特征值
- 用于向硬件发送的命令
- 数据解析协议
- 返回数据为二进制(16进制),根据数据解析协议通过位运算等方法解析成我们想要的数据。
- 蓝牙传输协议
- 采用苹果自带
CoreBluetooth
框架- 实现连接、读、写、监听硬件的功能
- 数据解析的两种方式
- 本地自己编写算法,缺点:iOS和安卓都要编写
- 后台编写算法,优点:只需要写一套算法,缺点:必须网络请求对网络依赖
- 条件
- 使用第三方SDK