我的iOS开发之旅

你若安好,便是晴天☀️

MVVM框架学习实践

1、mvvm基本概念

   在MVC框架的基础之上,将view和view controller联系在一起作为一个整体,同时新建一个视图模型层view model,负责view和view controller这个整体与model层之间的通信。同时将原来ViewController中放置用户输入验证逻辑、视图显示逻辑、发起网络请求和其他各种各样的耗时操作放到 view model中。在MVVM框架的基础之上,再结合RAC响应式编程,通过在控制器层对模型层数据的绑定,来实现界面与模型层之间的数据交换。

2、mvvm层次结构

     MVVM主要分为一下几层,
     model层:数据模型层
     view(view和view controller)层:显示自定义视图和和管理视图的控制器层
     view model层:连接model层和view层之间的桥梁,主要负责一些业务逻辑操作

3、mvvm使用场景

(1)结合RAC简化自定义view与model层之间数据交互在控制器中使用RAC、RACObserver宏对view中要展示数据与view model层获取的model层数据进行绑定。
  使用RACCommad来响应界面的交互,具体步骤如下:
  第一步:在自定义view中定义一个command,在子view点击操作中执行这个command,excute:可以看看官网文档上它的用法

  第二步:在controlller中,把它与自定义view中的相应command进行绑定。在绑定的时候就实现这个commad的具体操作。

  第三步:也可以在controlller中定义这个commad属性。在属性的set方法中返回它的具体实现,最后不能忘记还要对这个commad与自定义view中的相应command进行绑定。

  第四步:commad的执行一般分以下几种状态:正在执行、执行失败、执行完(可以返回一个数据,但是一般通过修改某个与view进行绑定的数据之后,就可以自动通知view界面进行修改了,所有不返回数据也是可以的)

  (2)将UITableView上的数据源(实现<UITableViewDataSource>协议及其方法的类)分离出来,放到vm层。这样使得代码逻辑更清晰,且更有利于做单元测试。如果设置M层,还可以对代码做进一步的改进。
               实例代码如下所示:

VM层

//声明一个block类型
typedef void (^TableViewCellConfigureBlock)(id cell, id item);
 //声明TableDataSource 类,实现UITableViewDataSource协议
@interface TableDataSource : NSObject <UITableViewDataSource>
 //定义一个实例化方法,设置数组、重用标识符,并调用相应block
- (instancetype)initWithItems:(NSArray *)anItems
           CellIdentifier:(NSString *)aCellIdentifier
       ConfigureCellBlock:(TableViewCellConfigureBlock)aConfigureCellBlock;
 //根据indexPath返回item数组中的相应值
- (id)itemAtIndexPath:(NSIndexPath *)indexPath; 
@end



#import “TableDataSource.h”
@interface TableDataSource ()
 //存放数据的数组
@property (nonatomic, strong) NSArray  *items;
 //重用标识符
@property (nonatomic, copy) NSString *cellIdentifier;
 //block对象
 @property (nonatomic, copy) TableViewCellConfigureBlock configureCellBlock;
 @end

 @implementation TableDataSource
 //自动生成get和set方法
 @synthesize items = _items;
 @synthesize cellIdentifier = _cellIdentifier;
 @synthesize configureCellBlock = _configureCellBlock;



 - (id)init {
return nil;
 }
- (instancetype)initWithItems:(NSArray *)anItems
           CellIdentifier:(NSString *)aCellIdentifier
       ConfigureCellBlock:(TableViewCellConfigureBlock)aConfigureCellBlock
 {
self = [super init];
if (self) {
    self.items              = anItems;
    self.cellIdentifier     = aCellIdentifier;
    self.configureCellBlock = [aConfigureCellBlock copy];
}
return self;
 }


- (id)itemAtIndexPath:(NSIndexPath *)indexPath {
return _items[(NSUInteger)indexPath.row];
}

pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
 }

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_items count];
 }

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellIdentifier];
id item = [self itemAtIndexPath:indexPath];
 if (!cell) {
     cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_cellIdentifier];
 }
//回调block。这个block是传进来的,所有实际是执行外面的代码
self.configureCellBlock(cell, item);
return cell;
}

@end

V层

 #import "TableViewController.h"
 #import "TableDataSource.h"
 static NSString * const kCellIdentifier = @"Cell";
 @interface TableViewController ()//定义一个实现UITableViewDataSource协议的类的属性。用于给tableview设置数据源
 @property (strong, nonatomic) TableDataSource *dataSource;
 @end

 @implementation TableViewController
 //自动生成get和set方法
 @synthesize dataSource = _dataSource;
- (void)viewDidLoad
{
[super viewDidLoad];
TableViewCellConfigureBlock cellConfigureBlock = ^(UITableViewCell *cell, NSString *item) {
    cell.textLabel.text = item;
};
NSArray *stringsArray = @[@"1", @"2", @"3", @"1", @"2", @"3", @"1", @"2", @"3", @"1", @"2", @"3", @"1", @"2", @"3", @"1", @"2", @"3", @"1", @"2", @"3", @"1", @"2", @"3"];
self.dataSource = [[TableDataSource alloc] initWithItems:stringsArray
                                          CellIdentifier:kCellIdentifier
                                      ConfigureCellBlock:cellConfigureBlock];
self.tableView.dataSource = _dataSource;
 }

@end

(3)下面的代码将用户登录的验证代码分离出来,但是应该不是完全的mvvm的模式,因为还是通过block进行的回调?还要多些代码体会理解 类似VM层

#import <Foundation/Foundation.h>
//定义返回值参数的block的类型
typedef void (^RWSignInResponse)(BOOL);
@interface RWDummySignInService : NSObject
//在该方法的实现中,对传入的用户名密码进行处理,然后给block传入参数,并调用block
//定义一个包含输入参数及返回值block参数的函数。函数的返回值通过block传递出去,调用这个函数,执行block,就可以对这个返回值进行处理了。
- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock;
@end


 #import "RWDummySignInService.h"
  @implementation RWDummySignInService
 //具体实现,传入用户名和密码,调用block(设置block的参数,然后回调传入的blobk的实现代码,
 //block作为方法的参数,调用方法的时候一定要实现这个block,方法的实现是调用这个block

 - (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock {
double delayInSeconds = 2.0;
//设置执行线程之前的等待时间,将前面的double类型进行一个转换
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
//执行线程,参数popTime:延迟时间。参数dispatch_get_main_queue():获取运行在主线程的Main queue
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    BOOL success = [username isEqualToString:@"user"] && [password isEqualToString:@"password"];
    completeBlock(success);
});
 }
@end

V层

 $import <UIKit/UIKit.h>
 @interface RWViewController : UIViewController
 @end


  #import "RWViewController.h"
  #import "RWDummySignInService.h"
  #import "ReactiveCocoa/ReactiveCocoa.h"
  @interface RWViewController ()
  @property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
  @property (weak, nonatomic) IBOutlet UITextField *passwordTextField;
  @property (weak, nonatomic) IBOutlet UIButton *signInButton;
 @property (weak, nonatomic) IBOutlet UILabel *signInFailureText;
 @property (strong, nonatomic) RWDummySignInService *signInService; //处理用户点击事件的接口(信号)

 @end


 @implementation RWViewController

 - (void)viewDidLoad {
  [super viewDidLoad];
 //下面是创建(有效状态信号),理解其含义的代码
//我们可以使用map操作转换我们想要的数据,只需要它是一个对象,
RACSignal *validUsernameSignal = [self.usernameTextField.rac_textSignal map:^id(NSString *text) {
    return @(text.length > 3);
  ]);
}];
RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal map:^id(NSString *text) {
    return @(text.length > 3);
}];

//下面ReactiveCocoa定义的一些宏,感觉这个宏是把函数要返回的对象的属性都声明了,直接return就行了

RAC(self.passwordTextField, backgroundColor) = [validPasswordSignal map:^id(NSNumber *passwordValid){
    return [passwordValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}];

RAC(self.usernameTextField, backgroundColor) = [validUsernameSignal map:^id(NSNumber *usernameValid) {
    return [usernameValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}];

//一开始将下面的combinelatest的参数直接用text,一致报找不到方法的错误,然后马上跟断点进行调试。发现应该要转换成信号。才可以

RAC(self.signInButton, enabled) = [RACSignal combineLatest:@[self.usernameTextField.rac_textSignal, self.passwordTextField.rac_textSignal] reduce:^id(NSString *name, NSString *password){
    return @(name.length > 3 && password.length>3);
}];

//正确的,将按钮点击事件映射到一个登录信号,但同时通过将事件从内部信号发送到外部信号,使这个过程变得扁平化。

[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^RACStream *(id value) {
    return [self signInSignal];
}] subscribeNext:^(NSNumber *signedIn) {
    BOOL success = [signedIn boolValue];
    self.signInFailureText.hidden = YES;
    if (success) {
        [self performSegueWithIdentifier:@"signInSuccess" sender:self];
    }
    NSLog(@"sign in result :%@",signedIn);

}];


 //创建处理点击事件的接口类对象
  self.signInService = [RWDummySignInService new];
 // 初始化提示框为隐藏
  self.signInFailureText.hidden = YES;
 }

- (RACSignal *)signInSignal{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [self.signInService signInWithUsername:self.usernameTextField.text password:self.passwordTextField.text complete:^(BOOL success) {
        [subscriber sendNext:@(success)];
        [subscriber sendCompleted];
    }];
    return nil;
}];
 }
 @end

OC内存管理

1、RunTime(运行时)概念理解

(1)函数调用方式

      OC的函数调用即通过在对象上调用方法,也称之为“传递消息”,相对于C语言函数调用,使用静态绑定,即在编译期就决定运行时所应调用的函数,OC则是通过动态绑定的方式,对所要调用的函数在运行期才能确定,即对象收到消息之后,究竟该调用哪个方法完全由运行期决定,并且可以在程序运行时改变,是一门真正的动态语言。

(2)函数调用(传递消息)过程 1

       函数调用过程即给对象发送消息的处理过程,以id returnValue = [someObject measageName:pameter] 消息为例:
       编译期,编译器看到这行代码,会将其转换为一条标准的C语言函数调用,id returnValue = objc_msgSend(someObject, @selector(messageName:),  parameter);  如果在编译期向类发送了其无法解读的消息也不会报错,因为运行时可以继续向类中添加方法。

       运行期,即动态消息派发系统执行过程,objc_msgSend函数会首先根据接收者(someObject)和选择子(measageName)的类型在接收者所属的类中搜寻它的方法列表,这个方法列表其实是类里面的一张表格,表格是一个存放健值对的列表,健用选择子的名字表示,值即选择子对应方法的函数指针,因为OC对象的每个方法都可以视为如下简单的C函数,<return_type> Class_selector(id self, SEL _cmd, ...),这也是为什么后面找到相应匹配的方法后,能够跳转到相应实现代码的原因。
        如果在方法列表中找到与选择子名称相符的方法,就跳转到相应方法的实现代码,如果找不到,就沿着继承体系继续向上查找,若找到合适的方法就会再跳转到该方法实现代码,如果最终还是没找到相符合的代码,执行消息转发。即后面将要介绍的消息转发过程。《在这个过程中,objc_msgSend会将匹配结果缓存到快速映射表,这样后面执行相同的查找速度更快,尽管没有静态绑定的函数调用那么迅速,但是也不会慢很多了》
       下面的几个函数,与objc_msgSend函数类似,它们主要用于处理一些边界情况,可以了解一下
        objc_msgSend_stret 待发送的消息要返回结构体   objc_msgSend_fpret 待发送的消息要返回浮点数  objc_msgSendSuper待发送的消息要给超类发消息。

(3)函数调用(消息转发)过程 2

      经过上面传递消息过程还是没有找到匹配的方法之后,就会启动下面的消息转发过程。消息转发又分为动态方法解析、备援接受者、完整的消息转发三个步骤。
     动态方法解析:类的接受者,看是否能动态添加方法,以处理当前无法解读的选择子,首先要在所属类中重写下面的类方法,+(BOOL) resolveInstanceMethod ( SEL ),前面是为了处理未知的实例方法,如果是处理未知的类方法,重写 +(BOOL) resolveClassMethod ( SEL ),selector 参数,即未知的选择子,在这里有机会新增处理此选择子的方法,若方法的实现代码已经写好,运行的时候就会动态插入到类里面。书本上coredata的dynamic属性即通过上面的方式实现。

     备援接受者:请接受者看看有没有其他对象能处理这条消息,这一步进行处理的相应方法为 - (id) forwardingTargetForSelector: (SEL) selector; 方法参数即代码未知选择子,注意:我们无法操作经由这一步转发的消息,要通过接受者自己去找备援对象,若找到则返回,找不到返回nil。 

    完整消息转发:修改消息的内容,即创建NSInvocation对象,把未能处理的消息的选择子,目标及参数封装到这个对象中,然后触发NSInvocation对象,把消息指派给目标对象,通过重写下列方法来转发消息。- (void) forwardInvocation:(NSInvocation *) invocation。此方法默认实现,即改变调用目标,使消息在新目标上可以调用,这样与备援接受者实现的方法等效,只是在发送给备援接受者之前可以修改消息内容。

2、method swizzling的理解和用法

    (1) Method Swizzling基本概念        
        通过前面对OC运行时的理解,允许我们修改selector(method name)到implementation(the method code itself)的映射。网上说,利用这个特性,我们可以"修补"那些没有源码的方法,如(AppKit,FoundationKit,或第三方的库等)。给这些方法基础上,增加新的操作,等和category不同的是,category如果定义了一个原来相同的方法,那么会直接覆盖原来的方法。Method Swizzling让你可以在替代原来的方法的同时可以调用原来的方法,并且为这个方法增加一些新的功能,记录日志等等,有点像subclassing。
    (2) Method Swizzling原理
    在前面已经讲到,在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。

  每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
  selectorA --> IMPa    selectorB --> IMPb    selectorC -->IMPc ....

 要实现互换两个函数的操作,首先要利用下面的两个函数:
 void method_exchangeImplementation(Method m1, Method m2)   //交换参数中传入的两个方法实现,方法实现可通过下面的函数获得
 Method class_geInstanceMethod (Class aClass, SEL aSelector)   //根据给定的选择从类中取出与之相关的方法的实现
以交换selectorA和selectorB两个方法的实现为例,交换后的指针指向为,selectorA --> IMPb  selectorB -->  IMPa

 (3) Method Swizzling实例代码
 以在NSString的一个分类中添加一个新的方法myLowercaseString ,与NSString已经存在的lowercaseString方法的交换为例,首先在分类中定义如下方法:

  @interface NSString (myAdditions)
 - (NSString *)myLowercaseString;
 @end

 @implementation NSString (myAdditions)
 - (NSString *)myLowercaseString {
NSString *lowercase = [self myLowercaseString];
NSLog(@"输出结果为:%@ => %@", self, lowercase);
return lowercase;
}
 @end

 // 通过下面代码来交换上面两个方法
Method originMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
 Method swappedMethod = class_getInstanceMethod([NSString class], @selector(myLowercaseString));
 method_exchangeImplementations(originMethod, swappedMethod);
 //最后在NSString上调用lowercaseString方法,会输出NSLog的打印语句,输出结果为:
 NSString *string = @"THIS IS THE STRING";
 NSString *lowercaseString = [string lowercaseString];

 //实际上,交换方法后,lowercaseString调用的是myLowercaseString的具体实现,而myLowercaseString里面又调用它自己指向的lowercaseString方法的实现,从而得到上面的输出结果

第三部分、懒加载

懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小)。 注意:如果是懒加载的话则一定要注意先判断是否已经有了,如果没有那么再去进行实例化

使用懒加载的好处: (1)不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强 (2)每个控件的getter方法中分别负责各自的实例化处理,代码彼此之间的独立性强,松耦合 具体实现:重写相关属性的get方法,在这个方法中alloc init该属性,如果是ui控件,在这里创建后添加到父控件上。

有一个问题:在ARC下使用懒加载,不在当前可见范围内的控件会不会做release处理,就像UITabBarController不在当前显示窗口上的viewcontroller就应该释放。

自动布局AutoLayout

1、AutoLayout的基本概念

 (1)定义
  When calculating the runtime positions of elements in a user interface, the Auto Layout system considers all constraints at the same time, and sets positions in such a way that best satisfies all of the constraints.
 当计算用户界面上元素位置的时候,Auto Layout会考虑会同时考虑所有的约束,通过最好的满足所有的约束来设置它的位置。

 (2)表示方法
   通过创建多个元素之间关系的数学表达式进行表示
   y = m*x + b
   y and x are attributes of views. 如:left top width等
   m and b are floating point values.  常量值 关系 = >= <=

   优先级 NSLayoutPriority,高priority的constraint在冲突时会覆盖低constraint的效果
   1、constraint是累加的,设定同类型的constraint不会替换原有的constraint,如果想替换需要自己删除原有的constraint。
   2、对于某些控件,constraint可以跨层级添加,button.left = buton.superview.superview.left + 10 。但是对于其subviews是通过设置frame值手动设置,以及类似scroll view(have a bounds transform)的控件,有一个内部世界和一个外部世界,内部控件无法通过约束与外部世界进行连接。
   3、ntrinsic Content Size :内在内容大小,有些控件如label是根据其内容自动调整大小的,如Size To Fit Content
   4、autolayout的实现是将它的任务分配到各个viewcontroller和view 上,因此,说明view可以控制器内部控件内容的大小,对于controller对象如何在运行时添加、移除和调整约束需要进一步学习
   5、定义Intrinsic Content Size指定自己在autolayout下的默认大小,除了Intrinsic Content Size,还可以设定自己在过宽或者过窄情况下的自适应能力.
   setContentHuggingPriority:forAxis: 设定view的抗拉能力,一般默认抗拉比较低都在250.
   setContentCompressionResistancePriority:forAxis:设定抗压能力,一般抗压能力默认值较高在750.

2、AutoLayout的布局过程

   理解布局过程是正确使用autolayout的关键,布局过程主要分为以下三个部分:更新约束 (updating constraints)----布局视图 (laying out views)----显示视图

   第一部分:更新约束:自下而上(从子视图到父视图)发生的,通过子视图来撑开父视图,并且系统会在此过程中自动计算视图的 frame 。对于自定义视图,一般重写
   updateConstraints方法(一定要注意最后调用super 的这个方法),可以通过调用 setNeedsUpdateConstraints 来触发这个操作。并且最好通过实现 requiresConstraintBasedLayout 返回 YES 明确这个依赖。且可以返回intrinsicContentSize这个属性的大小。
    在使用第三方库masonry进行约束布局时,可以通过mas_makeConstraint   mas_updateConstraints(更新前面的约束,不会删除之前的约束)    mas_remakeConstraint(重新添加约束,删除之前这个view且仅仅这个view上的所有约束) 的使用,对布局进行灵活处理。注意在使用后面两种方法后,都要对当前这个view调用updateConstraints/setNeedsUpdateConstriants。使更新的约束生效。

     第二部分:布局视图:是与前面相反的过程,即自上而下(从父视图到子视图)的过程,即将前面约束值设置得到的frame值应用到视图上,在自定义view中,可以重写layoutSubviews来获得对布局变化的所有权,即在该方法中,可以更改它的布局过程,通过调用 setNeedsLayout 来触发一个操作请求,这并不会立刻应用布局,而是在稍后再进行处理。因为所有的布局请求将会被合并到一个布局操作中去,所以你不需要为经常调用这个方法而担心。
     可以调用 layoutIfNeeded / layoutSubtreeIfNeeded(分别针对 iOS / OS X)来强制系统立即更新视图树的布局。如果你下一步操作依赖于更新后视图的 frame,这将非常有用.

    第三部分:显示视图:自上而下将渲染后的视图传递到屏幕上,重写drawRect:可以获得自定义视图显示过程的所有权。通过调用 setNeedsDisplay 可以触发,这将会导致所有的调用都被合并到一起推迟重绘。

    规律:由于每一步都是依赖前一步操作的,如果有任何布局的变化还没实行的话,显示操作将会触发一个布局行为。类似地,如果约束条件系统中存在没有实行的改变,布局变化也将会触发更新约束条件。因此,这三步并不是单向的。基于约束条件的布局是一个迭代的过程,布局操作可以基于之前的布局方案来对约束做出更改,而这将再次触发约束的更新,并紧接另一个布局操作。这可以被用来创建高级的自定义视图布局,但是如果你每一次调用的自定义 layoutSubviews 都会导致另一个布局操作的话,你将会陷入到无限循环的麻烦中去。

    使用:在viewcontroller中,可以重写下面的方法,获得布局后的控件的frame等属性,并且还可以在这里对这些属性进行修改和更新   
    - (void)viewWillLayoutSubviews
    - (void)viewDidLayoutSubviews

    调试:
    当你在不可满足的约束条件错误信息中看到 NSLayoutResizingMaskConstraints 时,你肯定忘了为你某一个视图设定 translatesAutoResizingMaskIntoConstraints 为 NO

springs和strutsc方法存在的布局问题

   (1) 让视图贴附左上边缘(或者右下边缘),并且当父视图大小改变时,重新调整自身水平和垂直方向的大小。但又没告诉子视图该调整多少(即保证视图之间的间隔是多少)(默认的处理就是按初始的固定大小排列,如果与其他视图出现冲突,就缩小自己的宽和高)这样带来的问题是如果切换横竖屏会出现一些问题。
    在当父视图改变大小时,autosizing masks告诉子视图调整大小,改变灵活宽度和高度设置(springs在旋转用户界面之前、之间、之后,UIKit会发送一些消息到你的视图控制器,你可以截获这些消息,从而对你UI做出改变。代表性的像viewWillLayoutSubviews,你会重写这个方法从而改变任何需要重新排列的视图的frame。

   (2) 根据上面的分析可以重写viewWillLayoutSubviews。判断屏幕方法来设置视图的不同固定宽度和高度,注意将autosizing对应图中的宽高约束去掉,此时横竖屏切换可以了,但是在不同尺寸的模拟器上运行又会出现一些问题。所以最后autolayout自动布局应运而生啦

 http://www.cocoachina.com/industry/20131203/7462.html   

 约束是一个真实的对象(NSLayoutConstraint),并且他们也有属性,自动布局最基本的工具是约束

参考文档

http://www.cocoachina.com/industry/20131012/7148.html http://objccn.io/issue-3-5/

iOS生命周期相关知识总结

1、 iOS工程中的各文件和目录的作用

Tests文件用于单元测试(commad+u 执行单侧命令)
Supporting files文件夹下Info.plist的文件,该文件对工程做一些运行期的配置,非常重要,不能删除
Localiztion native development region(CFBundleDevelopmentRegion)-本地化相关
Bundle display name(CFBundleDisplayName)-程序安装后显示  的名称,限制在10-12个字符,如果超出,将被显示缩写名称
Icon file(CFBundleIconFile)-app图标名称,一般为Icon.png
Bundle version(CFBundleVersion)-应用程序的版本号,每次往          
App Store上发布一个新版本时,需要增加这个版本号
Main storyboard file base name(NSMainStoryboardFile)-主storyboard文件的名称
Bundle identifier(CFBundleIdentifier)-项目的唯一标识,部署到真机时用到
.pch文件,是一个头文件(Xcode6没有找到)pch头文件的内容能被项目中的其他所有源文件共享和访问一般在pch文件中定义一些全局的宏

2、app的生命周期

(1)执行main.m文件的main函数,实现下面两个操作

  创建UIApplication对象
  创建UIApplication的delegate对象
  main函数中执行了一个UIApplicationMain这个函数:
 int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName); 各个参数含义如下:
  argc、argv:直接传递给UIApplicationMain进行相关处理即可
  principalClassName:指定应用程序类名(app的象征),该类必须是UIApplication(或子类)。如果为nil,则用UIApplication类作为默认值
 delegateClassName:指定应用程序的代理类,该类必须遵守  UIApplicationDelegate协议,UIApplicationMain函数会根据principalClassName创建UIApplication对象,根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性。

 以创建的sigle view applicaiton为例,相应方法实现为:return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
 (2)接着会建立应用程序的Main Runloop(事件循环)  
 如果有storyboard,根据Info.plist获得最主要storyboard的文件名,加载最主要的storyboard
 创建UIWindow
 创建和设置UIWindow的rootViewController(即storyboard属性面板的class属性定义的视图控制器)显示窗口
(再去执行delegate的相关方法)
如果没有storyboard,delegate对象开始处理(监听)系统事件程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions:方法在application:didFinishLaunchingWithOptions:中创建UIWindow创建和设置UIWindow的rootViewController显示窗口

有一点不明白的地方就是,如果有storyboard的时候,是显示窗口之后,再去调用appdelegete代理的方法,还是先调用appdelegete代理的方法,再显示storyboard的窗口(解决办法,在代 理的application:didFinishLaunchingWithOptions:方法中创建uiwindow或对它重新赋值,看是否会覆盖storyboard)结论是先从storyboard加载,再执行代理方法。

(3)UIWindow

UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了

一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow,也就说,`没有UIWindow,就看不见任何UI界面
  /**
 *  程序启动完毕就会调用一次
 */
  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// 1.创建window
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// 2.设置window的背景色
self.window.backgroundColor = [UIColor whiteColor];    
MjOneViewController *one = [[MjOneViewController alloc] init];
// 方法一 :加入到UIWindow  [self.window addSubview:one.view];
//方法二 推荐
self.window.rootViewController = one;
// 3.显示window
[self.window makeKeyAndVisible];
return YES;
}

 最后,程序正常退出时UIApplicationMain函数才返回。

3、对象的生命周期

任何对象都是继承自NSObject的,可以通过重写下列方法,设置对象的初始化。
+ (void)load;
+ (void)initialize;
- (instancetype)init

4、视图控制器的生命周期

 loadView     从nib载入视图 ,通常这一步不需要去干涉。
 loadView 此方法在控制器的view为nil的时候被调用。 此方法用于以编程的方式创建view的时候用到。 如:
- ( void ) loadView {
UIView *view = [ [ UIView alloc] initWithFrame:[ UIScreen mainScreen] .applicationFrame] ;
[ view setBackgroundColor:_color] ;
self.view = view;  }

 viewDidLoad方法:此方法为控制器已被实例化,在视图被加载到内存中的时候调用该方法,这个时候视图并未出现。在该方法中通常进行的是对所控制的视图进行初始化处理。
  viewDidLoad方法在应用运行的时候只调用一次,而其他4个方法viewWillAppear   viewDidAppear   viewWillDisapper    viewDidDisapper可以被反复调用多次,它们的使用很广泛但同时也具有很强的技巧性。
 iOS系统在低内存时情况下会调用didReceiveMemoryWarning:该方法的主要职能是释放内存,包括视图控制器中的一些成员变量和视图的释放

  ViewController的生命周期中各方法执行流程如下:
  init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc

 修改应用初始启动时关联的storyboard文件,通过代码创建初始启动的视图控制器
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//重新设置self.window
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
ViewController *vc = [[ViewController alloc] init];
UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.backgroundColor = [UIColor whiteColor];
//设置window上加载的视图控制器
self.window.rootViewController = nvc;
//使改动生效
[self.window makeKeyAndVisible];
NSLog(@"%s",__FUNCTION__);
return YES;
}

通过创建storyboard加载视图控制器的方法: 创建一个storyboard,New File –> IOS –> User Interface –> Storyboard 设置storyboard右侧的class属性为相应视图控制器类 在代码中创建storyboard的同时,启动相应的视图控制器 UIStoryboard *two = [UIStoryboard storyboardWithName:@“Main” bundle:nil]; [self.navigationController pushViewController:[two instantiateInitialViewController] animated:YES]; 创建视图控制器时勾选创建xib,自动生成对应xib文件 New File –> IOS –> Source –> Cocoa Touch Class(勾选Also create Xib File)

通过手动创建带xib的视图控制器 创建一个xib文件,New File –> IOS –> User Interface –> view FiveViewController *fiveVC = [[FiveViewController alloc]initWithNibName:@“other” bundle:nil]; [self.navigationController pushViewController:fiveVC animated:YES];

5、 视图控制器装载和卸载视图的过程

ViewController的职责主要包括管理内部各个view的加载显示和卸载,同时负责与其他ViewController的通信和协调 ViewController可以分为两类: 一类是显示内容的,比如UIViewController、UITableViewController等,同时还可以自定义继承自UIViewController的ViewController;另一类是ViewController容器,UINavigationViewController和UITabBarController等 在控制器view加载过程中首先会调用loadView方法,在这个方法中主要完成一些关键view的初始化工作,比如UINavigationViewController和UITabBarController等容器类的ViewController;接下来就是加载view,加载成功后,会接着调用viewDidLoad方法,这里要记住的一点是,在loadView之前,是没有view的,也就是说,在这之前,view还没有被初始化。完成viewDidLoad方法后,ViewController里面就成功的加载view了,如下图右下角所示。 在Controller中创建view有两种方式,一种是通过代码创建、一种是通过Storyboard或Interface Builder来创建,后者可以比较直观的配置view的外观和属性 关于loadView方法的重写,官方文档中有一个明显的注释,当通过代码方式去创建你自己的view时,在loadView方法中不应该调用super,如果调用[super loadView]会影响CPU性能。 - (void)loadView
{
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];
contentView.backgroundColor = [UIColor blackColor];
self.view = contentView;
} view加载过程.png 当系统发出内存警告时,会调用didReceiveMemoeryWarning方法,如果当前有能被释放的view,系统会调用viewWillUnload方法来释放view,完成后调用viewDidUnload方法,至此,view就被卸载了。此时原本指向view的变量要被置为nil,具体操作是在viewDidUnload方法中调用self.myButton = nil。 ViewController中view卸载过程.png loadView和viewDidLoad的区别就是,loadView时view还没有生成,viewDidLoad时,view已经生成了,loadView只会被调用一次,而viewDidLoad可能会被调用多次(View可能会被多次加载),当view被添加到其他view中之前,会调用viewWillAppear,之后会调用viewDidAppear。当view从其他view中移除之前,调用viewWillDisAppear,移除之后会调用viewDidDisappear。当view不再使用时,受到内存警告时,ViewController会将view释放并将其指向为nil。

6、视图UIView的生命周期

常用方法如下: - (void)removeFromSuperview; - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index; - (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2;

  • (void)addSubview:(UIView *)view;
  • (void)insertSubview:(UIView )view belowSubview:(UIView )siblingSubview;
  • (void)insertSubview:(UIView )view aboveSubview:(UIView )siblingSubview;

  • (void)bringSubviewToFront:(UIView *)view;

  • (void)sendSubviewToBack:(UIView *)view;

  • (void)didAddSubview:(UIView *)subview;

  • (void)willRemoveSubview:(UIView *)subview;

  • (void)willMoveToSuperview:(nullable UIView *)newSuperview;

  • (void)didMoveToSuperview;
  • (void)willMoveToWindow:(nullable UIWindow *)newWindow;
  • (void)didMoveToWindow;