美文网首页interviewiOS面试题iOS大咖说
iOS面试进阶篇(八)-高级面试题

iOS面试进阶篇(八)-高级面试题

作者: 路飞_Luck | 来源:发表于2019-03-20 09:27 被阅读49次
    目录
    • 常用设计模式
    • APNS介绍
    • keychain介绍
    • NSOperation与GCD对比
    • 如何捕获异常
    • 断点续传
    • KVO原理
    • 通知,代理,KVO 优劣对比
    • APP优化途径
    1.如何应对APP版本升级,数据结构随之变化?

    如果是移动端, 视数据的重要性来定, 如果不重要, 那就忽视它. 如果重要, 就要额外做一个检查Documents(我这里假设你的数据文件放在Documents下)下的数据文件, 如果存在, 就SQL导出再加上按照新的数据结构导入到新的数据文件. 也就是两句SQL的事, 在升级后第一次进入应用的时候做这个事.

    如果是服务端, 正常情况还是需要做接口层(当然, 我也遇到没做接口层, 直接远程数据库操作的, 对这种, 我无话可说), 接口层的变动幅度, 往往没有数据层的变动大, 有时候, 哪怕数据结构变化了, 但接口层还是一样. 如果是碰到数据层变化逼迫接口层变化的情况, 那就需要保留老接口的同时, 提供新接口服务, 直到使用老接口的app保有量低到一定程度, 再关闭老接口. 我的产品接口, 是在接口中加上一个v(version)参数作为版本判断标志.

    2.常用的设计模式
    2.1 代理模式

    应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
    优势:解耦合
    敏捷原则:开放-封闭原则

    实例:

    • tableview的 数据源delegate,通过和protocol的配合,完成委托诉求。
    • 列表row个数delegate
    • 自定义的delegate
    2.2 观察者模式

    应用场景:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。
    优势:解耦合
    敏捷原则:接口隔离原则,开放-封闭原则

    实例:

    • Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
    • kvo,键值对改变通知的观察者,平时基本没用过。
    2.3 MVC模式

    应用场景:是一中非常古老的设计模式,通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。
    优势:使系统,层次清晰,职责分明,易于维护
    敏捷原则:对扩展开放-对修改封闭

    实例:

    • model-即数据模型,view-视图展示,controller进行UI展现和数据交互的逻辑控制。
    2.4 单例模式

    应用场景:确保程序运行期某个类,只有一份实例,用于进行资源共享控制。
    优势:使用简单,延时求值,易于跨模块
    敏捷原则:单一职责原则

    实例:

    • [UIApplication sharedApplication]。

    注意事项:确保使用者只能通过 getInstance方法才能获得,单例类的唯一实例。
    java,C++中使其没有公有构造函数,私有化并覆盖其构造函数。
    object c中,重写allocWithZone方法,保证即使用户用 alloc方法直接创建单例类的实例,

    返回的也只是此单例类的唯一静态变量。

    2.5 策略模式

    应用场景:定义算法族,封装起来,使他们之间可以相互替换。
    优势:使算法的变化独立于使用算法的用户
    敏捷原则:接口隔离原则;多用组合,少用继承;针对接口编程,而非实现。

    实例:

    • 排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。

    注意事项:
    1.剥离类中易于变化的行为,通过组合的方式嵌入抽象基类
    2.变化的行为抽象基类为,所有可变变化的父类
    3.用户类的最终实例,通过注入行为实例的方式,设定易变行为防止了继承行为方式,导致无关行为污染子类。完成了策略封装和可替换性。

    2.6 工厂模式

    应用场景:工厂方式创建类的实例,多与proxy模式配合,创建可替换代理类。
    优势:易于替换,面向抽象编程,application只与抽象工厂和易变类的共性抽象类发生调用关系。
    敏捷原则:DIP依赖倒置原则

    实例:
    项目部署环境中依赖多个不同类型的数据库时,需要使用工厂配合proxy完成易用性替换

    注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,增加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。

    3.单例会有什么弊端?

    主要优点:
    1、提供了对唯一实例的受控访问。
    2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    3、允许可变数目的实例。

    主要缺点:
    1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
    3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

    4.什么时候会使用Core Graphics,以及使用步骤?

    Core Graphics是基于C的API,当使用绘图操作的时候才能用到!

    1. 获取上下文(画布)
    2. 创建路径(自定义或者调用系统的API)并添加到上下文中。
    3. 进行绘图内容的设置(画笔颜色、粗细、填充区域颜色、阴影、连接点形状等)
    4. 开始绘图(CGContextDrawPath)
    5. 释放路径(CGPathRelease)
    5.你会如何存储用户的一些敏感信息,如登录的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的类

    • CSKeyChain.h
    @interface CSKeyChain : NSObject
    
    + (NSMutableDictionary *)getKeychainQuery:(NSString *)service;
    
    + (void)save:(NSString *)service data:(id)data;
    
    + (id)load:(NSString *)service;
    
    + (void)delete:(NSString *)service;
    
    @end
    
    • CSKeyChain.m
    #import "CSKeyChain.h"
    #import<Security/Security.h>
    
    @implementation CSKeyChain
    
    + (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);
    }
    
    @end
    
    • 在别的类实现存储,加载,删除敏感信息方法
    // 用来标识这个钥匙串
    static NSString * const KEY_IN_KEYCHAIN = @"com.cs.app.allinfo";
    // 用来标识密码
    static NSString * const KEY_PASSWORD = @"com.cs.app.password";
    
    + (void)savePassWord:(NSString *)password {
        NSMutableDictionary *passwordDict = [NSMutableDictionary dictionary];
        [passwordDict setObject:password forKey:KEY_PASSWORD];
        [CSKeyChain save:KEY_IN_KEYCHAIN data:passwordDict];
    }
    
    + (id)readPassWord {
        NSMutableDictionary *passwordDict = (NSMutableDictionary *)[CSKeyChain load:KEY_IN_KEYCHAIN];
        return [passwordDict objectForKey:KEY_PASSWORD];
    }
    
    + (void)deletePassWord {
        [CSKeyChain delete:KEY_IN_KEYCHAIN];
    }
    
    6.iOS Extension是什么?

    Extension是扩展,没有分类名字,是一种特殊的分类,类扩展可以扩展属性,成员变量和方法

    7.Apple Pay是什么?它的大概工作流程是怎样的?

    是苹果研制的一款用于支付的应用。

    Apple Pay依靠NFC芯片,通过结合Touch ID,可以便捷完成移动端支付,并且可以通过拍照添加信用卡。Apple Pay所有存储的支付信息都是经过加密的,用户可以通过Find my iPhone来关闭所有的支付功能。此外,iTunes用户可以使用iTunes中已经存储的信用卡信息。

    Apple Pay在消费支付流程里是扮演的是支付通道的角色,把实物信用卡电子化。通过Apple Pay消费的时候,信用卡消费信息是通过苹果的通道到银联,银联再跟相关的发卡商结算。

    8.block底层实现
    • block本质是指向一个结构体的一个指针
    • 运行时机制 比较高级的特性 纯C语言,平时写的OC代码 转换成C语言运行时的代码
    • 指令:clang -rewrite-objc main.m(可以打印验证)
    • 默认情况下,任何block都是在栈里面的,随时可能被回收
    • 只要对其做一次copy操作block的内存就会放在堆里面 不会释放
    • 只有copy才能产生一个新的内存地址 所有地址会发生改变
    9.UIScrollView大概是如何实现的,它是如何捕捉、响应手势的?

    UIScrollView在滚动过程当中,其实是在修改原点坐标。当手指触摸后, scroll view会暂时拦截触摸事件,使用一个计时器。假如在计时器到点后没有发生手指移动事件,那么 scroll view 发送 tracking events 到被点击的 subview。假如在计时器到点前发生了移动事件,那么 scroll view 取消 tracking 自己发生滚动。

    首先了解下UIScrollView对于touch事件的接收处理原理:

    • UIScrollView应该是重载了hitTest 方法,并总会返回itself 。所以所有的touch 事件都会进入到它自己里面去了。内部的touch事件检测到这个事件是不是和自己相关的,或者处理或者除递给内部的view。
    • 为了检测touch是处理还是传递,UIScrollView当touch发生时会生成一个timer。
      • 如果150ms内touch未产生移动,它就把这个事件传递给内部view
      • 如果150ms内touch产生移动,开始scrolling,不会传递给内部的view。(例如, 当你touch一个table时候,直接scrolling,你touch的那行永远不会highlight。)
      • 如果150ms内touch未产生移动并且UIScrollView开始传递内部的view事件,但是移动足够远的话,且canCancelContentTouches = YES,UIScrollView会调用touchesCancelled方法,cancel掉内部view的事件响应,并开始scrolling。(例如, 当你touch一个table, 停止了一会,然后开始scrolling,那一行就首先被highlight,但是随后就不在高亮了)
    10.NSOperation相比于GCD有哪些优势?

    GCD是基于c的底层api,NSOperation属于object-c类。ios首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用gcd实现的。

    相对于GCD:

    • NSOperation拥有更多的函数可用,具体查看api。
    • 在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
    • 有kvo,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)。
    • NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

    GCD主要与block结合使用。代码简洁高效。

    GCD也可以实现复杂的多线程应用,主要是建立个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。

    具体使用哪个,依需求而定。从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。

    11.如何访问并修改一个类的私有属性?

    有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性

    12.如何实现夜间模式?
    • 1.准备两套资源,分别对应日间模式和夜间模式。
    • 2.在系统全局保存一个变量(BOOL isNight),根据用户的操作改变这个变量的值;
    • 3.把每个需要被改变的view, viewcontroller加入通知中心中监听(NeedTransferToNight和NeedTransferToDay)事件;
    • 4.默认为日间模式,isNight = YES.
      1. 当用户点击夜间按钮时,如果isNight == YES, 讲此变量的值置为NO,通知中心发布NeedTransferToNight通知,所有需要被改变的view和viewcontroller在监听到此事 件时使用夜间资源重新绘制自身。其他view在初始化时如果发现isNight为YES.则使用夜间资源初始化自身。(反之亦然)
    • 6.运行程序,可以看到夜间模式。
    13.如何捕获异常?
    • 在app启动时(didFinishLaunchingWithOptions),添加一个异常捕获的监听。
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        
        NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
    
        return YES;
    }
    
    • 实现捕获异常日志并保存到本地的方法。
    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];
        }
    }
    
    14.frame与center bounds的关系
    • frame属性是相对于父容器的定位坐标。
    • bounds属性针对于自己,指明大小边框,默认点为(0,0),而宽和高与frame宽和高相等。
    • center属性是针对与frame属性的中心点坐标。
    • 当frame变化时,bounds和center相应变化。
    • 当bounds变化时,frame会根据新bounds的宽和高,在不改变center的情况下,进行重新设定。
    • center永远与frame相关,指定frame的中心坐标!
    15.直接调用_objc_msgForward函数将会发生什么?

    _objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

    直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。

    一旦调用_objc_msgForward,将跳过查找IMP的过程,直接触发“消息转发”,如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:“我没有在这个对象里找到这个方法的实现”

    16.通知中心的实现原理?

    推送通知的过程可以分为以下几步:

    • 1.应用服务提供商从服务器端把要发送的消息和设备令牌(device token)发送给苹果的消息推送服务器APNs。
    • 2.APNs根据设备令牌在已注册的设备(iPhone、iPad、iTouch、mac等)查找对应的设备,将消息发送给相应的设备。
    • 3.客户端设备接将接收到的消息传递给相应的应用程序,应用程序根据用户设置弹出通知消息。
    APNS.png
    17.如何关闭默认的KVO的默认实现,KVO的实现原理?

    所谓的“手动触发”是区别于“自动触发”:

    • 自动触发是指类似这种场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。

    想知道如何手动触发,必须知道自动触发 KVO 的原理:

    键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey:didChangevlueForKey:。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。

    当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。


    kvo.png

    如下所示:

    18.断点续传如何实现的?

    断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当 某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。续传就是当一个未完成的下载任务再次开始时,会从上次的断点继续传送。

    使用多线程断点续传下载的时候,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,多个线程并发可以占用服务器端更多资源,从而加快下载速度。

    在下载(或上传)过程中,如果网络故障、电量不足等原因导致下载中断,这就需要使用到断点续传功能。下次启动时,可以从记录位置(已经下载的部分)开始,继续下载以后未下载的部分,避免重复部分的下载。断点续传实质就是能记录上一次已下载完成的位置。

    断点续传的过程

    • 1.断点续传需要在下载过程中记录每条线程的下载进度;
    • 2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库;
    • 3.在每次向文件中写入数据之后,在数据库中更新下载进度;
    • 4.下载完成之后删除数据库中下载记录。
    19.通知,代理,KVO的区别,以及通知的多线程问题
    19.1 delegate

    当我们第一次编写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是遵守同一个协议,但还是很难告诉多个对象同一个事件,不过有可能。
    19.2 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不能从观察者获得任何的反馈信息
    19.3 KVO

    KVO是一个对象能够观察另外一个对象的属性的值,并且能够发现值的变化。前面两种模式更加适合一个controller与任何其他的对象进行通信,而KVO更加适合任何类型的对象侦听另外一个任意对象的改变(这里也可以是controller,但一般不是controller)。这是一个对象与另外一个对象保持同步的一种方法,即当另外一种对象的状态发生改变时,观察对象马上作出反应。它只能用来对属性作出反应,而不会用来对方法或者动作作出反应。

    优点:

    • 1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
    • 2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
    • 3.能够提供观察的属性的最新值以及先前值;
    • 4.用key paths来观察属性,因此也可以观察嵌套对象;
    • 5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察

    缺点:

    • 1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
    • 2.对属性重构将导致我们的观察代码不再可用;
    • 3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
    • 4.当释放观察者时不需要移除观察者。

    总结:

    1. 从上面的分析中可以看出3中设计模式都有各自的优点和缺点。其实任何一种事物都是这样,问题是如何在正确的时间正确的环境下选择正确的事物。下面就讲讲如何发挥他们各自的优势,在哪种情况下使用哪种模式。注意使用任何一种模式都没有对和错,只有更适合或者不适合。每一种模式都给对象提供一种方法来通知一个事件给其他对象,而且前者不需要知道侦听者。在这三种模式中,我认为KVO有最清晰的使用案例,而且针对某个需求有清晰的实用性。而另外两种模式有比较相似的用处,并且经常用来给controller间进行通信。那么我们在什么情况使用其中之一呢?

    2. 根据我开发iOS应用的经历,我发现有些过分的使用通知模式。我个人不是很喜欢使用通知中心。我发现用通知中心很难把握应用的执行流程。UserInfo dictionaries的keys到处传递导致失去了同步,而且在公共空间需要定义太多的常量。对于一个工作于现有的项目的开发者来说,如果过分的使用通知中心,那么很难理解应用的流程。

    3. 我觉得使用命名规则好的协议和协议方法定义对于清晰的理解controllers间的通信是很容易的。努力的定义这些协议方法将增强代码的可读性,以及更好的跟踪你的app。代理协议发生改变以及实现都可通过编译器检查出来,如果没有将会在开发的过程中至少会出现crash,而不仅仅是让一些事情异常工作。甚至在同一事件通知多控制器的场景中,只要你的应用在controller层次有着良好的结构,消息将在该层次上传递。该层次能够向后传递直至让所有需要知道事件的controllers都知道。

    4. 当然会有delegation模式不适合的例外情况出现,而且notification可能更加有效。例如:应用中所有的controller需要知道一个事件。然而这些类型的场景很少出现。另外一个例子是当你建立了一个架构而且需要通知该事件给正在运行中应用。

    5. 根据经验,如果是属性层的时间,不管是在不需要编程的对象还是在紧紧绑定一个view对象的model对象,我只使用观察。对于其他的事件,我都会使用delegate模式。如果因为某种原因我不能使用delegate,首先我将估计我的app架构是否出现了严重的错误。如果没有错误,然后才使用notification。

    20.苹果是如何实现autorelesaepool的?

    autoreleasepool以一个队列数组的形式实现,主要通过下列三个函数完成.

    • objc_autoreleasepoolPush
    • objc_autoreleasepoolPop
    • objc_aurorelease
    21.你一般是如何优化你的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 持续重构代码。减少代码的逻辑复杂度)
    • 日常交流(经常分享一些代码,或者逻辑处理中的坑)
    22.push Notification原理

    本地推送:不需要联网也可以推送,是开发人员在APP内设定特定的时间来提醒用户干什么

    远程推送:需要联网,用户的设备会于苹果APNS服务器形成一个长连接,用户设备会发送uuid和Bundle idenidentifier给苹果服务器,苹果服务器会加密生成一个deviceToken给用户设备,然后设备会将deviceToken发送给APP的服务器,服务器会将deviceToken存进他们的数据库,这时候如果有人发送消息给我,服务器端就会去查询我的deviceToken,然后将deviceToken和要发送的信息发送给苹果服务器,苹果服务器通过deviceToken找到我的设备并将消息推送到我的设备上,这里还有个情况是如果APP在线,那么APP服务器会于APP产生一个长连接,这时候APPF服务器会直接通过deviceToken将消息推送到设备上

    23.为什么NotificationCenter要removeObserver?如何实现自动remove?
    • 如果不移除的话,万一注册通知的类被销毁以后又发了通知,程序会崩溃.因为向野指针发送了消息
    • 实现自动remove:通过自释放机制,通过动态属性将remove转移给第三者,解除耦合,达到自动实现remove

    相关文章

      网友评论

        本文标题:iOS面试进阶篇(八)-高级面试题

        本文链接:https://www.haomeiwen.com/subject/zxyamqtx.html