iOS面试准备 - ios篇
ios面试准备 - objective-c篇
ios面试准备 - 网络篇
IOS面试准备 - C++篇
iOS面试准备 - 其他篇
https://juejin.cn/post/6844903586216804359
Runtime消息发送机制
首先进行方法的查找:
1)iOS调用一个方法时,实际上会调用objc_msgSend(receiver, selector, arg1, arg2, …),该方法第一个参数是消息接收者,第二个参数是方法名,剩下的参数是方法参数;
2)iOS调用一个方法时,会先去该类的方法缓存列表里面查找是否有该方法,如果有直接调用,否则走第3)步;
3)去该类的方法列表里面找,找到直接调用,把方法加入缓存列表;否则走第4)步;
4)沿着该类的继承链继续查找,找到直接调用,把方法加入缓存列表;否则消息转发流程;
1)动态消息解析。检查是否重写了resolveInstanceMethod 方法,如果返回YES则可以通过class_addMethod 动态添加方法来处理消息,否则走第2)步;
2)消息target转发。forwardingTargetForSelector 用于指定哪个对象来响应消息。如果返回nil 则走第3)步;
3)消息转发。这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil 执行第四步;否则返回 methodSignature,则进入 forwardInvocation ,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。否则执行第4)步;
4)报错 unrecognized selector sent to instance。
load和initialize
+load在main函数之前被Runtime调用,+initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。
load
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。
当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
类中的load方法执行顺序要优先于类别(Category)
当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致
initialize
initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。
1.父类的initialize方法会比子类先执行
2.当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
3.当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)
怎么确保在load和initialize的调用只执行一次:
由于load和initialize可能会调用多次,所以在这两个方法里面做的初始化操作需要保证只初始化一次,用dispatch_once来控制
runtime应用
关联对象(Objective-C Associated Objects)给分类增加属性
方法魔法(Method Swizzling)方法添加和替换和KVO实现
消息转发(热更新)解决Bug(JSPatch)
实现NSCoding的自动归档和自动解档
实现字典和模型的转换(YYModel)
https://segmentfault.com/a/1190000023390697
线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
系统默认注册了5个Mode:
kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
与GCD关系:当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调
解决NSTimer事件在列表滚动时不执行问题
因为定时器默认是运行在NSDefaultRunLoopMode,在列表滚动时候,主线程会切换到UITrackingRunLoopMode,导致定时器回调得不到执行。
有两种解决方案:
● 指定NSTimer运行于 NSRunLoopCommonModes下。
● 在子线程创建和处理Timer事件,然后在主线程更新 UI。
GCD是核心XNU内核级实现的高效多线程编程功能。
dispatch_queue_create 创建队列
dispatch_get_main_queue() 获取主队列
dispatch_get_global_queue();并行队列,优先级依次为:ios7:高,默认,低,后台. ios7之后:用户交互,用户需要,默认,工具级,后台,没有指定。
dispatch_set_target_queue 变更优先级
dispatch_after 在一定时间之后执行
dispatch_time(DISPATCH_TIME_NOW, 200ull * NSEC_PER_SEC); 时间变量
dispatch_group_t group = dispatch_group_create(); 创建组
dispatch_group_async(group, queue, ^{NSLog(@“1”);}); 使用组
dispatch_group_notify(group, queue, ^{NSLog(@“4”);}); 在一组最后执行
dispatch_barrier_async 在这个调用之前的async先完成,再调用这个的代码块,在这个之调用后,在他之后的async才开始执行
dispatch_sync是等待处理执行结束后,再继续
dispatch_apply 自动适配线程,循环处理指定次数
dispatch_once 保证只执行一次,可以用在单例模式
+(instancetype)sharedInstance{
static MyClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[MyClass alloc]init];
});
return _sharedInstance;
}
dispatch_semaphore_create 创建信号量 自旋锁
dispatch_semaphore_signal 信号量加1
dispatch_semaphore_wait 信号量减1,如果信号量等于0就阻塞等待
NSOperation是基于GCD更高一层的封装。
一般使用 NSInvocationOperation 或者 NSBlockOperation ,也可以自己继承实现,需要实现main函数。
NSOperationQueue 是执行队列
添加到主队列的都会在主队列中执行
添加到其他队列的都会在子线程中执行
队列最大并发数设置 maxConcurrentOperationCount
函数:
NSOperationQueue的函数:
addOperation:把NSOperation 加到队列中
addExecutionBlock: 直接把代码块作为任务加到队列中
cancelAllOperations; NSOperationQueue提供的方法,可以取消队列的所有操作,所有任务取消,包括正在执行的,还未执行的。
setSuspended:(BOOL)b; 可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列
NSOperation的函数:
cancel; ,可取消单个操作
.qualityOfService = NSQualityOfServiceBackground;设置优先级有:非常低,低,通常,高,非常高
[A addDependency:B] A在B执行完了过后开始执行
https://www.jianshu.com/p/5445411fb53c
NSLock
互斥锁,底层用pthread_mutex实现
lock、unlock
trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁**操作并返回 YES,反之返回 NO
@synchronized
由recursive_mutex(互斥递归锁)实现,最终还是用了pthread_mutex。最大的问题就是,效率低,传入对象必须等待之前的锁执行完成之后才能执行,无法达到异步的效果。
NSCondition
条件变量也是互斥锁,底层是的pthread_cond条件变量和pthread_mutex互斥量的封装
wait:进入等待状态
waitUntilDate::让一个线程等待一定的时间
signal:唤醒一个等待的线程
broadcast:唤醒所有等待的线程
dispatch_semaphore 自旋锁
dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
NSRecursiveLock
是对pthread_mutex的封装
递归锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。
atomic
自旋锁
OSSpinLock 自旋锁
互斥锁和信号量的区别
互斥量用于线程的互斥,信号线用于线程的同步。
互斥量值只能为0/1,信号量值可以为非负整数。也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。
线程和进程的区别
进程是资源分配的最小单位,线程是CPU调度的最小单位
一个进程可以包含多个线程
不同进程间数据很难共享
同一进程下不同线程间数据很易共享
进程要比线程消耗更多的计算机资源
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉
进程可以拓展到多机,进程最多适合多核
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
URL scheme
Keychain 本质是数据库,经过加密,为不同app存储敏感信息
UIPasteBoard 剪切板
socket
APP Groups 同组的不同app,通过相同组名读取文件
AirDrop 分享(设备间信息传递)
UIActivityViewController 封装AirDrop
线程间通信方法包含进程间通信方法(如上),因为两个进程间通信,其实就是两个线程间在通信。单个进程内的线程通信有如下方法。
共享内存
共享磁盘文件
NSObject对象 performSelectorOnMainThread 或者 performSelector: onThread:
GCD
NSOperationQueue
解析Info.plist:加载相关信息, 签名验证,
沙箱和进程建立
加载动态库
dyld(动态链接器)会首先读取mach-o文件的Header和load commands。接着就知道了这个可执行文件依赖的动态库,然后根据递归的依赖递归加载所有动态库,同时缓存到 dyld shared cache 提高加载效率。
Rebase 修正内部(指向当前mach-o文件)的指针指向(因为地址是随机的,苹果会运用地址空间布局随机化技术ASLR来给指针的起始地址一个随机的偏移量,)
Bind 修正外部指针指向
objC的runtime初始化(ObjC setup):ObjC相关Class的注册、category注册等。
类的load方法调用
main函数 UIApplicationMain
app生命周didFinishLaunchWithOptions,applicationDidBecomeActive
vc生命周期
先编译cocopods里面的所有依赖文件
编译信息写入辅助信息,创建编译后的文件架构:将项目的文件结构对应表、将要执行的脚本、项目依赖库的文件结构对应表写成文件
运行预设的脚本。如 Cocoapods 会在 Build Phases 中预设一些脚本
编译 .m 文件,生成可执行文件 Mach-O。每次进行了 LLVM 的完整流程:前端(词法分析 - 语法分析 - 生成 IR)、优化器(优化 IR)、后端(生成汇编 - 链接 - 生成可执行文件)
链接需要的.framework库
拷贝资源文件到目标包
编译 xib 文件
链接 xib 文件
编译 Asset 文件。
处理 info.plist
执行 CocoaPods 脚本,将在编译项目前已编译好的依赖库和相关资源拷贝到包中。
拷贝 Swift 标准库
创建 .app 文件并对其签名
iOS 离屏渲染探究
手动触发离屏渲染
可以通过设置layer的shouldRasterize为YES来触发离屏渲染,在某些场景下,打开 shouldRasterize 可以将一个layer反复利用,从而达到提升效率的优势。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
// 把当前控件上的坐标系转换成子控件上的坐标系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 寻找到最合适的view
return fitView;
}
}
// 循环结束,表示没有比自己更合适的view
return self;
}
本地通知是在程序中指定某个时间,或者在多少时间倒计时,或者在特定条件之后,出现在设备的状态栏消息中的功能。
本地通知使用:在AppDelegate.m中application didFinishLauch方法中对通知方法执行注册;在AppDelegate的didRegisterUserNotificationSettings回调里知道注册是否成功;在 didReceiveLocalNotification中响应通知点击事件;发本地通知用UILocalNotification ,可配置标题,内容,声音,程序图标通知数目,触发事件等信息。
移除全部本地通知:
[[UIApplication sharedApplication] removeAllNoticfications];
取所有通知:
[UIApplication sharedApplication].scheduledLocalNotifications;
根据移除指定通知:
[[UIApplication sharedApplication] cancelLocalNotification:notification]
每台设备只要联网就会和苹果的 APNs服务器建立一个 长连接,这样只要通过苹果的APNs服务器,我们自己的服务器就可以间接的和设备保持连接了.
通知注册跟本地通知一样。
如果用户同意,苹果会根据应用的 bundleID 和 手机UDID 生成 deviceToken,然后调用 -application: didRegisterForRemoteNotificationsWithDeviceToken : 方法返回 devicetoken。
// 用户同意后,会调用此程序,获取系统的deviceToken,应把deviceToken传给服务器保存,此函数会在程序每次启动时调用(前提是用户允许通知)
//会自动执行下面的方法
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(@"log--didRegisterForRemoteNotificaionWith:%@",deviceToken);
}
相应远程推送函数是 didReceiveRemoteNotification
停止运行-应用程序已经终止,或者还未启动。
不活动-应用程序处于前台但不再接收事件(例如,用户在app处于活动时锁住了设备)。
活动-app处于“使用中”的状态。
后台-app不再屏幕上显示,但它仍然执行代码。
挂起-app仍然驻留内存但不再执行代码。
按下Home键时,app从活动状态转入后台,绝大部分app通常在几秒内就从后台变成了挂起。
在内存吃紧的时候,iphone会首先关闭那些挂起的app
1、Background Audio,这是后台的音频,这个很早之前便有,也是iOS设备中用得最多的后台应用,调用这个接口可以实现后台的音乐播放。
2、Location Services,这是后台的定位,系统会拥有统一页面进行管理。
3、VoIP,后台语音服务,类似Skype通话应用需要调用,可进行后台的语音通话。
4、Newsstand,报刊杂志后台自动下载更新,其能够自动实时更新。
5、Background Task Completion,这个接口早在iOS 4时候便拥有,其可以供任意类型的APP使用,不过在旧系统中,这个接口的后台限制运行时间仅为10分钟,意味着当应用退至后台,其后台运行仅能持续10分钟便会转至休眠状态。iOS 7中对这个接口作出了改变,原来的为连续10分钟,即不论你这10分钟内用户是否关闭屏幕进入休眠状态,应用仍然会在后台等待10分钟完结后推出,而新的改进为假如遇到关闭屏幕休眠的情况,这后台运行的10分钟便会跟随一同休眠,剩余的后台时间将会留待用户再一次唤醒设备才计算。这样后台运行的时间仍然为10分钟,但并不连续,这样做的优点为省电。
如现在有一些词典应用带有后台复制选词功能,实际上其是利用了这个接口,如果用户开启词典后并推出,即使屏幕关闭,但词典仍然在后台运行,电量消耗还是比较大的,在iOS 7上,这个问题可以得到解决。
6、 Remote Notification,这是本次较大的一个改进接口,以往聊天类应用接受推送后点进去需要再收一次信息,这情况在QQ、微信等应用上最为明显。不过拥有了这个接口后,这情况将不复存在,以后推送将能够直接启动后台任务。值得注意的是remote notification支持silent notification(静默推送),这样dropbox这类同步应用可以在后台以最节能的模式实时静默同步了,类似布卡漫画这种也可以推送正在追的漫画的新章节并在后台静默下载,待到下载好再给用户发送一个本地推送,用户点开即看无需再联网。
7、Background Transfer Service,后台上传下载。iOS最接近传统多任务的后台接口,可供任意类型的app调用,无时间限制。应用场景包括后台上传和下载数据,这使得游戏后台更新数据包,后台上传视频等等都成为可能,但是正如其名字,它只能用于处理上传下载这种传输类的任务,类似后台剪切板监控这种它就无能为力了。
https://segmentfault.com/a/1190000019569119
Masonry是一个对系统NSLayoutConstraint进行封装的第三方自动布局框架,采用链式编程的方式提供给开发者API。
https://juejin.cn/post/6844903805369188366
1.简述
CoreData是数据存储框架,采用了一种对象关系映射的存储关系。
CoreData一个比较大的优势在于在使用CoreData过程中不需要我们编写SQL语句,也就是将OC对象存储于数据库,也可以将数据库数据转为OC对象(数据库数据与OC对象相互转换)。
2.CoreData几个类
(1)NSManagedObjectContext
托管对象上下文,数据库的大多数操作是在这个类操作
(2)NSManagedObjectModel
托管对象模型,其中一个托管对象模型关联到一个模型文件,里面存储着数据库的数据结构。
(3)NSPersistentStoreCoordinator
持久化存储协调器,主要负责协调上下文玉存储的区域的关系。
(4)NSManagedObject
托管对象类,其中CoreData里面的托管对象都会继承此类。
内部封装c语言 通过 sqlite3系列函数操作数据库。
有三个主要的类:
1.FMDatabase – 表示一个单独的SQLite数据库。 用来执行SQLite的命令。
2.FMResultSet – 表示FMDatabase执行查询后结果集
3.FMDatabaseQueue – 如果你想在多线程中执行多个查询或更新,你应该使用该类。这是线程安全的。
创建FMDatabase对象时参数为SQLite数据库文件路径。该路径可以是以下三种之一:
1…文件路径。该文件路径无需真实存,如果不存在会自动创建。
2…空字符串(@”")。表示会在临时目录创建一个空的数据库,当FMDatabase 链接关闭时,文件也被删除。
3.NULL. 将创建一个内在数据库。同样的,当FMDatabase连接关闭时,数据会被销毁。
打开数据库
在和数据库交互 之前,数据库必须是打开的。如果资源或权限不足无法打开或创建数据库,都会导致打开失败
常用命令
SELECT、CREATE, UPDATE, INSERT,ALTER,COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE (等)
只要不是以SELECT开头的命令都是UPDATE命令。执行更新返回一个BOOL值。YES表示执行成功,否则表示有那些错误 。你可以调用 -lastErrorMessage 和 -lastErrorCode方法来得到更多信息。
执行查询
SELECT命令就是查询,执行查询的方法是以 -excuteQuery开头的。执行查询时,如果成功返回FMResultSet对象, 错误返回nil. 与执行更新相当,支持使用 NSError**参数。同时,你也可以使用 -lastErrorCode和-lastErrorMessage获知错误信息。
添加表字段的sql语句怎么写 1、判断表是否打开 2、判断表中是否存在当前的一个或者多个字段 3、如果不存在添加字段
应用启动优化:合并动态库;删除无用类;删除无用静态变量;将不必须在+load方法中做的事情延迟到+initialize中;尽量使用纯代码编写,减少xib的使用;耗时操作异步执行;不用AutoLayout;优化代码降低包大小
懒加载和复用对象
是否透明属性opaque YES表示不透明。
列表性能提升: reuseIdentifier避免每次渲染cell都重建;尽量非透明opaque 为YES;缓存行高;耗时如网络请求,异步加载缓存结果;Shadow Path替代直接用阴影;减少层次结构。
不要阻塞主线程,耗时操作异步到子线程
正确选择Collection:Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢, 插入/删除很慢;Dictionaries: 存储键值对。 用键来查找比较快;Sets: 无序的一组值。用值来查找很快,插入/删除很快。
使用缓存,比如NSCache,缓存文件等
重用大开销对象,恰当使用单例模式
webview重用
使用NSUserDefaults
存沙盒文件,小文件
plist 文件储存(xcode配置里面就有这个文件)
归档保存,将字典数组或者实现了(NSCoding)协议的对象转换成字节流NSData,再写入文件。使用NSKeyedArchiver和NSKeyedUnarchiver
SQLite数据库 (开源库 FMDB、Realm等)
使用 Core Data
自动释放池块提供了一种机制,您可以通过该机制放弃对象的所有权,但避免立即释放它的可能性(例如当您从方法返回对象时)
Cocoa 总是希望代码在自动释放池块中执行,否则自动释放的对象不会被释放并且您的应用程序会泄漏内存。
使用自己的自动释放池块:
1.如果您正在编写不基于 UI 框架的程序,例如命令行工具。
2.如果您编写一个创建许多临时对象的循环。
您可以在循环内使用自动释放池块在下一次迭代之前处理这些对象。在循环中使用自动释放池块有助于减少应用程序的最大内存占用。
3.如果你产生一个辅助线程。
您必须在线程开始执行后立即创建自己的自动释放池块;否则,您的应用程序将泄漏对象。(Cocoa 应用程序中的每个线程都维护自己的自动释放池块堆栈。如果您正在编写仅 Foundation 程序或分离线程,则需要创建自己的自动释放池块。)
main函数的autoreleasepool :autorelease对象的释放时机是由RunLoop控制的,会在当前RunLoop每次循环结束时释放。
iOS 自动释放池ARC与MRC
内存泄漏的原因
在用C/C++时,创建对象后未销毁,比如调用malloc后不free、调用new后不delete;
调用CoreFoundation里面的C方法后创建对对象后不释放。比如调用CGImageCreate不调用CGImageRelease;
循环引用。
NSTimer
有些注册未解开注册
子线程未关闭runlooper或者有死循环,死锁等让该关闭的线程关闭不了
CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
检测循环引用
静态代码分析。 通过Xcode->Product->Anaylze分析结果来处理;
动态分析。用MLeaksFinder(只能检测OC泄露)或者Instrument或者OOMDetector(能检测OC与C++泄露)。
对于视图或者图层来说,frame是一个虚拟属性,是根据bounds,position和transform计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响到他们当中的值。
https://zsisme.gitbooks.io/ios-/content/chapter3/layout.html
为什么圆角和透明触发离屏渲染:
当App需要进行额外的渲染和合并时,例如按钮设置圆角,我们是需要对UIButton这个控件中的所有图层都进行圆角+裁剪,然后再将合并后的结果存入帧缓存区,再从帧缓存中取出交由屏幕显示,这时,在正常的渲染流程中,我们是无法做到对所有图层进行圆角裁剪的,因为它是用一个丢一个。所以我们需要提前将处理好的结果放入离屏缓冲区,最后将几个图层进行叠加合并,存放到站缓冲区,最后屏幕上就是我们想实现的效果。
super class是怎么调用的
super 不同于 self,它不是个对象,而是个 flag
用于 objc_msgSendSuper 的结构体 objc_super 是编译时确定的,里面包含了当前类的父类信息
[super someMethod] 会去利用结构体中的父类信息,从这个父类开始顺着继承链向上查找,直到找到第一个实现 - someMethod 这个方法的类
找到方法后,利用结构体中的 receiver,也就是一开始触发这个方法调用的实例,调用这个方法实现。
所以说,[super class] 经过这么一大圈的转换,实际上变成了 [self class] 了。
weak的实现
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。
1、初始化时:runtime初始化一个新的weak指针指向对象的地址。
2、添加引用时:更新指针指向,创建对应的弱引用表。
3、释放时,首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除。
静态链接
静态链接:每一个.c的代码源文件可以被理解成一个模块,每一个模块独立编译,再把所有编译完的文件链接起来,这个过程就是我们所说的静态链接。 静态库: 例如.a和.framework。静态库链接时,会被完整地复制到可执行文件中,被使用到了多次,就会复制多份,这样就有多份拷贝很冗余,链接时间长了,还浪费了内存空间。
静态链接流程:
1.空间与地址分配
每个单独的文件编译后都会生成一个符号表,静态链接后这些表会被合并成一个全局符号表。合并的规则是相似段合并、数据段与数据段合并、代码段与代码段合并。
合并后每一个符号的的地址被确定,并写入全局符号表中。
2.重定位符号
经过空间与地址分配之后代码段中指令用到的符号地址还没有更新,想要确定符号的地址需要用到重定位表。编译后.o文件中需要重定位的符号的相关信息会存入重定位表中。
动态链接
程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时才被载入。:
共享内存,节省资源,同一份库可以被多个程序使用;减少打包之后的 app 的大小;
CoreGraphics,和CoreAnimation
为什么必须在主线程操作UI
UIKit并不是一个线程安全的类,UI操作涉及到渲染访问各种View对象的属性,如果异步操作下会存在读写问题,
而为其加锁则会耗费大量资源并拖慢运行速度。
另一方面因为整个程序的起点UIApplication是在主线程进行初始化,所有的用户事件都是在主线程上进行传递(如点击、拖动),所以view只能在主线程上才能对事件进行响应。
而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上 同时 更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。
省电
测试工具:xcode自带工具:Energy Impact。可以图形直观展示CPU,GPU,定位,网络,后台,前台等耗电占比。
省电的方案:
识别:想清楚你需要app在特定时刻需要完成哪些工作,如果是不必要的工作,考虑延后执行或者省去。
优化:优化app的功能实现,尽可能以更有效率的方式去完成功能。
合并:不需要立刻获取,可以延后合并执行,比如合并网络
减少:在满足需求的基础上,尽量减少做重复工作的频率。
耗电大户:
网络:应减少数据传输,合并网络请求,适当的网络延时等
定位:精度越高,定位时间越长,越耗电
CPU:
GPU:UI不可见时,应该避免更新其内容
传感器和蓝牙: 传感器数据应按需获取,用完即停;蓝牙应该尽量分批、减少数据轮询等操作
帧率FPS
代码中检测:CADisplayLink
Xcode检测工具:Core Animation
主线程卡顿监测: 通过开辟一个子线程来监测主线程的RunLoop,当两个状态区域的耗时大于设定的阈值时,即为一次卡顿。
判断是否实现协议
conformsToProtocol
判断有没有实现dalegate的某个方法
respondsToSelector
通知
postNotification是同步方法。当调用addObserver方法监听通知,然后调用postNotification抛通知,postNotification会在当前线程遍历所有的观察者,然后依次调用观察者的监听方法,调用完成后才会去执行postNotification后面的代码。
实现异步的通知使用:addObserverForName:object:queue:usingBlock来实现异步通知。
监测野指针
xcode Run配置打开 内存涂鸦(Malloc Scribble),将释放的内存进行涂鸦成固定值,导致使用野指针的时候一定crash
xcode Run配置打开 僵尸对象,僵尸对象替换对象的dealloc方法,如果调用已经dealloc过后的对象抛出异常
iOS常见崩溃以及总结
常见崩溃
非法参数
数组越界
KVO 重复一处观察者,没有实现observeValueForKeyPath:
kvc
对象接收到未知的消息
signal 信号量崩溃
捕获crash
捕获oc崩溃:NSSetUncaughtExceptionHandler 。如下
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数,处理Signal层面的crash。
例如如下
signal(SIGHUP, SignalExceptionHandler);
signal(SIGINT, SignalExceptionHandler);
signal(SIGQUIT, SignalExceptionHandler);
crash保护
hook相关的方法,用trycatch保护,增加保护机制。
调用方法前,判断 respondsToSelector
崩溃分析方法
用xcode查看崩溃
xcode->Window->Organizer->Crashes
在Archive的时候会生成.xcarchive文件,然后显示包内容就能够在里面找到.dsYM文件。用友盟.dsYM分析,选中UUID,输入崩溃地址。
** 路由方案 **
url 路由
target-action方案:
给组件封装一层target对象来对外提供服务,然后调用者通过依赖中间件来使用服务;其中,中间件是通过runtime来调用组件的服务,是真正意义上的解耦,也是该方案最核心的地方。不会对原来组件造成入侵;然后,通过实现中间件的category来提供服务给调用者,这样使用者只需要依赖中间件,而组件则不需要依赖中间件。每个category对应一个Target,Categroy中的方法对应Action场景
protocol-class
就是通过protocol定义服务接口,组件通过实现该接口来提供接口定义的服务,具体实现就是把protocol和class做一个映射,同时在内存中保存一张映射表,使用的时候,就通过protocol找到对应的class来获取需要的服务。
ios的导航设计模式
平铺导航( UITabbarController )
标签导航( UINavigationController )
树形导航(UIPageViewController)
属性修饰符
可以用strong和retain修饰同一个属性
但是不能用strong和copy修饰同一属性,运行会报错
viewDidUnload在这里插入代码片
是ios6后舍弃的 vc销毁时回调的生命周期
WKWebview ios和js通信
ios调用js,在ios中:
[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable title, NSError * _Nullable error) {
NSLog(@"Hello, %@", title);
}];
js调用ios
在ios中注册回调
KWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
//声明 hello message handler 协议
[config.userContentController addScriptMessageHandler:self name:@"hello"];
//定义 Message Handler 处理方法
- (void)userContentController:(WKUserContentController *)userContentController{}
在js中调用:
window.webkit.messageHandlers.hello.postMessage();
真机调试前的准备
创建登录开发者账号
在本地用钥匙串创建csr文件
进入证书管理页面,创建证书,上传csr文件,生成cer证书,下载这个证书
双击下载证书,安装到电脑的钥匙串
创建appid
添加允许调试的设备的UDID
创建PP文件:选择真机调试配置文件,填入appid,真机调试证书,最后生成并下载mobileprovision文件
双击pp文件,安装到xcode上
xcode中Bundle identifier设置成appid,配置选择pp文件,配置cer证书