面试   (八) : 高级面试题

宋明    |     2023/03/18 posted in    葵花宝典

2015年11月25日 星期三
11:19

  • 答的不对的地方,还请不吝指出 (陆续写答案) !
  • 1.如何应对APP版本升级,数据结构随之变化?
  • 如果是移动端, 视数据的重要性来定, 如果不重要, 那就忽视它. 如果重要, 就要额外做一个检查Documents(我这里假设你的数据文件放在Documents下)下的数据文件, 如果存在, 就SQL导出再加上按照新的数据结构导入到新的数据文件. 也就是两句SQL的事, 在升级后第一次进入应用的时候做这个事.
  •   
  • 如果是服务端, 正常情况还是需要做接口层(当然, 我也遇到没做接口层, 直接远程数据库操作的, 对这种, 我无话可说), 接口层的变动幅度, 往往没有数据层的变动大, 有时候, 哪怕数据结构变化了, 但接口层还是一样. 如果是碰到数据层变化逼迫接口层变化的情况, 那就需要保留老接口的同时, 提供新接口服务, 直到使用老接口的app保有量低到一定程度, 再关闭老接口. 我的产品接口, 是在接口中加上一个v(version)参数作为版本判断标志.
  •   
  •   
  • 2.常用的设计模式
  • (一)代理模式
  • 应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
  • 优势:解耦合
  • 敏捷原则:开放-封闭原则
  • 实例:tableview的 数据源delegate,通过和protocol的配合,完成委托诉求。
  • 列表row个数delegate
  • 自定义的delegate
  •   
  • (二)观察者模式
  • 应用场景:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。
  • 优势:解耦合
  • 敏捷原则:接口隔离原则,开放-封闭原则
  • 实例:Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
  • kvo,键值对改变通知的观察者,平时基本没用过。
  •   
  • (三)MVC模式
  • 应用场景:是一中非常古老的设计模式,通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。
  • 优势:使系统,层次清晰,职责分明,易于维护
  • 敏捷原则:对扩展开放-对修改封闭
  • 实例:model-即数据模型,view-视图展示,controller进行UI展现和数据交互的逻辑控制。
  •   
  • (四)单例模式
  • 应用场景:确保程序运行期某个类,只有一份实例,用于进行资源共享控制。
  • 优势:使用简单,延时求值,易于跨模块
  • 敏捷原则:单一职责原则
  • 实例:[UIApplication sharedApplication]。
  • 注意事项:确保使用者只能通过 getInstance方法才能获得,单例类的唯一实例。
  • java,C++中使其没有公有构造函数,私有化并覆盖其构造函数。
  • object c中,重写allocWithZone方法,保证即使用户用 alloc方法直接创建单例类的实例,
  • 返回的也只是此单例类的唯一静态变量。
  •   
  • (五)策略模式
  • 应用场景:定义算法族,封装起来,使他们之间可以相互替换。
  • 优势:使算法的变化独立于使用算法的用户
  • 敏捷原则:接口隔离原则;多用组合,少用继承;针对接口编程,而非实现。
  • 实例:排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。
  • 注意事项:1,剥离类中易于变化的行为,通过组合的方式嵌入抽象基类
  • 2,变化的行为抽象基类为,所有可变变化的父类
  • 3,用户类的最终实例,通过注入行为实例的方式,设定易变行为
  • 防止了继承行为方式,导致无关行为污染子类。完成了策略封装和可替换性。
  •   
  • (六)工厂模式
  • 应用场景:工厂方式创建类的实例,多与proxy模式配合,创建可替换代理类。
  • 优势:易于替换,面向抽象编程,application只与抽象工厂和易变类的共性抽象类发生调用关系。
  • 敏捷原则:DIP依赖倒置原则
  • 实例:项目部署环境中依赖多个不同类型的数据库时,需要使用工厂配合proxy完成易用性替换
  • 注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,
  • 增 加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。
  •   
  • 3.单例会有什么弊端?
  • 主要优点:
  • 1、提供了对唯一实例的受控访问。
  • 2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
  • 3、允许可变数目的实例。
  •   
  • 主要缺点:
  • 1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
  • 3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
  •   
  • 4.编程题:简述「Snakes and Ladders」的实现思路
  •   
  •   
  • 5.什么时候会使用Core Graphics,以及使用步骤?
  • Core Graphics是基于C的API,当使用绘图操作的时候才能用到!
    1. 获取上下文(画布)
    2. 创建路径(自定义或者调用系统的API)并添加到上下文中。
    3. 进行绘图内容的设置(画笔颜色、粗细、填充区域颜色、阴影、连接点形状等)
    4. 开始绘图(CGContextDrawPath)
    5. 释放路径(CGPathRelease)
  •   
  • 6.你会如何存储用户的一些敏感信息,如登录的token
  • 使用keychain来存储,也就是钥匙串,使用keychain需要导入Security框架
  • iOS的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每个ios程序都有一个独立的keychain存储。相对于 NSUserDefaults、文件保存等一般方式,keychain保存更为安全,而且keychain里保存的信息不会因App被删除而丢失,所以在 重装App后,keychain里的数据还能使用。从ios 3。0开始,跨程序分享keychain变得可行。
  •   
  • 如何需要在应用里使 用使用keyChain,我们需要导入Security.framework ,keychain的操作接口声明在头文件SecItem.h里。直接使用SecItem.h里方法操作keychain,需要写的代码较为复杂,为减轻 咱们程序员的开发,我们可以使用一些已经封装好了的工具类,下面我会简单介绍下我用过的两个工具类:KeychainItemWrapper和 SFHFKeychainUtils。
  •   
  • 自定义一个keychain的类
  • #import <Security/Security.h>
  •   
  • @implementation YCKKeyChain
  •   
    • (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
  •      return [NSMutableDictionary dictionaryWithObjectsAndKeys:
  •              ( __bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
  •              service, ( __bridge_transfer id)kSecAttrService,
  •              service, ( __bridge_transfer id)kSecAttrAccount,
  •              ( __bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible,
  •              nil];
  • }
  •   
    • (void)save:(NSString *)service data:(id)data {
  •      获得搜索字典
  •      NSMutableDictionary *keychainQuery = [ self getKeychainQuery:service];
  •      添加新的删除旧的
  •      SecItemDelete(( __bridge_retained CFDictionaryRef)keychainQuery);
  •      添加新的对象到字符串
  •      [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:( __bridge_transfer id)kSecValueData];
  •      查询钥匙串
  •      SecItemAdd(( __bridge_retained CFDictionaryRef)keychainQuery, NULL);
  • }
  •   
    • (id)load:(NSString *)service {
  •      id ret = nil;
  •      NSMutableDictionary *keychainQuery = [ self getKeychainQuery:service];
  •      配置搜索设置
  •      [keychainQuery setObject:( id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
  •      [keychainQuery setObject:( __bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];
  •      CFDataRef keyData = NULL;
  •      if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
  •          @try {
  •              ret = [NSKeyedUnarchiver unarchiveObjectWithData:( __bridge_transfer NSData *)keyData];
  •          } @catch (NSException *e) {
  •              NSLog( @"Unarchive of %@ failed: %@", service, e);
  •          } @finally {
  •          }
  •      }
  •      return ret;
  • }
  •   
    • (void)delete:(NSString *)service {
  •      NSMutableDictionary *keychainQuery = [ self getKeychainQuery:service];
  •      SecItemDelete(( __bridge_retained CFDictionaryRef)keychainQuery);
  • }
  •   
  • 在别的类实现存储,加载,删除敏感信息方法
  • 用来标识这个钥匙串
  • static NSString * const KEY_IN_KEYCHAIN = @"com.yck.app.allinfo";
  • 用来标识密码
  • static NSString * const KEY_PASSWORD = @"com.yck.app.password";
  •   
    • (void)savePassWord:(NSString *)password
  • {
  •      NSMutableDictionary *passwordDict = [NSMutableDictionary dictionary];
  •      [passwordDict setObject:password forKey:KEY_PASSWORD];
  •      [YCKKeyChain save:KEY_IN_KEYCHAIN data:passwordDict];
  • }
  •   
    • (id)readPassWord
  • {
  •      NSMutableDictionary *passwordDict = (NSMutableDictionary *)[YCKKeyChain load:KEY_IN_KEYCHAIN];
  •      return [passwordDict objectForKey:KEY_PASSWORD];
  • }
  •   
    • (void)deletePassWord
  • {
  •      [YCKKeyChain delete:KEY_IN_KEYCHAIN];
  • }
  •   
  •   
  • 7.iOS Extension 是什么?
  • Extension是扩展,没有分类名字,是一种特殊的分类,类扩展可以扩展属性,成员变量和方法
  •   
  • 8.Apple Pay 是什么?它的大概工作流程是怎样的?
  • 是苹果研制的一款用于支付的应用。
  • Apple Pay依靠NFC芯片,通过结合Touch ID,可以便捷完成移动端支付,并且可以通过拍照添加信用卡。Apple Pay所有存储的支付信息都是经过加密的,用户可以通过Find my iPhone来关闭所有的支付功能。此外,iTunes用户可以使用iTunes中已经存储的信用卡信息。
  •   
  • Apple Pay在消费支付流程里是扮演的是支付通道的角色,把实物信用卡电子化。通过Apple Pay消费的时候,信用卡消费信息是通过苹果的通道到银联,银联再跟相关的发卡商结算。
  •   
  • 9.iOS 的签名机制大概是怎样的?
  • 点击跳转
  •   
  • 10.block底层实现
  • block本质是指向一个结构体的一个指针
  • 运行时机制 比较高级的特性 纯C语言
  • 平时写的OC代码 转换成C语言运行时的代码
  • 指令:clang -rewrite-objc   main.m(可以打印验证)
  • 默认情况下,任何block都是在栈里面的,随时可能被回收
  • 只要对其做一次copy操作 block的内存就会放在堆里面 不会释放
  • 只有copy才能产生一个新的内存地址 所有地址会发生改变
  •   
  • 11.UIScrollView 大概是如何实现的,它是如何捕捉、响应手势的?
  • UIScrollView在滚动过程当中,其实是在修改原点坐标。当手指触摸后, scroll view会暂时拦截触摸事件,使用一个计时器。假如在计时器到点后没有发生手指移动事件,那么 scroll view 发送 tracking events 到被点击的 subview。假如在计时器到点前发生了移动事件,那么 scroll view 取消 tracking 自己发生滚动。
  • 首先了解下UIScrollView对于touch事件的接收处理原理:
  • UIScrollView应该是重载了hitTest 方法,并总会返回itself 。所以所有的touch 事件都会进入到它自己里面去了。内部的touch事件检测到这个事件是不是和自己相关的,或者处理或者除递给内部的view。
  • 为了检测touch是处理还是传递,UIScrollView当touch发生时会生成一个timer。
    1. 如果150ms内touch未产生移动,它就把这个事件传递给内部view
    2. 如果150ms内touch产生移动,开始scrolling,不会传递给内部的view。(例如, 当你touch一个table时候,直接scrolling,你touch的那行永远不会highlight。)
    3. 如果150ms内touch未产生移动并且UIScrollView开始传递内部的view事件,但是移动足够远的话,且canCancelContentTouches = YES,UIScrollView会调用touchesCancelled方法,cancel掉内部view的事件响应,并开始scrolling。(例如, 当你touch一个table, 停止了一会,然后开始scrolling,那一行就首先被highlight,但是随后就不在高亮了)
  •   
  • 12.NSOperation 相比于 GCD 有哪些优势?
  • GCD是基于c的底层api,NSOperation属于object-c类。ios 首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用gcd实现的。
  • 相对于GCD:
  • 1,NSOperation拥有更多的函数可用,具体查看api。
  • 2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
  • 3,有kvo,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)。
  • 4,NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
  • GCD主要与block结合使用。代码简洁高效。
  •    GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。
  • 具体使用哪个,依需求而定。 从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。
  •   
  • 13.如何访问并修改一个类的私有属性?
  • 有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性
  •   
  • 14.支付功能
  • 支付宝是第三方支付平台,简单来说就是协调客户,商户,银行三者关系的方便平台
  • 使用支付宝进行一个完整的支付功能,大致有以下步骤:
  •   
  • a 与支付宝进行签约,获得商户ID(partner)和账号ID(seller)
  • b 下载相应的公钥私钥文件(加密签名用)
  • c 下载支付宝SDK
  • d 生成订单信息
  • e   调用支付宝客户端,有支付宝客户端跟支付宝安全服务器打交道
  • f   支付完毕后返回支付结果给客户端和服务器
  •   
  • 支付功能的实现 有两种方式:
  • 1   支付宝的应用可以 用url 直接连接到 支付宝的官网 (当然后台是要进行处理的)
  • 2   第二种就是添加支付宝的第三方了 首先 下载支付宝的demo
  •   
  • 支付宝的demo 一般的常见问题解决
  •   
  • 1 No architecutures to compile for (ONLY_ACTIVE_ARCH = YES, active arch = x86_64,VALID_ARCHS = i386)
  •   
  • 出现这样的问题一般是 将 64 位编译进去就能解决了(这个问题只要你下载的是最新的demo一般很少见了 )
  •   
  • 解决方案:
  • targets -> Architectures 下面的Valid Architectures 添加上 arm64
  •   
  • 2 将支付宝的第三方添加到项目中的时候 有时 会出现 openssl 文件中的.h 文件报错 说此文件不能被找到出现这样的问题是 的原因一般是添加的路径 不对
  •   
  • 解决方案:
  • 点击项目名称,点击“Build Settings”选项卡,在搜索框中,以关键字“search”搜索,对“Header Search Paths” 增加头文件路径:$(SRCRCOT)/项目名称 设置一下路径 一般都能解决。
  •   
  • 3   Cannot find interface declaration for "NSObject", supercalss of 'Base64'
  • 解决方案    打开报错的文件,增加头文件
  •   
  • #import <Foundation/Foundation.h>
  •   
  • 基本上支付宝中的demo 里面的问题一般都会得到解决。然后 看着demo 跟实际的项目结合一下就ok 了
  •   
  • 正式开始   支付宝教程:
  •   
  • (因为申请的工作不是我做的 所以就此过程就略了 )
  •   
  • 1 将需要的文件,静态库等拖入工程中,这里有:include,libs,Utilities,libcrypto.a,libssl.a文件
  • 2 添加 库
  •   
  • Linked Frameworks and Libraries   中 添加
  •   
  • libssl.a    libcrypto.a    SystemConfiguration.framework
  •   
  • AlipaySDK.framework
  •   
  • 3   如果商户要在某个文件中使用给支付宝的SDK 类库,需要增加引用头文件
  • #import "Order.h"
  • #import "DataSigner.h"
  • #import <AlipaySDK/AlipaySDK.h>
  •   
  • 4   后面的基本上都是 按照官方demo写的
  • 接口调用步骤:
  •   
  • 1.封装订单模型将商品信息赋予AlixPayOrder的成员变量
  • Order *order = [[Order alloc] init];
  •   
  • 应用注册scheme,在项目的info.plist 定义URL types
  • NSString *appScheme = @"zhifu";
  •   
  • 生成订单描述
  • NSString *orderSpec = [order description];
  •   
  • 2.签名:获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
  • idsigner = CreateRSADataSigner(@“私钥key”);
  • NSString *signedString = [signer signString:orderSpec];
  •   
  • 传入订单描述 进行 签名
  • NSString *signedString = [signer signString:orderSpec];
  •   
  • 3.生成订单字符串
  • NSString *orderString = [NSString stringWithFormat:@"%@&sign="%@"&sign_type="%@"",orderSpec,signedString, @"RSA"];
  •   
  • 4.调用支付接口
  • [[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) { }];
  •   
  •   
  • 15.+[UIView animateWithDuration:animations:completion:] 内部大概是如何实现的?
  • animateWithDuration:这就等于创建一个定时器
  • animations:这是创建定时器需要实现的SEL
  • completion:是定时器结束以后的一个回调block
  •   
  • 16.Toll-Free Bridging 是什么?什么情况下会使用?
  • 17.如何实现夜间模式?
  • 1.准备两套资源,分别对应日间模式和夜间模式。
  • 2.在系统全局保存一个变量(BOOL isNight),根据用户的操作改变这个变量的值;
  • 3.把每个需要被改变的view, viewcontroller加入通知中心中监听(NeedTransferToNight和NeedTransferToDay)事件;
  • 4.默认为日间模式,isNight = YES.
    1. 当用户点击夜间按钮时,如果isNight == YES, 讲此变量的值置为NO,通知中心发布NeedTransferToNight通知,所有需要被改变的view和viewcontroller在监听到此事 件时使用夜间资源重新绘制自身。其他view在初始化时如果发现isNight为YES.则使用夜间资源初始化自身。(反之亦然)
  • 6.运行程序,可以看到夜间模式。
  •   
  • 18.如何捕获异常?
  • 1> 在app启动时(didFinishLaunchingWithOptions),添加一个异常捕获的监听。
  • NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
  •   
  • 2> 实现捕获异常日志并保存到本地的方法。
  •   
  • void UncaughtExceptionHandler(NSException *exception){
  •       
  •      异常日志获取
  •      NSArray   *excpArr = [exception callStackSymbols];
  •      NSString *reason = [exception reason];
  •      NSString *name = [exception name];
  •       
  •      NSString *excpCnt = [NSString stringWithFormat: @"exceptionType: %@ \n reason: %@ \n stackSymbols: %@",name,reason,excpArr];
  •       
  •      日常日志保存(可以将此功能单独提炼到一个方法中)
  •      NSArray   *dirArr   = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  •      NSString *dirPath = dirArr[0];
  •      NSString *logDir = [dirPath stringByAppendingString: @"/CrashLog"];
  •       
  •      BOOL isExistLogDir = YES;
  •      NSFileManager *fileManager = [NSFileManager defaultManager];
  •      if (![fileManager fileExistsAtPath:logDir]) {
  •          isExistLogDir = [fileManager createDirectoryAtPath:logDir withIntermediateDirectories: YES attributes:nil error:nil];
  •      }
  •       
  •      if (isExistLogDir) {
  •          此处可扩展
  •          NSString *logPath = [logDir stringByAppendingString: @"/crashLog.txt"];
  •          [excpCnt writeToFile:logPath atomically: YES encoding:NSUTF8StringEncoding error:nil];
  •      }
  • }
  •   
  • 19.frame 与 center bounds的关系
  •   1.frame属性是相对于父容器的定位坐标。
  • 2.bounds属性针对于自己,指明大小边框,默认点为(0,0),而宽和高与frame宽和高相等。
  • 3.center属性是针对与frame属性的中心点坐标。
  • 4.当frame变化时,bounds和center相应变化。
  • 5.当bounds变化时,frame会根据新bounds的宽和高,在不改变center的情况下,进行重新设定。
    1. center永远与frame相关,指定frame的中心坐标!
  •   
  • 20.直接调用_objc_msgForward函数将会发生什么?
  • _objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
  • 直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
  • 一旦调用_objc_msgForward,将跳过查找 IMP 的过程,直接触发“消息转发”,如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:“我没有在这个对象里找到这个方法的实现”
  •   
  • 21.通知中心的实现原理?
  • 推送通知的过程可以分为以下几步:
    1. 应用服务提供商从服务器端把要发送的消息和设备令牌(device token)发送给苹果的消息推送服务器APNs。
    2. APNs根据设备令牌在已注册的设备(iPhone、iPad、iTouch、mac等)查找对应的设备,将消息发送给相应的设备。
    3. 客户端设备接将接收到的消息传递给相应的应用程序,应用程序根据用户设置弹出通知消息。
  •   
  •   
  • 22.如何关闭默认的KVO的默认实现,KVO的实现原理?
  • 所谓的“手动触发”是区别于“自动触发”:
  • 自动触发是指类似这种场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。
  • 想知道如何手动触发,必须知道自动触发 KVO 的原理:
  • 键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。
  •   
  • 当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我画了一张示意图,如下所示:
  •   
  •   
  •   
  •   
  • 23.断点续传如何实现的?
  • 断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当 某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。续传就是当一个未完成的下载任务再次开始时,会从上次的断点继续传送。
  •   
  • 使用多线程断点续传下载的时候,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,多个线程并发可以占用服务器端更多资源,从而加快下载速度。
  • 在下载(或上传)过程中,如果网络故障、电量不足等原因导致下载中断,这就需要使用到断点续传功能。下次启动时,可以从记录位置(已经下载的部分)开始,继续下载以后未下载的部分,避免重复部分的下载。断点续传实质就是能记录上一次已下载完成的位置。
  • 断点续传的过程
  • ①断点续传需要在下载过程中记录每条线程的下载进度;
  • ②每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库;
  • ③在每次向文件中写入数据之后,在数据库中更新下载进度;
  • ④下载完成之后删除数据库中下载记录。
  •   
  •   
  • 24.通知,代理,KVO的区别,以及通知的多线程问题
  • delegation
  • 当我们第一次编写ios应用时,我们注意到不断的在使用“delegate”,并且贯穿于整个SDK。delegation模式不是IOS特有的模式,而是依赖与你过去拥有的编程背景。针对它的优势以及为什么经常使用到,这种模式可能不是很明显的。
  • delegation的基本特征是,一个controller定义了一个协议(即一系列的方法定义)。该协议描述了一个delegate对象为了能够响应一个controller的事件而必须做的事情。协议就是delegator说,“如果你想作为我的delegate,那么你就必须实现这些方法”。实现这些方法就是允许controller在它的delegate能够调用这些方法,而它的delegate知道什么时候调用哪种方法。delegate可以是任何一种对象类型,因此controller不会与某种对象进行耦合,但是当该对象尝试告诉委托事情时,该对象能确定delegate将响应。
  •   
  • delegate的优势:
  • 1.非常严格的语法。所有将听到的事件必须是在delegate协议中有清晰的定义。
  • 2.如果delegate中的一个方法没有实现那么就会出现编译警告/错误
  • 3.协议必须在controller的作用域范围内定义
  • 4.在一个应用中的控制流程是可跟踪的并且是可识别的;
  • 5.在一个控制器中可以定义定义多个不同的协议,每个协议有不同的delegates
  • 6.没有第三方对象要求保持/监视通信过程。
  • 7.能够接收调用的协议方法的返回值。这意味着delegate能够提供反馈信息给controller
  • 缺点:
  • 1.需要定义很多代码:1.协议定义;2.controller的delegate属性;3.在delegate本身中实现delegate方法定义
  • 2.在释放代理对象时,需要小心的将delegate改为nil。一旦设定失败,那么调用释放对象的方法将会出现内存crash
  • 3.在一个controller中有多个delegate对象,并且delegate是遵守同一个协议,但还是很难告诉多个对象同一个事件,不过有可能。
  •   
  • notification
  • 在IOS应用开发中有一个”Notification Center“的概念。它是一个单例对象,允许当事件发生时通知一些对象。它允许我们在低程度耦合的情况下,满足控制器与一个任意的对象进行通信的目的。这种模式的基本特征是为了让其他的对象能够接收到在该controller中发生某种事件而产生的消息,controller用一个key(通知名称)。这样对于controller来说是匿名的,其他的使用同样的key来注册了该通知的对象(即观察者)能够对通知的事件作出反应。
  • 通知优势:
  • 1.不需要编写多少代码,实现比较简单;
  • 2.对于一个发出的通知,多个对象能够做出反应,即1对多的方式实现简单
  • 3.controller能够传递context对象(dictionary),context对象携带了关于发送通知的自定义的信息
  • 缺点:
  • 1.在编译期不会检查通知是否能够被观察者正确的处理;
  • 2.在释放注册的对象时,需要在通知中心取消注册;
  • 3.在调试的时候应用的工作以及控制过程难跟踪;
  • 4.需要第三方对喜爱那个来管理controller与观察者对象之间的联系;
  • 5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况;
  • 6.通知发出后,controller不能从观察者获得任何的反馈信息
  •   
  • KVO
  • KVO是一个对象能够观察另外一个对象的属性的值,并且能够发现值的变化。前面两种模式更加适合一个controller与任何其他的对象进行通信,而KVO更加适合任何类型的对象侦听另外一个任意对象的改变(这里也可以是controller,但一般不是controller)。这是一个对象与另外一个对象保持同步的一种方法,即当另外一种对象的状态发生改变时,观察对象马上作出反应。它只能用来对属性作出反应,而不会用来对方法或者动作作出反应
  • 优点:
  • 1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
  • 2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
  • 3.能够提供观察的属性的最新值以及先前值;
  • 4.用key paths来观察属性,因此也可以观察嵌套对象;
  • 5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
  • 缺点:
  • 1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
  • 2.对属性重构将导致我们的观察代码不再可用;
  • 3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
  • 4.当释放观察者时不需要移除观察者。
  •   
  • 总结:
  • 从上面的分析中可以看出3中设计模式都有各自的优点和缺点。其实任何一种事物都是这样,问题是如何在正确的时间正确的环境下选择正确的事物。下面就讲讲如何发挥他们各自的优势,在哪种情况下使用哪种模式。注意使用任何一种模式都没有对和错,只有更适合或者不适合。每一种模式都给对象提供一种方法来通知一个事件给其他对象,而且前者不需要知道侦听者。在这三种模式中,我认为KVO有最清晰的使用案例,而且针对某个需求有清晰的实用性。而另外两种模式有比较相似的用处,并且经常用来给controller间进行通信。那么我们在什么情况使用其中之一呢?
  • 根据我开发iOS应用的经历,我发现有些过分的使用通知模式。我个人不是很喜欢使用通知中心。我发现用通知中心很难把握应用的执行流程。UserInfo dictionaries的keys到处传递导致失去了同步,而且在公共空间需要定义太多的常量。对于一个工作于现有的项目的开发者来说,如果过分的使用通知中心,那么很难理解应用的流程。
  • 我觉得使用命名规则好的协议和协议方法定义对于清晰的理解controllers间的通信是很容易的。努力的定义这些协议方法将增强代码的可读性,以及更好的跟踪你的app。代理协议发生改变以及实现都可通过编译器检查出来,如果没有将会在开发的过程中至少会出现crash,而不仅仅是让一些事情异常工作。甚至在同一事件通知多控制器的场景中,只要你的应用在controller层次有着良好的结构,消息将在该层次上传递。该层次能够向后传递直至让所有需要知道事件的controllers都知道。
  • 当然会有delegation模式不适合的例外情况出现,而且notification可能更加有效。例如:应用中所有的controller需要知道一个事件。然而这些类型的场景很少出现。另外一个例子是当你建立了一个架构而且需要通知该事件给正在运行中应用。
  • 根据经验,如果是属性层的时间,不管是在不需要编程的对象还是在紧紧绑定一个view对象的model对象,我只使用观察。对于其他的事件,我都会使用delegate模式。如果因为某种原因我不能使用delegate,首先我将估计我的app架构是否出现了严重的错误。如果没有错误,然后才使用notification。
  •   
  • 25.JSON 转对象的时候,一个NSString的属性,如果后台返回对应这个属性的类型不是NSString,而是其他的数据类型,怎么办?
  • 26.苹果是如何实现autorelesaepool的 ?
  • autoreleasepool以一个队列数组的形式实现,主要通过下列三个函数完成.
    1. objc_autoreleasepoolPush
    2. objc_autoreleasepoolPop
    3. objc_aurorelease
  •   
  • 27.说说iOS7之后, 蓝牙的围栏功能
  • 28.无线滚动
  • 29.如何实现类似 “Find My iPhone” 这样功能,这个是咱实现的呢?
  • 30.UIWebView 有哪些性能问题?有没有可替代的方案?
  • 31.为什么 NotificationCenter 要 removeObserver? 如何实现自动 remove?
  • 32.深度遍历和广度遍历使用场景
  • 33.如何保证软件质量, 怎么分析Crash日志
  • 34.如何加密
  • iOS里常见的几种信息加密方法简单总结
  • 一.MD5加密
  • MD5加密是最常用的加密方法之一,是从一段字符串中通过相应特征生成一段32位的数字字母混合码。
  • MD5主要特点是 不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样(也不是绝对的,但基本是不能一样的)。
  • MD5算法还具有以下性质:
  • 1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
  • 2、容易计算:从原数据计算出MD5值很容易。
  • 3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
  • 4、弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
  • 5、强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
  •   
  • MD5虽然说是不可逆的 但是由于有网站http://www.cmd5.com的存在,专门用来查询MD5码 所以有的简单的MD5码是可以在这里搜到源码的。
  • 为了让MD5码更加安全 涌现了很多其他方法 如加盐。 盐要足够长足够乱 得到的MD5码就很难查到。
  •   
  • 终端代码:$ echo -n abc|openssl md5           给字符串abc加密
  •   
  • 二.HMAC加密
  • 此加密方法需要先生成密钥,然后再对密码进行MD5和HMAC加密,数据库中需要存放当时使用的密钥和密码加密后的密文
  • 在用户登陆时 再次对填入的密码用密钥进行加密 并且还要加上当前时间(精确到分钟) 再次HMAC加密,服务器里也会拿出以前存放的密文加上时间再次加密。所以就算黑客在中途截取了密码的密文 也在能在1分钟只能破译才能有效,大大加强了安全性。服务器为了考虑到网络的延迟一般会多算一种答案,如23分过来的密码 他会把23分和22分的都算一下和用户匹配只要对上一个就允许登陆。
  • 如图 用户注册与用户登录
  •   
  •   
  •   
  • 三.base64加密
  • 在MIME格式的电子邮件中,base64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本。使用时,在传输编码方式中指 定base64。使用的字符包括大小写字母各26个,加上10个数字,和加号“+”,斜杠“/”,一共64个字符,等号“=”用来作为后缀用途。
  • 完整的base64定义可见RFC 1421和RFC 2045。编码后的数据比原始数据略长,为原来的4/3。
  • 原理图
  •   
  • 终端指令
  • 先cd 找到当前目录
  • 加密: $ base64 abc.png -o abc.txt
  • 解密:  $ base64 abc.txt -o 123.png -D
  •   
  • 四.对称加密算法
  • 优点:算法公开、计算量小、加密速度快、加密效率高、可逆
  • 缺点:双方使用相同钥匙,安全性得不到保证
  • 现状:对称加密的速度比公钥加密快很多,在很多场合都需要对称加密,
  • 相较于DES和3DES算法而言,AES算法有着更高的速度和资源使用效率,安全级别也较之更高了,被称为下一代加密标准
  •   
  • nECB :电子代码本,就是说每个块都是独立加密的
  • nCBC :密码块链,使用一个密钥和一个初始化向量 (IV)对数据执行加密转换
  •   
  • ECB和CBC区别:CBC更加复杂更加安全,里面加入了8位的向量(8个0的话结果等于ECB)。在明文里面改一个字母,ECB密文对应的那一行会改变,CBC密文从那一行往后都会改变。
  •   
  • ECB终端命令:
  • $ openssl enc -des-ecb -K 616263 -nosalt -in msg1.txt -out msg1.bin
  • CBC终端命令:
  • $ openssl enc -des-cbc -K 616263 -iv 0000000000000000 -nosalt -in msg1.txt -out msg2.bin
  •   
  • 五.RSA加密
  • RSA非对称加密算法
  • 非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)
  • 公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密
  • 特点:
  • 非对称密码体制的特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快
  • 对称密码体制中只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。所以保证其安全性就是保证密钥的安全,而非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了
  • 基本加密原理:
  • (1)找出两个“很大”的质数:P & Q
  • (2)N = P * Q
  • (3)M = (P – 1) * (Q – 1)
  • (4)找出整数E,E与M互质,即除了1之外,没有其他公约数
  • (5)找出整数D,使得E*D除以M余1,即 (E * D) % M = 1
  • 经过上述准备工作之后,可以得到:
  • E是公钥,负责加密
  • D是私钥,负责解密
  • N负责公钥和私钥之间的联系
  • 加密算法,假定对X进行加密
  • (X ^ E) % N = Y
  • n根据费尔马小定义,根据以下公式可以完成解密操作
  • (Y ^ D) % N = X
  •   
  • 但是RSA加密算法效率较差,对大型数据加密时间很长,一般用于小数据。
  • 常用场景:
  • 分部要给总部发一段报文,先对报文整个进行MD5得到一个报文摘要,再对这个报文摘要用公钥加密。然后把报文和这个RSA密文一起发过去。
  • 总部接收到报文之后要先确定报文是否在中途被人篡改,就先把这个密文用私钥解密得到报文摘要,再和整个报文MD5一下得到的报文摘要进行对比 如果一样就是没被改过。
  •   
  •   
  •   
  • 35.做过支付功能没
  • 36.你一般是如何优化你的APP的?
  • 一、首页启动速度
  •   启动过程中做的事情越少越好(尽可能将多个接口合并)
  •   不在UI线程上作耗时的操作(数据的处理在子线程进行,处理完通知主线程刷新)
  • 在合适的时机开始后台任务(例如在用户指引节目就可以开始准备加载的数据)
  • 尽量减小包的大小
  • 优化方法:
  • 量化启动时间
  • 启动速度模块化
  • 辅助工具(友盟,听云,Flurry)
  •   
  • 二、页面浏览速度
  • json的处理(iOS 自带的NSJSONSerialization,Jsonkit,SBJson)
  • 数据的分页(后端数据多的话,就要分页返回,例如网易新闻,或者 微博记录)
  • 数据压缩(大数据也可以压缩返回,减少流量,加快反应速度)
  • 内容缓存(例如网易新闻的最新新闻列表都是要缓存到本地,从本地加载,可以缓存到内存,或者数据库,根据情况而定)
  • 延时加载tab(比如app有5个tab,可以先加载第一个要显示的tab,其他的在显示时候加载,按需加载)
  • 算法的优化(核心算法的优化,例如有些app 有个 联系人姓名用汉语拼音的首字母排序)
  •   
  • 三、操作流畅度优化:
  • Tableview 优化(tableview cell的加载优化)
  • ViewController加载优化(不同view之间的跳转,可以提前准备好数据)
  •   
  • 四、数据库的优化:
  • 数据库设计上面的重构
  • 查询语句的优化
  • 分库分表(数据太多的时候,可以分不同的表或者库)
  •   
  • 五、服务器端和客户端的交互优化:
  • 客户端尽量减少请求
  • 服务端尽量做多的逻辑处理
  • 服务器端和客户端采取推拉结合的方式(可以利用一些同步机制)
  • 通信协议的优化。(减少报文的大小)
  • 电量使用优化(尽量不要使用后台运行)
  •   
  • 六、非技术性能优化
  • 产品设计的逻辑性(产品的设计一定要符合逻辑,或者逻辑尽量简单,否则会让程序员抓狂,有时候用了好大力气,才可以完成一个小小的逻辑设计问题)
  • 界面交互的规范(每个模块的界面的交互尽量统一,符合操作习惯)
  • 代码规范(这个可以隐形带来app 性能的提高,比如 用if else 还是switch ,或者是用!还是 ==)
  • code review(坚持code Review 持续重构代码。减少代码的逻辑复杂度)
  • 日常交流(经常分享一些代码,或者逻辑处理中的坑)
  •   
  • 37.push Notification原理
    • 本地推送:不需要联网也可以推送,是开发人员在APP内设定特定的时间来提醒用户干什么
    • 远程推送:需要联网,用户的设备会于苹果APNS服务器形成一个长连接,用户设备会发送uuid和Bundle idenidentifier给苹果服务器,苹果服务器会加密生成一个deviceToken给用户设备,然后设备会将deviceToken发送给APP的服务器,服务器会将deviceToken存进他们的数据库,这时候如果有人发送消息给我,服务器端就会去查询我的deviceToken,然后将deviceToken和要发送的信息发送给苹果服务器,苹果服务器通过deviceToken找到我的设备并将消息推送到我的设备上,这里还有个情况是如果APP在线,那么APP服务器会于APP产生一个长连接,这时候APPF服务器会直接通过deviceToken将消息推送到设备上
  •   
  • 38.为什么 NotificationCenter 要 removeObserver? 如何实现自动 remove?
    • 如果不移除的话,万一注册通知的类被销毁以后又发了通知,程序会崩溃.因为向野指针发送了消息
    • 实现自动remove:通过自释放机制,通过动态属性将remove转移给第三者,解除耦合,达到自动实现remove
  • 如果不移除的话,万一注册通知的类被销毁以后又发了通知,程序会崩溃.因为向野指针发送了消息
  • 实现自动remove:通过自释放机制,通过动态属性将remove转移给第三者,解除耦合,达到自动实现remove
  •   
  • 39.当 TableView 的 Cell 改变时,如何让这些改变以动画的形式呈现?
  • @interface ViewController ()
  • @property (nonatomic, strong) NSIndexPath *index;
  • @end
  •   
  • @implementation ViewController
  •   
  • static NSString *ID = @"cell";
    • (void)viewDidLoad {
  •       
  •      [ super viewDidLoad];
  •       
  •       
  • }
  •   
    • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  • {
  •      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
  •      cell.textLabel.text = [NSString stringWithFormat: @"%ld",(long)indexPath.row];
  •      return cell;
  • }
  •   
    • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  • {
  •      return 20;
  • }
  •   
    • (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  • {
  •       
  •      if(self.index == indexPath){
  •           
  •          return 120;
  •      }
  •       
  •      return 60;
  • }
  •   
    • (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  • {
  •      self.index = indexPath;
  •       
  •      [tableView deselectRowAtIndexPath:indexPath animated: TRUE];
  •      重点是这2句代码实现的功能
  •      [tableView beginUpdates];
  •      [tableView endUpdates];
  • }
  •   
  • 40.如何把一个包含自定义对象的数组序列化到磁盘?
  • 自定义对象只需要实现NSCoding协议即可
    • (void)viewDidLoad
  • {
  •      [ super viewDidLoad];
  •       
  •      User *user = [User new];
  •      Account *account = [Account new];
  •      NSArray *userArray = @[user, account];
  •      存到磁盘
  •      NSData * tempArchive = [NSKeyedArchiver archivedDataWithRootObject: userArray];
  • }
  •   代理方法
    • (instancetype)initWithCoder:(NSCoder *)coder
  • {
  •      self = [super initWithCoder:coder];
  •      if (self) {
  •          self.user = [aDecoder decodeObjectForKey:@"user"];
  •          self.account = [aDecoder decodeObjectForKey:@"account"];
  •      }
  •      return self;
  • }
  •   代理方法
  • -(void)encodeWithCoder:(NSCoder *)aCoder{
  •      [aCoder encodeObject: self.user forKey:@"user"];
  •      [aCoder encodeObject: self.account forKey:@"account"];
  • }
  •   
  •   
  • 41.非对称加密特性和用法
  • 非对称加密算法可能是世界上最重要的算法,它是当今电子商务等领域的基石。简而言之,非对称加密就是指加密密钥和解密密钥是不同的,而且加密密钥和 解密密钥是成对出现。非对称加密又叫公钥加密,也就是说成对的密钥,其中一个是对外公开的,所有人都可以获得,称为公钥,而与之相对应的称为私钥,只有这 对密钥的生成者才能拥有。公私钥具有以下重要特性:
    • 对于一个私钥,有且只有一个与之对应的公钥。生成者负责生成私钥和公钥,并保存私钥,公开公钥
    • 公钥是公开的,但不可能通过公钥反推出私钥,或者说极难反推,只能穷举,所以只要密钥足够长度,要通过穷举而得到私钥,几乎是不可能的
    • 通过私钥加密的密文只能通过公钥解密,公钥加密的密文只有通过私钥解密
  • 由于上述特性,非对称加密具有以下的典型用法:
    • 对信息保密,防止中间人攻击:将明文通过接收人的公钥加密,传输给接收人,因为只有接收人拥有对应的私钥,别人不可能拥有或者不可能通过公钥推算出私钥,所以传输过程中无法被中间人截获。只有拥有私钥的接收人才能阅读。此用法通常用于交换对称密钥。
    • 身份验证和防止篡改:权限狗用自己的私钥加密一段授权明文,并将授权明文和加密后的密文,以及公钥一并发送出来,接收方只需要通过公钥将密文解密后与授权明文对比是否一致,就可以判断明文在中途是否被篡改过。此方法用于数字签名。
  • 著名的RSA算法就是非对称加密算法,RSA以三个发明人的首字母命名。
  • 非对称加密算法如此强大可靠,却有一个弊端,就是加解密比较耗时。因此,在实际使用中,往往与对称加密和摘要算法结合使用。对称加密很好理解,此处略过1w字。我们再来看一下摘要算法。
  •   

  
已使用 Microsoft OneNote 2016 创建。