本文从以下几点整理iOS基础的相关知识
一、应用的生命周期
二、ViewController的生命周期
三、修饰符的使用和区别
四、多线程技术
五、事件的传递和响应链
六、Notification
七、Block
八、Delegate
九、TableView优化方案
十、Runtime
十一、Runloop
十二、OC与JS之间的互相调用和通信方案
十三、静态库的打包和集成
十四、Crash日志的收集和分析
一、应用的生命周期
Main函数入口
UIApplicationMain
- 从可用Storyboard文件加载用户界面
- 调用AppDelegate自定义代码来初始化设置
- 将app放入Main Run Loop环境中来响应和处理与用户交互产生的事件
应用程序的五种状态
- Not Running(非运行状态)
应用没有运行或被系统终止。 - Inactive(前台非活动状态)
应用正在进入前台状态,但是还不能接受事件处理。 - Active(前台活动状态)
应用进入前台状态,能接受事件处理 - Background(后台状态)
应用进入后台后,依然能够执行代码,如果有可执行的代码,就会执行代码,如果没有可执行的代码活着将可执行的代码执行完毕,应用会马上进入挂起状态。有的程序经过特殊的请求后可以长期处于Background状态。 - Suspended(挂起状态)
处于挂起的应用进入一种“冷冻”状态,不能执行代码,如果系统内存不够,系统就把挂起的程序清除掉,为前台程序提供更多的内存,应用会被终止。
AppDelegate的代理方法
作为应用程序的委托对象,AppDelegate类在应用生命周期的不同阶段会调用不同的方法。
方法 | 本地通知 | 说明 |
---|---|---|
application: didFinishLaunchingWithOptions: | UIApplicationDidFinishLaunching-Notification | 应用启动并进行初始化时会调用该方法并发出通知。这个阶段会示例化根视图控制器 |
application: DidBecomeActive: | UIApplicationDidBecomeActive-Notification | 应用进入前台并处于活动状态时调用该方法并发出通知,这个阶段可恢复UI的状态(例如游戏状态等) |
application:WillResignActive: | UIApplicationWillResignActive-Notification | 应用从活动状态进入到非活动状态时调用该方法并发出通知。这个阶段可以保存UI的状态(例如游戏状态等) |
application:DidEnterBackground: | UIApplicationDidEnterBcakground-Notification | 应用进入后台时调用该方法并发出通知。这个阶段可以保存用户数据,释放一些资源(例如释放数据库资源等) |
application:WillEnterForeground: | UIApplicationWillEnterForeground-Notification | 应用进入到前台,但是还没有处于活动状态时调用该方法并发出通知。这个阶段可以恢复用户数据 |
application:WillTerminate: | UIApplicationWillTerminate-Notification | 应用被终止时调用该方法并发出通知,但内存清除时除外。这个阶段可以释放一些数据,也可以保存用户数据。 |
二、ViewController的生命周期
1.init
init函数并不会每次创建对象都调用,只有在这个类第一次创建对象时才会调用,做一些类的准备工作,再次创建这个类的对象,initalize方法将不会被调用,对于这个类的子类,如果实现了initialize方法,在这个子类第一次创建对象时会调用自己的initalize方法,之后不会调用,如果没有实现,那么它的父类将替它再次调用一下自己的initialize方法,以后创建也都不会再调用。因此,如果我们有一些和这个相关的全局变量,可以在这里进行初始化。
2. initCoder
initCoder方法和init方法相似,只是被调用的环境不一样,如果用代码进行初始化,会调用init,从nib文件或者归档进行初始化,会调用initCoder。
3. loadView
loadView方法是开始加载视图的起始方法,除非手动调用,否则在ViewController的生命周期中没特殊情况只会被调用一次。
4. viewDidLoad
viewDidLoad方法是我们最常用的方法的,类中成员对象和变量的初始化我们都会放在这个方法中,在类创建后,无论视图的展现或消失,这个方法也是只会在将要布局时调用一次。
5. viewWillAppear
视图将要展现时会调用。
6. viewWillLayoutSubviews
在viewWillAppear后调用,将要对子视图进行布局。
7. viewDidLayoutSubviews
已经布局完成子视图。
8. viewDidAppar
视图完成显示时调用。
9. viewWillDisappear
视图将要消失时调用。
10. viewDidDisappear
视图已经消失时调用。
11. dealloc
controller被释放时调用。
三、修饰符的使用和区别
在ARC环境下,修饰符有assign,strong,weak,copy,readwrite,readonly,nonatomic,atomic
属性修饰符对引用计数(retainCount)的影响
- alloc为对象分配内存,retainCount为1;
- retain,retainCount+1;
- copy一个对象变成另一个对象,retainCount为1,原有对象计数不变;
- release,etainCount-1;
- autorelease,retainCount-1。
assign、retain、copy分别对应的setter方法
// assign
- (void)setAssignObject:(id)newValue {
assignObject = newValue;
}
// retain
- (void)setRetainObject:(id)newValue {
if (retainObject != newValue) {
[retainObject release];
retainObject = [newValue retain];
}
}
// copy
- (void)setCopyObject:(id)newValue {
if (copyObject != newValue) {
[copyObjectrelease];
copyObject = [newValuecopy];
}
}
nonatomic和atomic的区别
atomic为默认属性,多线程安全,这个属性是为了保证程序在多线程情况下,编译器自动生成一些互斥加锁代码,避免该变量的读写不同步问题,对性能的消耗非常大;
nonatomic:禁止多线程,变量保护,提高性能。如果该对象无需考虑多线程的情况,建议加入此属性,这样会让编译器少产生一些互斥加锁代码,可以在一定程度上提高效率。
readwrite和readonly的区别
readonly表示这个属性是只读的,就是只生成 getter方法,不会生成setter方法。
readwrite表示可读写,生成getter和setter方法。
weak和strong的区别
默认情况下,一个指针都会使用strong属性,表明这是一个强引用。这意味着,只要引用存在,对象就不能被销毁。
weak是弱引用,使用weak修饰主要是为了解决循环引用的问题。
- 在修饰代理属性的时候使用weak;
- 通过连线方式创建控件时用weak;
- 在block中,为避免循环引用,使用weak修饰self,__weak typeof(self) weakSelf = self。
copy和strong的区别
当源字符串是NSString时,由于字符是不可变的,不论strong还是copy属性,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生了一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy的属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。这里strong属性只是单纯增加对象的引用计数,而copy操作则进行了一次深拷贝,所以性能上有所差异。
assign和weak的区别
assign适用于基本数据类型,weak适用于NSObject对象,并且是弱引用。当assign用来修饰对象时容易造成野指针,对象一般会分配在堆上的某块内存,如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃。
之所以assign可以用来修饰基本数据类型,是因为基础数据类型一般分配在栈上,栈的内存会由系统自己自动处理,不会造成野指针。
weak修饰的对象在被释放以后,指针地址会被置为nil,所以现在一般弱引用就是用weak。
block变量定义时用copy
block的循环引用并不是strong导致的,在ARC环境下,系统底层也会做一次copy操作使block从栈区复制一块内存空间到堆里,所以strong和copy在对block修饰上没有本质区别,只不过copy操作的效率高而已,一般用copy来修饰block。
四、多线程技术
多线程(multithreading)
是指软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
原理
-
同一时间,CPU只能处理一条线程,只有一条线程在工作。
-
多线程并发执行,其实是CPU快速地在多线程之间调度(切换)。
-
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象(CPU并不是真正的同时执行多个任务)。
多线程技术方案对比
多线程技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 一套通用的多线程API;适用于Unix/Linux/Windows等系统;跨平台,可移植;使用难度大 | C | 程序员管理 | 几乎不用 |
NSThread | 使用更加面向对象;简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
GCD | 旨在替代NSThread等线程技术;充分利用设备的多核 | C | 自动管理 | 经常使用 |
NSOperation | 基于GCD(底层是GCD);比GCD多了一些简单实用的功能;使用更加面向对象 | OC | 自动管理 | 经常使用 |
多线程技术方案实现
1. NSThread
(1)使用NSThread对象建立一个线程非常方便;
(2)使用NSThread管理多个线程非常困难,不推荐使用;
(3)使用[NSThread currentThread]获得任务所在线程,适用于这三种技术。
2. GCD(Grand Central Dispatch)
(1)是基于C语言的底层API,可用于多核并行计算;
(2)用Block定义任务,使用起来非常灵活,GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
(3)提供了更多的控制能力以及操作队列中所不能使用的底层函数。
任务与队列
- 任务
即需要执行的操作,在GCD中任务是放在block里。执行的方式有两种:同步(sync)与异步(async),两者的主要区别在于是否具备开启新线程的能力。
- 同步(sync)
同步添加任务到指定的队列中,在队列中的正在执行的任务执行结束之前,会一直等待,直到该任务在所在队列中优先级高于本身的任务执行完毕之后才能开始执行。
不具备开启新线程的能力。
- 异步(async)
异步添加任务到指定的队列中,无需等待即可直接执行。
具备开启新线程的能力。
- 队列
即执行任务的等待队列,用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则。
在GCD中队列分为串行队列(Serial Dispatch Queue)和并行队列(Concurrent Dispatch Queue)两种,两者的主要区别是:执行的顺序不同,开启线程数不同。
- 串行队列
每次只有一个任务被执行,让任务一个接着一个执行。(只开启一个子线程)
- 并行队列
可以让多个任务并发执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列的并发功能只有在异步下才有效
3. NSOperation
(1)是使用GCD实现的一套Objective-C的API;
(2)是面向对象的线程技术;
(3)提供了一些GCD中不容易实现的特性,如:现在最大并发数量、操作之间的依赖关系。
五、事件传递响应链
UIResponder
在iOS中,能够响应事件的对象都是UIResponder的子类对象。UIResponder提供了四个点击的回调方法,分别对应用户点击开始、移动、结束和取消,其中只有在程序强制退出或者来电时,取消点击事件才会调用。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
1. 响应链的传递
iOS应用程序加载时会先执行main函数
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
依次加载:UIApplication->AppDelegate->UIWindow->UIViewController->superView->subviews
关系为:[UIApplication sharedApplication].keyWindow.rootViewController.view.subviews
事件的产生和传递
-
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。
-
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序主窗口(keyWindow)。
-
主窗口会在视图层次结构中找到一个最适合的视图来处理当前触摸事件,这也是整个事件处理过程的第一步。
-
找到合适的视图控件后,就会调用视图控件的touches方法来做具体事件的处理。
2. Hit-Testing机制
iOS使用Hit-Testing寻找触摸的view。 Hit-Testing通过检查触摸点是否在关联的view边界内,如果在,则递归地(recursively)检查该view的所有子view。在层级上处于最近且边界范围包含触摸点的view成为hit-test view。确定hit-test view后,它传递触摸事件给该view。
重写hitTest
方法
- (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;
}
响应链的事件传递过程
-
如果当前view的控制器存在,就传递给控制器;如果控制器不存在,则将其传给它的父视图。
-
在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,此时会将事件或消息传递给window对象进行处理。
-
如果window对象也不处理,则将事件或消息传递给UIApplication对象。
-
如果UIApplication也不能处理该事件或消息,则将其丢弃。
六、Notification Center
概念
通过通知中心向所有注册观察者广播的信息容器
它是一个单例对象,允许当事情发生时通知一些对象,让对象作出相应反应。
它允许我们在低程度耦合的情况下,满足控制器与一个任意的对象进行通信的目的。
这种模式的基本特征是为了让其他的对象能够接收到某种事件传递过来的通知,主要使用通知名称来发送和接收通知。
基本上不用考虑其他影响因素,只需要使用同样的通知名称,监听该通知的对象(即观察者)再对通知做出反应即可。
优缺点
1.优点
-
代码简短,实现简单;
-
对于一个发出的通知,多个对象能够做出反应,简单实现一对多的方式,较之于Delegate可以实现更大跨度的通信机制;
-
能够通过object和userInfo传递参数,object和userInfo可以携带发送通知时传递的信息。
2.缺点
-
在编译期间不会检查通知是否能够被观察者正确处理;
-
在释放通知的观察者时,需要在通知中心移除观察者;
-
在调试的时候通知传递的过程很难控制和跟踪;
-
发送通知和接收通知的时候必须保持通知名称一致;
-
通知发出后,不能从观察者获得任何反馈信息。
使用方式
1.不传参数
发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"notificationWithoutParameter" object:nil];
监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiWithoutParam) name:@"notificationWithoutParameter" object:nil];
调用方法
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiWithoutParam) name:@"notificationWithoutParameter" object:nil];
2.使用object传递参数
发送通知
NSString *object = @"notiObject";
[[NSNotificationCenter defaultCenter] postNotificationName:@"notificationWithObject" object:object];
监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiWithObject:) name:@"notificationWithObject" object:nil];
调用方法
- (void)notiWithObject:(NSNotification *)notiication {
id object = notiication.object;
NSLog(@"Received the notification with object");
}
3.使用userInfo传递参数
发送通知
NSDictionary *userInfoDict = @{@"userInfo":@"130293739"};
[[NSNotificationCenter defaultCenter] postNotificationName:@"notificationWithUserInfo" object:nil userInfo:userInfoDict];
监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiWithUserInfo:) name:@"notificationWithUserInfo" object:nil];
调用方法
- (void)notiWithUserInfo:(NSNotification *)notiication {
NSDictionary *userInfo = notiication.userInfo;
NSLog(@"Received the notification with userInfo:%@",userInfo);
}
网友评论