02-面试iOS工程师常被问到的20个基础问题
1.什么是 ARC ? ( ARC 是为了解决什么问题诞生的 ? )####
自动引用计数 (
ARC,Automatic Reference Counting): 是指内存管理中对引用采取自动计数的技术,在Objective-C中采用ARC机制,让编译器来进行内存管理. 在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或release代码,这在降低程序崩溃、内存泄漏等风险的同时,很大程度上减少了开发程序的工作量.编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象.
ARC 是为了解决什么问题诞生的:需追溯到 MRC 手动内存管理时代.MRC 内存管理存在的问题:
- 当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被
release了( 避免提前释放 ) - 释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次(
MRC下即谁创建,谁释放,避免重复释放 ) - 模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放.
- 多线程操作时,不确定哪个线程最后使用完毕
2. 请解释以下 Keywords 的区别: assign 和 weak,block 和 weak?####
assign和weak:
-
assign适用于基本数据类型( 如NSInteger)和 C 数据类型(int、float、double、char等),另外还有id,weak适用于NSObject对象,并且是一个弱引用.( 反正记住:前面不需要加‘ * ’的就用assign) -
assign也可以用来修饰对象,但为什么不用它修饰对象呢 ?因为assign修饰的对象( 一般编译的时候会产生警告:Assigning retained object to unsafe property:object will be released after assignment)在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil,造成野指针. 对象一般分配在堆上的某块内存,如果在后续内存分配中,刚好分配了这块内存,程序就会崩溃. - 为什么可以用
assign修饰基本数据类型 ? 因为基础数据类型一般分配在栈上,栈的内存会由系统自动处理,不会造成野指针错误. -
weak修饰的对象在释放之后,指针地址会被置为nil. 这样再向weak修饰的属性发送消息就不会导致野指针操作 crash.
weak使用场景:- 在
ARC下,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如:delegate代理属性,通常就会声明为weak. - 自身已经对它进行一次强引用,没有必要再强引用一次时也用
weak.比如:自定义IBOutlet控件属性一般使用weak,当然也可以使用strong.
- 在
__block和__weak:
-
__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型. -
__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int). -
__block对象可以在block中被重新赋值,__weak不可以. -
__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用. -
__unsafe_unretained修饰符可以被视为iOS SDK 4.3以前版本的__weak的替代品,不过不会被自动置为nil,所以尽可能不要使用这个修饰符.
参考文章:
正确使用Block避免Cycle Retain和Crash - Cooper's Blog
参考书籍:《 Objective-C 高级编程 iOS 与 OS X 多线程和内存管理 - 第2章 Blocks 》
3. __block 在 ARC 和非 ARC 下含义一样吗 ?####
不一样,标记为
__block的变量,在block中使用,在ARC下会retain,在非ARC下则不会retain.
参考链接:iOS: ARC和非ARC下使用Block属性的问题
4.使用 atomic 一定是线程安全的吗 ?####
nonatomic的内存管理语义是非原子的,非原子的操作本来就是线程不安全的,而atomic的操作是原子的,但是并不意味着它是线程安全的,它会增加正确的几率,能够更好的避免线程的错误,但它仍然是线程不安全的.
当使用nonatomic的时候,属性的setter和getter操作是非原子的,所以当多个线程同时对某一属性进行读和写的操作,属性的最终结果是不能预测的.
当使用
atomic时,虽然对属性的读和写是原子的,但是仍然可能出现线程错误:当线程 A 进行写操作,这时其他线程的读或写操作会因为该操作的进行而等待,当 A 线程的写操作结束后,B 线程进行写操作,然后当 A 线程进行读操作时,却获得了在 B 线程中的值,这就破坏了线程安全,如果有线程 C 在 A 线程读操作前release了该属性,那么还会导致程序崩溃.所以仅仅使用atomic并不会使得线程安全,我们还需要为线程添加lock来确保线程的安全.
注意:
atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。如下列所示:@property(atomic,strong)NSMutableArray *arr;如果一个线程循环的读数据,一个线程循环写数据,那么肯定会产生内存问题,因为这和setter、getter没有关系,如使用[ self.arrobjectAtIndex:index ]就不是线程安全的. 好的解决方案就是加锁
5.描述一个你遇到过的 retain cycle 例子####
typedef void ( ^blk_t )( void );
@interface MyObject : NSObject
{
blk_t blk;
id obj_;
}
@implementation MyObject
- ( id ) init
{
self = [ super init ];
blk_ = ^ { NSLog ( @" obj_ = %@ " ,** obj_ **); }
return self;
}
虽然 Block 内没有使用self也同样截获了 self,引起了循环引用,即Block语法内使用的obj_实际上截获了self,对编译器来说,obj_只不过是对象用结构体的成员变量.相当于blk_ = ^ { NSLog( @" obj_ = %@ " , self -> obj_ ); }
为了避免循环引用,声明附有 __weak 修饰符的变量并赋值 obj_使用,也可安全的使用__unsafe__unretained修饰符.
- ( id ) init
{
self = [ super init ];
id __weak obj = obj_;
blk_ = ^ { NSLog ( @" obj_ = %@ " , **obj** ); }
return self;
}
6. +(void)load; 和 +(void)initialize; 有什么用处 ?####
**Objective-C 中绝大多数类都继承自 NSObject,该类有两个方法,+load 和 +initialize,用来实现类的初始化操作. **
**
+load(): **该方法是在类或者分类被添加到Objective-C runtime时被调用的,而且只调用一次. 当包含类或分类的程序库载入系统时,就会执行此方法,这通常就是指应用程序启动的时候,load方法务必实现的精简一些,也就是要减少其所执行的操作,因为整个应用程序在执行load方法时都会阻塞.
注意:
- 子类的
+load方法会在它的所有父类的+load方法之后执行,而分类的+load方法会在它的主类的+load方法之后执行,但是不同类的+load方法的调用顺序是不确定的. -
load方法并不像普通的方法那样,它并不遵循那套继承规则,如果某个类本身没有实现load方法,那么不管其各级超类是否实现此方法,系统都不会调用. -
load方法执行时,运行时系统处于脆弱状态,在执行子类的load方法之前,必定会执行所有超类的load方法,而如果代码还依赖其他程序库,那么程序库里相关类的load方法也必定会先执行,然而由第一条知根据某个给定的程序库,却无法判断出其中各个类的载入顺序,因此在load方法中使用其他类是不安全的. - 分类和其所属的类里,都可能出现
load方法,此时两种实现代码都会调用,类的实现要比分类的实现先执行.
+initialize():该方法是在类或它的子类收到第一条消息之前被调用的,这里的消息包括实例方法和类方法的调用,也就是说+initialize方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的initialize方法永远不会被调用,这样可以节省系统资源,避免浪费.
注意:
- 该方法会在程序首次用该类之前调用,且只调用一次. 它是由运行期系统来调用的,绝不应该通过代码直接调用.
- 它是“惰性调用”,就是说只有当程序用到相关类时才会调用.也就是说应用程序无需先把每个类的
initialize都执行一遍,这与load方法不同,对于load方法来说,应用程序必须阻塞并等着所有类的load都执行完才能继续. -
initialize方法与其他消息一样,如果某个类未实现它,而其超类实现了,那么就会运行超类的实现代码.
要点: - 在加载阶段,如果类实现了
load方法,那么系统就会调用它. 分类里也可以定义此方法,类的load方法要比分类中的先调用. 与其他方法不同,load方法不参与覆写机制. - 首次使用某个类之前,系统会向其发送
initialize消息,由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类. -
load与initialize方法都应该实现的精简一些,这有助于保持应用程序的响应能力,也能减少引入“依赖环”的几率. - 无法在编译期设定的全局变量,可以放在
initialize方法里初始化.
参考链接:
Objective-C +load vs +initialize - 雷纯锋的技术博客
Objective C类方法load和initialize的区别 - Ider - 博客园
参考书籍:Effective Objective-C 2.0 编写高质量的 iOS 与 OS X 代码的52个有效方法 (第51条)
7.为什么其他语言里叫函数调用,OC 中则是给对象发送消息(或者谈下对 runtime 的理解).
首先,
OC是一门动态语言,可以在运行的时候动态决定调用哪个方法实现,甚至添加、替换方法的具体实现,而这些都归功于Objective-C的运行时runtime系统.
消息与函数之间的关键区别在于:使用消息结构的语言,其运行时所应执行的代码有运行环境来决定,使用函数调用的语言,则有编译器决定;
-
发送消息的含义
[receiver message] //向 receiver 对象发送 message 消息
会被编译器转化为:objc_msgSend (receiver, selector);如果消息含有参数,转化为objc_msgSend ( receiver, selector,arg1,arg2,... );如果消息的接收者能够找到对应的selector,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或是临时向接收者动态添加这个selector对应的实现内容,要么就干脆玩完崩溃掉.
可以看出[receiver message]真的不是一个简简单单的方法调用,因为这只是在编译阶段确定了要向接收者发送message这条消息,而receive将要如何响应这条消息,那就要看运行时发生的情况来决定了.
-
关于runtime
Objc Runtime使得 C 具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法,可以使用runtime的一系列方法实现; 顺便附上 OC 中一个类的数据结构 /usr/include/objc/runtime.h
struct objc_class {
//isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
// 父类
Class super_class OBJC2_UNAVAILABLE;
// 类名
const char *name OBJC2_UNAVAILABLE;
//类的版本号,默认为0
long version OBJC2_UNAVAILABLE;
// 类信息,供运行期使用的一些位标识
long info OBJC2_UNAVAILABLE;
// 该类的实例变量大小
long instance_size OBJC2_UNAVAILABLE;
// 该类的成员变量链表
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
// 方法定义的链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
// 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在methodLists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
struct objc_cache *cache OBJC2_UNAVAILABLE;
// 协议链表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
向object发送消息时,Runtime库会根据object的isa指针找到这个实例object所属于的类,然后在类的方法列表以及父类方法列表寻找对应的方法运行,id是一个objc_object结构类型的指针,这个类型的对象能够转换成任何一种对象.
然后再来看看消息发送的函数:objc_msgSend函数,看起来objc_msgSend像是返回了数据,其实objc_msgSend从不返回数据而是你的方法被调用后返回了数据.下面详细叙述下消息发送步骤:
- 1)检测这个
selector是不是要忽略的,比如 Mac OS X 开发,有了垃圾回收就不理会retain,release这些函数了, - 2)检测这个
target是不是nil对象,Objc的特性是允许对一个nil对象执行任何一个方法不会Crash,因为会被忽略掉. - 3)如果上面两个都过了,那就开始查找这个类的
IMP,先从cache里面找,完了找得到就跳到对应的函数去执行. - 4)如果
cache找不到就找一下方法分发表,如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止,如果还找不到就要开始进入动态方法解析了.
参考文章:
刨根问底Objective-C Runtime
8.什么是 Method Swizzling ?####
Method Sswizzling指的是改变一个已存在的选择器对应的实现过程,它依赖于Objective-C中方法的调用能够在运行时改变,在运行时通过修改类的分发表dispatch table中selector对应的函数,来修改方法的实现.
-
举个例子,假设我们想跟踪在一个iOS应用中每个视图控制器展现给用户的次数:
-
我们可以给每个视图控制器对应的
viewWillAppear:实现方法中增加相应的跟踪代码,但是这样做会产生大量重复的代码; -
子类化可能是另一个选择,但要求你将
UIViewController、UITableViewController、UINavigationController以及所有其他视图控制器类都子类化,这也会导致代码重复; -
幸好,还有另一个方法,在分类中进行
method swizzling,在这之前先来看看选择器、方法和实现的区别; -
选择器、方法及实现:在
Objective-C中,尽管这些词经常被放在一起来描述消息传递的过程,但选择器、方法及实现分别代表运行时的不同方面;下面是苹果Objective-C Runtime Reference文档中对它们的描述: -
选择器(
typedef struct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射 -
方法(
typedef struct objc_method *Method):一个代表类定义中一个方法的不明类型; -
实现(
typedef id (*IMP)(id, SEL, ...)):这种数据类型是实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约,第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向元类(metaclass)的指针),第二个参数是方法的选择器,后面跟的都是参数;
理解这些概念之间关系最好的方式是:一个类
Class维护一张调度表dispatch table,用于解析运行时发送的消息;调度表中的每个实体entry都是一个方法Method,其中key值是一个唯一的名字——选择器SEL,它对应到一个实现IMP——实际上就是指向标准C函数的指针(注:个人觉得一个Method包含了方法名SEL和方法实现IMP,不知道这样理解对不对)
-
在分类中进行Method Swizzing
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
//这段代码看起来像是会导致一个死循环,但其实并没有,在Swizzling的过程中,xxx_viewWillAppear:会被重新分配给UIViewController的-viewWillAppear:的原始实现
//反而,如果我们在这个方法中调用viewWillAppear:才会真的导致死循环,因为这个方法的实现会在运行时被swizzle到viewWillAppear:的选择器
@end
现在,当UIViewController或它子类的任何实例触发viewWillAppear:方法都会打印一条log日志;Method Swizzling就是改变类的调度表让消息解析时从一个选择器对应到另外一个的实现,同时将原始的方法实现混淆到一个新的选择器;
-
根据上述代码总结
Method Swizzling的实现过程: - Swizzling应该在
+load方法中实现:每个类的+load和+initialize这两个方法都会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方法之前调用,这两个方法都是可选的,只有实现了才会被执行;因为method swizzling会影响全局,所以减少冒险情况就很重要;+load能够保证在类初始化的时候就会被加载,这为改变系统行为提供了一些统一性,但+initialize并不能保证在什么时候被调用——事实上也有可能永远也不会被调用,例如应用程序从未直接的给该类发送消息 - Swizzling应该在
dispatch_once中实现:还是因为swizzling会改变全局,我们需要在运行时采取所有可用的防范措施,保障原子性就是一个措施,它确保代码即使在多线程环境下也只会被执行一次,GCD中的diapatch_once就提供这些保障,它应该被当做swizzling的标准实践
参考文章:
Objective-C Method Swizzling 的最佳实践
Objective-C的hook方案(一): Method Swizzling
Method Swizzling
9. UIView 和 CALayer 是什么关系 ?####
首先应该明确:
-
CALayer继承自NSObject,而UIView继承自NSResponder,所以UIView是可以响应用户的点击事件的,而CALayer更多的是做渲染和动画效果. -
CALayer和UIView都可以在屏幕上展示,每个在页面上展示的UIView都是要在自己的根layer上进行对自己的绘制,就好比layer是画板,而view是画笔 - 它们两个在展示效率上并没有很大差别,但是依然推荐子啊
tableViewCell中使用drewRect:而不是subViews的形式进行cell绘制.
10.如何高性能的给 UIImageView 加个圆角 ?(不准说 layer.cornerRadius ).####
圆角是一种很常见的视图效果,相比于直角它更加柔和优美,易于接受;当然,设置圆角会带来一定的性能损耗,如何提高性能是另一个需要重点讨论的话题.
UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现,在下文会讲到;
-
如何设置圆角
通常label.layer.cornerRadius = x;就可以设置圆角,但是cornerRadius只会影响视图的背景颜色和border,对于内部还有子视图的控件就会设置不成功,如UILabel;对于内部还有子视图的控件还需要设置label.layer.masksToBounds = true;才能使cornerRadius属性生效,此时会造成离屏渲染,设置圆角时masksToBounds的使用会降低手机每秒刷新的帧数,特别是在tableView中设置圆角时帧率下降非常快,帧率下降直接造成界面的不流畅;
Off-Screen Rending
离屏渲染,指的是图形处理器GPU(Graphic Processing Unit)在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作,视图和圆角的大小对帧率并没有什么影响,设置圆角的数量才是影响帧率的原因;离屏渲染耗时是发生在离屏这个动作上面,而不是渲染;为什么离屏这个操作耗时?原因主要有缓冲区和上下文切换,创建新的缓冲区代价都不算大,付出最大代价的是上下文切换,不管实在GPU渲染过程中,还是进程切换,上下文切换都是一个相当耗时的操作;首先我要保存当前屏幕渲染环境,然后切换到一个新的绘制环境,申请绘制资源,初始化环境,然后开始一个绘制,绘制完毕后销毁这个绘制环境,如需要切换到On-Screen Rendering或者再开始一个新的离屏渲染,重复之前的操作;
-
针对上述设置圆角造成频率下降的解决方案
- 方案一:不要在滚动视图使用
cornerRadius或者mask,如果你非要作死使用怎么办?那么这样也可以拯救你:
- 方案一:不要在滚动视图使用
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
这样大部分情况下可以马上挽救你的帧数在55帧每秒以上,shouldRasterize = YES会使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存,当然要在视图内容不改变的情况下;参考文章iOS图片圆角设置的正确姿势
- 方案二:预先生成圆角图片并缓存起来,这个方法才是比较好的手段,预处理圆角图片可以在后台处理,处理完毕后缓存起来,再在主线程显示,这就避免了不必要的离屏渲染了;
- 方案三:在图片上面覆盖一个镂空圆形图片同样可以实现圆形头像效果,这个也是极为高效的方法,缺点就是对视图的背景有要求,单色背景效果就最为理想;
- 方案四:为普通的
UIView添加圆角和为UIImageView设置圆角的原理截然不同;-
为普通的
UIView添加圆角的原理是手动画出圆角,利用Core Graphics自己画出一个圆角矩形,再从当前的绘图上下文中获取图片并返回,有了这个图片后,我们创建一个UIImageView并插入到视图层级的底部(参考文章iOS 高效添加圆角效果实战讲解) -
为
UIImageView添加圆角通过直接截取图片实现,可以给UIImage添加一个分类UIImage+Extension,分类中增加一个返回圆形图片的方法,扩展性强(参考文章iOS开发中设置圆角的几种方法)#import <UIKit/UIKit.h> @interface UIImage (Extension) - (UIImage *)circleImage; @end#import "UIImage+Extension.h" @implementation UIImage (Extension) - (UIImage *)circleImage { // 开始图形上下文 UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 获得图形上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 设置一个范围 CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); // 根据一个rect创建一个椭圆 CGContextAddEllipseInRect(ctx, rect); // 裁剪 CGContextClip(ctx); // 将原照片画到图形上下文 [self drawInRect:rect]; // 从上下文上获取剪裁后的照片 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); // 关闭上下文 UIGraphicsEndImageContext(); return newImage;
-
}
```
具体使用:
```
// 获得的就是一个圆形的图片
UIImage *placeHolder = [[UIImage imageNamed:@"defaultUserIcon"] circleImage];
```
参考文章:
圆角卡顿刨根问底
iOS 高效添加圆角效果实战讲解
iOS view圆角化的四种方法
开源高性能处理圆角
ZYCornerRadius 一句代码,圆角风雨无阻
11.使用 drawRect 有什么影响 ?(可深可浅,至少得用过)####
drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行,这样的话从性能的角度来说,对CPU和内存来说都是欠佳的;特别是如果在我们的界面上有多个这样的UIButton实例.
参考文章:
内存恶鬼drawRect
iOS重绘机制drawRect
UIView的layoutSubviews和drawRect
12.ASIHttpRequest 或者 SDWebImage 里面给 UIImageView 加载图片的逻辑是什么样的 ?(把 UIImageView 放到 UITableViewCell 里面问更赞)####
13.麻烦你设计一个简单的图片内存缓存器( 移除策略是一定要说的 ).####
14.讲讲你用 Instrument 优化动画性能的经历吧(别问我什么是 Instrument )####
15. loadView 是干嘛用的 ?####
当你访问一个
ViewController的view属性时,如果此时view的值是nil,那么,ViewController就会自动调用loadView这个方法;这个方法就会加载或者创建一个view对象,赋值给view属性;
loadView默认做的事情是:如果此ViewController存在一个对应的nib文件,那么就加载这个nib;否则,就创建一个UIView对象;
如果你用Interface Builder来创建界面,那么不应该重载这个方法;
如果你想自己创建view对象,那么可以重载这个方法;此时你需要自己给view属性赋值;你自定义的方法不应该调用super;如果你需要对view做一些其他的定制操作,在viewDidLoad里面去做;
-
根据上面的文档可以知道,有两种情况:
-
1、如果你用了
nib文件,重载这个方法就没有太大意义;因为loadView的作用就是加载nib;如果你重载了这个方法不调用super,那么nib文件就不会被加载;如果调用了super,那么view已经加载完了,你需要做的其他事情在viewDidLoad里面做更合适; -
2、如果你没有用
nib,这个方法默认就是创建一个空的view对象;如果你想自己控制view对象的创建,例如创建一个特殊尺寸的view,那么可以重载这个方法,自己创建一个UIView对象,然后指定self.view = myView;但这种情况也没有必要调用super,因为反正你也不需要在super方法里面创建的view对象;如果调用了super,那么就是浪费了一些资源而已;
16. viewWillLayoutSubView 讲一讲####
frame改变之前会调用viewWillLayoutSubviews,如横竖屏切换的时候,系统会响应一些函数,其中 包括viewWillLayoutSubviews和viewDidLayoutSubviews;
-
ARC环境下单个viewController的生命周期: -
initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib) -
loadView:加载view -
viewDidLoad:view加载完毕 -
viewWillAppear:控制器的view将要显示 -
viewWillLayoutSubviews:控制器的view将要布局子控件 -
viewDidLayoutSubviews:控制器的view布局子控件完成
注:这期间系统可能会多次调用viewWillLayoutSubviews和viewDidLayoutSubviews这两个方法 -
viewDidDisappear:控制器的view完全消失的时候