iOS开发中基础性问题解答

前言

如有错误欢迎大家指点。

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;
启动过程图解.png

2 、ARC详解

ARC其实在实质上和MRC是一样的,还是通过应用计数法去实现,只是一个 需要手动书写release一个不用而已。
要理解ARC 我们就先来理解一下ARC下的俩个属性。

strong 对象的持有者

例如:

   self.textField.text = @"OneV";
   NSString *firstName = self.textField.text; 

这个时候在 堆内存中 存在着一个字符串 @"OneV",有俩个强指针指向他。如下图

内存示例1.png

如果这个时候 self.textField.text变成了 @"OneVCat",那么 firstName还是会依然指向 @"OneV"

内存示例2.png

这就是强指针。
只有当firstName也被设定了新的值,或者是超出了作用范围的空间(比如它是局部变量但是这个方法执行完了或者它是实例变量但是这个实例被销毁了),那么此时firstName也不再持有@“OneV”,此时不再有指针指向@”OneV”,在ARC下这种状况发生后对象@”OneV”即被销毁,内存释放。


内存示例3.png

总结

如果把堆内存比喻成一只🐶,那么一条🐶(也就是一个内存地址),可以让很多人(指针)牵着。如果一个其中一个人放手去牵了例外一条狗,那么另外一个人还是可以安安稳稳的牵着。如果大家都放手了,狗就会逃跑(也就是内存释放)。

weak 指向对象

同样的例子

   self.textField.text = @"OneVcat";
   __weak NSString *weakName = self.textField.text; 

这个时候就相当于:


内存示例4.png

这里声明了一个weak的指针weakName,它并不持有@“onevcat”。如果self.textField.text的内容发生改变的话,根据之前提到的“只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁”**原则,此时指向@“onevcat”的指针中没有strong类型的指针,@”onevcat”将被销毁。同时,在ARC机制作用下,所有指向这个对象的weak指针将被置为nil。这个特性相当有用,相信无数的开发者都曾经被指针指向已释放对象所造成的EXC_BAD_ACCESS(用到的地址提前被释放)困扰过,使用ARC以后,不论是strong还是weak类型的指针,都不再会指向一个dealloced的对象,从根源上解决了意外释放导致的crash。

内存示例5.png

weak指针的出现,在一定程度上是为了解决 循环应用的问题。所以delegate一例要用weak。

总结

如果内存地址是一条🐶,strong就相当于拿着绳子牵着狗的人,而weak呢 就相当于在旁边弱弱的看着狗的人。如果牵着狗的人还在的话,那么看的人 依然可以看到狗。而如果 牵的人放手了,狗跑了(内存释放了),这个时候看的人就无法看到这条狗(也就是无法引用这块内存地址,也就是指向nil)。

3 、代理遇到过循环引用
如果代理方法不设置成 weak(ARC)或者assign(MRC),就会产生循环引用,这个时候会产生内存泄漏,久而久之是一件很可怕的事情。


循环引用.png

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 事件 传递响应过程

响应者链条.png

响应者链条有什么用
使父控件响应事件
[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
这个不多说。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,139评论 30 470
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,304评论 0 6
  • http://www.devstore.cn/essay/essayInfo/6525.html【原文地址】 序言...
    起名好难_fz阅读 645评论 1 1
  • 转自 iOS 面试常见问题最全梳理 序言 目前形势,参加到iOS队伍的人是越来越多,甚至已经到供过于求了。今年,找...
    ZMJun阅读 798评论 0 11
  • 2017.8.28今天晚上闺女从托辅回来高高兴兴滴又唱又跳的,我问她干嘛这么高兴,她说妈妈我觉得上托辅挺好的,再也...
    瑜珊妈妈阅读 202评论 0 0