前言在投递简历之前,就是所谓的寒冬将至,开个年会都是守望寒冬,然后我身边的准备跳槽的大佬们,都是有几分凉意,不过我还好,总感觉一个人吃饱,全家不饿,O(∩_∩)O哈!没想那么多,直接就全身投入,找工作。现在做个回顾吧,为自己,也为路过的各位大侠。 先说一个问题,是寒冬吗?我真没觉得,说自己的一个亲身体会,不夸张的说,基本上是每天2家,且持续一个月,当然是距离可以接受,公司小中大都有的,我感觉不是互联网的寒冬,是自己的寒冬,有一句说的很好,人生就两季,努力是旺季,不努力是淡季!我感觉很有道理~~~~
现在面试要求高在要会各种语言,另外要很深入,要够底层,要懂数据结构与算法之美(面试过的都会体会什么是真是一言难尽吧),看一些大佬,进入一个大厂,也写了自己的准备,我感觉真是有付出有回报的,也看出自己的一些不足吧!so,革命尚未成功,同志们仍需努力伐!!
知识点总结因为自己水平有限,可能有些路过的大佬感觉比较简单,我也总结了下,请飘过~~还有一些答案仅供参考,如有错误,请不吝赐教,在此谢过----> +(void)initinstance 与 +(void)load两个方法的区别于比较//小红书面试问题\ 先看下面表格两者的区别,后续会继续介绍 | +load | +initialize |
|---|
| 调用时机 | 被添加runtime时 | 收到第一条消息时,可能永远不调用 | | 调用顺序 | 父类->子类->分类 | 父类->子类 | | 调用次数 | 1次 | 多次 | | 是否需要显示调用父类实现 | 否 | 否 | | 是否沿用父类的实现 | 否 | 是 | | 分类中的实现 | 类和分类都执行 |
相同点: 系统都执行一次。 假如父类和子类都被调用,父类在子类之前被调用
不同点: load 方法会在加载类的时候就被调用,也就是 ios 应用启动的时候,就会加载所有的类,就会调用每个类的 + load 方法。 +initialize 这个方法会在 第一次初始化这个类之前 被调用,我们用它来初始化静态变量 load 会在main()函数之前调用。initialize 则在类实例化 或 类方法被调用时调用; 如果子类中没有initialize方法,则会再次调用父类的initialize方法,类别会覆盖主类的initialize,load则不会被覆盖 load顺序在 initialize之前; • initialize 方法的调用看起来会更合理,通常在它里面写代码比在 + load 里写更好,因为它是懒调用的,也有可能完全不被调用。类第一次被加载时, 类接收消息时,运行时会先检查 + initialize 有没有被调用过。如果没有,会在消息被处理前调用
--->>>> initialize 最终是通过 objc_msgSend 来执行的,objc_msgSend 会执行一系列方法查找,并且 Category 的方法会覆盖类中的方法 load 是在被添加到 runtime 时开始执行,父类最先执行,然后是子类,最后是 Category。又因为是直接获取函数指针来执行,不会像 objc_msgSend 一样会有方法查找的过程。 ---->>>> 怎么实现单例, 2种方法实现//喜马拉雅面试问题\ //第一种方式:线程安全的单例2(不推荐 效率低) + (instancetype)shareSingleton2 { @synchronized(self) { if (!singleton) { singleton = [[self alloc]init]; } } return singleton; }
//第二种方式 线程安全的单例 + (instancetype)shareSingleton { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ singleton = [[self alloc]init]; }); return singleton; }
然而仅仅知道这些是不够的,说了上面的,面试官会继续问单例,怎么实现的,加锁了吗?单例什么时候释放?然后你就会一脸懵~有同感的举个手 单例,怎么实现的,加锁了吗?单例什么时候释放 其实在上面的两个单例的创建中,@synchronized是一个锁,后面会讲到,就是说第一种是通过加锁的方式来实现,而第二种解析如下: GCD创建:dispatch_once中dispatch_once_t类型为typedef long • onceToken= 0,线程执行dispatch_once的block中代码 • onceToken= -1,线程跳过dispatch_once的block中代码不执行 • onceToken= 其他值,线程被线程被阻塞,等待onceToken值改变 用途:限制创建,提供全局调用,节约资源和提高性能。 参考 常见的应用场景: • UIApplication • NSNotificationCenter • NSFileManager • NSUserDefaults • NSURLCache • NSHTTPCookieStorage
那么单例是怎么销毁的呢?如下: 方法一: +(void)attemptDealloc{ [_instance release]; //mrc 需要释放,当然你就不能重写release的方法了. _instance = nil; }
方法二: 1. 必须把static dispatch_once_t onceToken; 这个拿到函数体外,成为全局的. 2. +(void)attempDealloc{ onceToken = 0; // 只有置成0,GCD才会认为它从未执行过.它默认为0.这样才能保证下次再次调用shareInstance的时候,再次创建对象. [_instance release]; _instance = nil; }
数据持久化下面说下数据持久化吧?如果是在2年前,你说了数据持久化有NSUserDefaults,plist,归档,CoreData巴拉巴拉,感觉这位童靴还阔以,但是现在就有点low了,你懂得~ 面试大佬会问有几种?然后每种有什么不同?什么能存储什么不能存储?每个在具体使用应该注意什么?等等,问到你怀疑人生 偏好设置(NSUserDefaults) 用于存储用户的偏好设置,同样适合于存储轻量级的用户数据,数据会自动保存在沙盒的Libarary/Preferences目录下,本质上就是一个plist文件,所以同样的不支持自定义对象存储,支持数据存储的类型为:Array,Dictionary,String,Number,Data,Date,Boolean,可以用做检查版本是否更新、是否启动引导页、自动登录、版本号等等,需要注意的是NSUserDefaults是定时的将缓存中的数据写入磁盘,并不是即时写入,为了防止在写完NSUserDefaults后,程序退出导致数据的丢失,可以在写入数据后使用synchronize强制立即将数据写入磁盘 如果这里你没有调用synchronize方法的话,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
PS: 在这里说了小问题,就是有面试官会问,你在开发中用NSUserDefaults有没有什么坑?你可以这样答:比如你存储一个值时,没有进行及时的调用synchronize方法,然后此时程序就crash了或者强制杀死,那么你再下次去取值的时候,就会取不到你之前存储的值,路过的大佬可以试下~~ Core Data Core Data是框架,并不是数据库,该框架提供了对象关系的映射功能,使得能够将OC对象转换成数据,将数据库中的数据还原成OC对象,在转换的过程中不需要编写任何的SQL语句,在Core Data中有三个重要的概念: NSPersistentStoreCoordinator:持久化存储协调器,在NSPersistentStoreCoordinator中包含了持久化存储区,在持久化存储区中包含了数据表中的很多数据,持久化存储区的设置通常选择NSSQLiteStoreType,也就是选择SQLite数据库 NSManagedObjectModel:托管对象模型,用于描述数据结构的模型
FMDB FMDB以OC的方式封装了SQLite的C语言API,减去了冗余的C语言代码,使得API更具有OC的风格,更加的面向对象,相对于Core Data框架更加的轻量级,FMDB还提供了多线程安全的数据库操作方法,在FMDB中有三个重要的概念: FMDatabase:一个FMDatabase就代表一个SQLite数据库,执行sql语句 FMResultSet:执行查询后的结果集 FMDatabaseQueue:用于在多线程中执行多个查询或更新,安全的
主要推荐的实施方案,也是最优方案,如下: 1.使用一个NSPersistentStoreCoordinator,以及两个独立的Contexts,一个context负责主线程与UI协作,一个context在后台负责耗时的处理,用Notifications的方式通知主线程的NSManagedObjectContext进行mergeChangesFromContextDidSaveNotification操作 2.后台线程做读写更新,而主线程只读 3.CoreData中的NSManagedObjectContext在多线程中不安全,如果想要多线程访问CoreData的话,最好的方法是一个线程一个NSManagedObjectContext,每个NSManagedObjectContext对象实例都可以使用同一个NSPersistentStoreCoordinator实例,这个实例可以很安全的顺序访_问永久存储,这是因为NSManagedObjectContext会在便用NSPersistentStoreCoordinator前上锁。ios5.0为NSManagedObjectContext提供了initWithConcurrentcyType方法,其中的一个NSPrivateQueueConcurrencyType,会自动的创建一个新线程来存放NSManagedObjectContext而且它还会自动创建NSPersistentStoreCoordinator, CoreData里面还带有一个通知NSManagedObjectContextDidSaveNotification,主要监听NSManagedObjectContext的数据是否改变,并合并数据改变到相应context。 面试官问的Context是那两种?这个面试官问的应该是用到的那两个Type? 答:NSConfinementConcurrencyType NSMainQueueConcurrencyType
接着谈谈数据库的优化问题,可以通过以下几点进行优化 FMDB事务批量更新数据库速度问题。(亲测可以呀---740条数据用和不用事务效率差别20倍+) 写同步(synchronous) 在SQLite中,数据库配置的参数都由编译指示(pragma)来实现的,而其中synchronous选项有三种可选状态,分别是full、normal、off 设置为synchronous OFF (0)时,SQLite在传递数据给系统以后直接继续而不暂停 一条SQL语句插入多条数据 在事务中进行插入处理。 数据有序插入。
再说下什么是事务?\英语流利说总监面试问题// 事务:
事务基本特征: 原子性(Atomicity):事务的个元素是不可分的,事务是一个完整的操作,一个操作序列,要么都执行,要么都不执行 一致性(Consistemcy):事务完成时,数据必须是一致的,保证数据的无损 隔离性(Isolation):多个事务彼此隔离,事务必须是独立的,任何事务都不应该受影响 持久性(Durability):事务完成之后,它对于系统的影响是永久的,该修改即使出现系统故障也将一直保留,真实的修改了数据库
五种 Mach-O 类型的浅要分析 这个面试题针对我自己的简历,可略过~ 在制作Framework时,可以设置framework中的Mach-O Type,不手动修改的默认配置即为 Dynamic Library,在SDK中默认使用的是 Relocatable Object File 
Executable: 可执行二进制文件 dynamic Library 动态库 Bundle :非独立二进制文件,显示加载 static Library 静态库 Relocatable Object File: 可重定位的目标文件,中间结果 Relocatable Object File 是组装静态库和动态库的零件,而静态库和动态库就是可执行二进制文件的组件。这里用了零件和组件的概念,零件是不可缺少的,组件则是可选的 Dynamic Library 更灵活;复用性更强;且就安全来说,统一放置在 Payload/Framework 目录下的自建的动态库,不参与应用的加壳操作,安全性稍逊一筹 Relocatable Object File 以及 Static Library 都是在编译后直接合并到最后的可执行文件中的,缺点相对不够灵活,但安全性稍强。 如果要偏向静态的方案,应该选择 Relocatable Object File 还是 Static Library? 使用 Relocatable Object File 可以减少二进制文件的大小 动态库和静态库的区别: 如果使用动态库,需要考虑的是: 对于启动速度的影响。 对于保密要求高的线下渠道 SDK,可能会被从 .app/ 中单独拿出来,反编译研究具体实现。静态库则比较安全一点。
内存管理 Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。 1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。 2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。 3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。 有一个很经典的面试题,考察自动释放池的如下: for (int i = 0; i < MAXFLOAT; i++) {
NSString *string = @"stdy"; string = [string lowercaseString]; string = [string stringByAppendingString:@"123"]; NSLog(@"--%@", string); }
上述的这种写法,会使内存慢慢增加,如何解决呢,面试官想要的答案就是用自动释放池,你也可以改成其他的,但不是面试官要的,你懂的,修改如下: for (int i = 0; i < MAXFLOAT; i++) { @autoreleasepool { NSString *string = @"stdy"; string = [string lowercaseString]; string = [string stringByAppendingString:@"123"]; NSLog(@"--%@", string); } }
什么时间会创建自动释放池?* 从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件,运行循环检测到事件并启动后,就会创建自动释放池。 子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。 自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如:自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|