前言
如有错误欢迎大家指点。
1、 APP 程序启动流程(原理)
启动过程和原理,这可以说是一个 老生常谈的问题啦。简单说明一下吧。
第一:当启动App的时候,系统会调用Main方法,这个方法的意义就是告诉App在整个运行的过程中都要遵守AppDelegate这个协议。
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
第二:接着会建立应用程序的Main Runloop(事件循环),进行事件的处理(首先会在程序启动完毕后调用delegate对象的application:didFinishLaunchingWithOptions:方法)
程序正常退出时UIApplicationMain函数才返回.
第三:创建AppDelegate监听。
// 当应用程序启动完毕的时候就会调用(系统自动调用)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// 即将失去活动状态的时候调用(失去焦点, 不可交互)
- (void)applicationWillResignActive:(UIApplication *)application
// 重新获取焦点(能够和用户交互)
- (void)applicationDidBecomeActive:(UIApplication *)application
// 应用程序进入后台的时候调用
// 一般在该方法中保存应用程序的数据, 以及状态
- (void)applicationDidEnterBackground:(UIApplication *)application
// 应用程序即将进入前台的时候调用
// 一般在该方法中恢复应用程序的数据,以及状态
- (void)applicationWillEnterForeground:(UIApplication *)application
// 应用程序即将被销毁的时候会调用该方法
// 注意:如果应用程序处于挂起状态的时候无法调用该方法
- (void)applicationWillTerminate:(UIApplication *)application
// 应用程序接收到内存警告的时候就会调用
// 一般在该方法中释放掉不需要的内存
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
第四:创建UIWindows
在AppDelegate的 didFinishLaunchingWithOptions中创建一个UIWindow,并创建其根视图控制器,然后显示。这个时候如果plist 文件中定义好了SB文件,就优先加载SB文件。
// Override point for customization after application launch.
//设置主目录
_window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
//设置根控制器
VSMainTabVC *mainTabVC = [[VSMainTabVC alloc]init];
_window.rootViewController = mainTabVC;
//设置Show
[self.window makeKeyAndVisible];
return YES;
2 、ARC详解
ARC其实在实质上和MRC是一样的,还是通过应用计数法去实现,只是一个 需要手动书写release一个不用而已。
要理解ARC 我们就先来理解一下ARC下的俩个属性。
strong 对象的持有者
例如:
self.textField.text = @"OneV";
NSString *firstName = self.textField.text;
这个时候在 堆内存中 存在着一个字符串 @"OneV",有俩个强指针指向他。如下图
如果这个时候 self.textField.text变成了 @"OneVCat",那么 firstName还是会依然指向 @"OneV"
这就是强指针。
只有当firstName也被设定了新的值,或者是超出了作用范围的空间(比如它是局部变量但是这个方法执行完了或者它是实例变量但是这个实例被销毁了),那么此时firstName也不再持有@“OneV”,此时不再有指针指向@”OneV”,在ARC下这种状况发生后对象@”OneV”即被销毁,内存释放。
总结
如果把堆内存比喻成一只🐶,那么一条🐶(也就是一个内存地址),可以让很多人(指针)牵着。如果一个其中一个人放手去牵了例外一条狗,那么另外一个人还是可以安安稳稳的牵着。如果大家都放手了,狗就会逃跑(也就是内存释放)。
weak 指向对象
同样的例子
self.textField.text = @"OneVcat";
__weak NSString *weakName = self.textField.text;
这个时候就相当于:
这里声明了一个weak的指针weakName,它并不持有@“onevcat”。如果self.textField.text的内容发生改变的话,根据之前提到的“只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁”**原则,此时指向@“onevcat”的指针中没有strong类型的指针,@”onevcat”将被销毁。同时,在ARC机制作用下,所有指向这个对象的weak指针将被置为nil。这个特性相当有用,相信无数的开发者都曾经被指针指向已释放对象所造成的EXC_BAD_ACCESS(用到的地址提前被释放)困扰过,使用ARC以后,不论是strong还是weak类型的指针,都不再会指向一个dealloced的对象,从根源上解决了意外释放导致的crash。
weak指针的出现,在一定程度上是为了解决 循环应用的问题。所以delegate一例要用weak。
总结
如果内存地址是一条🐶,strong就相当于拿着绳子牵着狗的人,而weak呢 就相当于在旁边弱弱的看着狗的人。如果牵着狗的人还在的话,那么看的人 依然可以看到狗。而如果 牵的人放手了,狗跑了(内存释放了),这个时候看的人就无法看到这条狗(也就是无法引用这块内存地址,也就是指向nil)。
3 、代理遇到过循环引用
如果代理方法不设置成 weak(ARC)或者assign(MRC),就会产生循环引用,这个时候会产生内存泄漏,久而久之是一件很可怕的事情。
4 、内存警告的时候怎么做
什么是内存警告
ios下每个app可用的内存是被限制的,如果一个app使用的内存超过了这个阀值,则系统会向该app发送Memory Warning消息。收到消息后,app必须尽可能多的释放一些不必要的内存,否则OS会关闭app。
处理方法
在didReceiveMemoryWarning方法中 ,释放没有正在展示的页面。
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];//即使没有显示在window上,也不会自动的将self.view释放。注意跟ios6.0之前的区分
// Add code to clean up any of your own resources that are no longer necessary.
// 此处做兼容处理需要加上ios6.0的宏开关,保证是在6.0下使用的,6.0以前屏蔽以下代码,否则会在下面使用self.view时自动加载viewDidUnLoad
if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {
//需要注意的是self.isViewLoaded是必不可少的,其他方式访问视图会导致它加载,在WWDC视频也忽视这一点。
if (self.isViewLoaded && !self.view.window)// 是否是正在使用的视图
{
// Add code to preserve data stored in the views that might be
// needed later.
// Add code to clean up other strong references to the view in
// the view hierarchy.
self.view = nil;// 目的是再次进入时能够重新加载调用viewDidLoad函数。
}
}
}
5、 TableViewCell 复用
tableView 是一个很高效的控件,很大一部分原因就是因为他的复用机制。
那什么是tableView的复用机制呢????
例如我们有100个Cell要展示,而屏幕就那么长,假如一个屏幕最多能展示10个,那么如果我们要alloc 100 Cell来展示是不是很耽误内存呢。当然苹果公司是很屌的,想到了一个解决办法。就是 当我们要展示地11个Cell的时候 那么我们的第一个Cell也就离开了页面,那么我们的地11个Cell就可以直接拿第一个Cell来用。以此类推。
先上代码看看:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
跟新Cell的数据
return Cell;
而在实现上,其实是这么做的。在tableView内存有一个缓存池,里面放着Cell,如果我要用到Cell。第一步是到 缓存池里取,如果没有那么 再alloc一个。当Cell离开 屏幕的时候 把Cell加入到缓存池中,每一个Cell都按自己的identifier分类。
当你取到Cell的时候,设置其上面的数据即可。
这就是复用机制。
6 Categroy 为何物, Category 与继承的区别
分类是一种为现有的类添加新方法的方式。
利用Objective-C的动态运行时分配机制,Category提供了一种比继承(inheritance)更为简洁的方法来对class进行扩展,无需创建对象类的子类就能为现有的类添加新方法,可以为任何已经存在的class添加方法,包括那些没有源代码的类(如某些框架类)。
类别的作用
类别主要有3个作用:
(1)可以将类的实现分散到多个不同文件或多个不同框架中,方便代码管理。也可以对框架提供类的扩展(没有源码,不能修改)。
(2)创建对私有方法的前向引用:如果其他类中的方法未实现,在你访问其他类的私有方法时编译器报错这时使用类别,在类别中声明这些方法(不必提供方法实现),编译器就不会再产生警告
(3)向对象添加非正式协议:创建一个NSObject的类别称为“创建一个非正式协议”,因为可以作为任何类的委托对象使用。
7 响应链
touches方法 默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理即如果 有实现touches方法优先实现 第一响应者,但是如果没有实现 那就交给它的上一个事件响应者。
最合适的控件来处理:
1 是否能接受touches事件
2 触摸点是否在自己身上
3 从后往前遍历控件 并判断 是否符合 1 和 2
如果父控件不能接收触摸事件,那么子控件就不可能接收到touches事件
响应者链条 -> 由很多响应者链条在一起组合起来的一个链条称之为响应者链条
响应者 -> 继承UIResponder的对象 称之为 响应者对象
上一个响应者是谁
-> 1 判断当前是否是控制器的View,如果是 控制器的View 上一个响应者就是控制器
-> 2 如果当前不是控制器的View,上一个响应者就是 父控件
简单的说
if(当前响应者实现了 我的方法)
{
return;
}
else
{
检查父控件是否实现 一直递归
}
一次完整的touches 事件 传递响应过程
响应者链条有什么用
使父控件响应事件
[super touchesbegan:touches withEvent:event];
可以让一个触摸事件发生的时候 让多个响应者同时响应事件
8 存储的方式
存储方式大致分以下几种
plist文件(属性列表)
preference(偏好设置)
NSKeyedArchiver(归档)
SQLite 3
CoreData
plist文件
plist文件是将某些特定的类,通过XML文件的方式保存在目录中。
可以被序列化的类型只有如下几种:
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate
总结:数组,字典,二进制数据,字符串,数字,时间
步骤
获得文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
存储
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];
读取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);
注意
只有以上列出的类型才能使用plist文件存储。
存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。
读取时使用arrayWithContentsOfFile:方法。
Preference
使用方法
//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
注意
偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。
如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
NSKeyedArchiver
归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。
遵循NSCoding协议
NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。
遵循协议和设置属性
//1.遵循NSCoding协议
@interface Person : NSObject //2.设置属性
@property (strong, nonatomic) UIImage *avatar;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
实现协议方法
//解档
- (id)initWithCoder:(NSCoder *)aDecoder {
if ([super init]) {
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
使用
需要把对象归档是调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法。
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
需要从文件中解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可。
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
self.avatarView.image = person.avatar;
self.nameField.text = person.name;
self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}
注意
必须遵循并实现NSCoding协议
保存文件的扩展名可以任意指定
继承时必须先调用父类的归档解档方法
SQLite3
这个不多说。