addChildViewController: VS addSubview:

本文探讨了addChildViewController与addSubview的区别和使用场景。addChildViewController自iOS5引入,能更好地处理内存管理,特别是在需要动态变化的subview中,与父控制器的生命周期同步。联合使用可以实现视图间的动画跳转,降低耦合度,并在内存警告时自动释放非激活状态的控制器内存。建议在需要时使用addChildViewController以遵循Apple的最佳实践。

前言

最近看大神们的博客时,发现一个之前一直没注意到的API:addChildViewController:很有用武之地,很大程度上弥补了经验不深的开发者(ps:说得好像不是我一样!)常使用的addSubview:的不足,而且Apple官方也希望我们在使用[self.view addSubview:XX]的同时调用[self addChildViewController:XX]。
addChildViewController:是从iOS5开始引入的,那为什么Apple要做出这个改变,下面我们就addSubview:单独使用与联合addChildViewController:使用比较一番!


权衡利弊

在某些场景中,UIViewController的view上包含许多的subview,而这其中的view有些是在特定的状态下才会显示(比如:登录、注册和订单的提示信息),对于经验比较浅我们通常就是在viewDidLoad中通过[self.view addSubview:XX]方法将subview加入控制器的view,而对于当前不需要显示的subview就将它隐藏起来。

乍一看好像没什么问题,效果也和预期的一样,而在收到内存警告时我们就只能手动的将subview从superView中移除(想想怎么移除?),同时你还得仔细想想怎样的移除顺序才能保证App的功能受影响程度最小,即使当前没有显示的view其实也存在于内存中,如果此次的操作view始终不会出现,那就白白浪费了内存。每个App的内存都有个阀值,当超过这个值时,系统将关闭App,所以在收到内存警告时,要尽可能多的释放不必要的内存。

在iOS6后,Apple就不建议将view置为nil。UIView有一个CALayer的成员变量,CALayer的作用具体用于将自己画到屏幕上。而CALayer是一个bitmap(位图)图像的容器类,当UIView调用自身的drawRect:时,CALayer才会创建这个bitmap图像类。实际上占内存的其实就是一个bitmap图像类,CALayer占48B,UIView占96B,全屏的ipadUIView的bitmap类会占12MB!iOS6后,当系统发出MemoryWarning时,系统会自动回收bitmap类,不会收UIView和CALayer类,这样就能保证回收大部分内存后,又能在需要bitmap类时,只通过调用UiView的drawRect:方法重建,所以当你想在内存警告时移除暂时不需要的view时还一时间想不到好的法子,可以参考这个:

  • 不要把subview当成成员变量来持有,使用tag来操作;
  • 不要使用viewDidUnload方法(iOS6 deprecated),由系统来控制内存的释放;
  • 在需要的时候实现didReceiveMemoryWarning来释放一些业务数据减少内存的占用,不要操作UIView。

所以单纯的使用addSubview:添加很多subview后内存管理将是个头疼的事,addChildViewController:的引入不但解决了这个问题,还能有别的惊喜!相关的API如下:

// 方法
addChildViewController:
removeFromParentViewController:
transitionFromViewController:toViewController:duration:options:animations:completion:
willMoveToParentViewController:
didMoveToParentViewController:
// 属性
@property(nonatomic,readonly) NSArray *childViewControllers

对于当前需要显示的view,我们只需要将对应的控制器通过addChildViewController:加在主控制器的管理队列中,再调用transitionFromViewController:toViewController:duration:options:animations:completion:还能实现多个subview之间的动画跳转,不需要时移除就可以了。(API参上)当收到内存警告时,系统就会自动将当前还没显示的subview从内存中移除,所以我们在需要向主视图中添加subview时就可以临时的创建相应的控制器的局部对象,而非在主控制器类中添加subview的成员变量来控制它的生命周期。很明显联合使用这两个方法有如下好处:

  • 多个控制器视图之间可以实现动画跳转;
  • 页面逻辑更加清晰,降低了代码的耦合度,页面即控制器;
  • 当收到内存警告时,当前不是激活状态下的控制器所占用的内存就会被系统自动释放,十分方便。

总结

对于自始至终都在view上的subview使用addSubview:就足够了,二者的联合使用的应用场景主要是需要进行动态变化的大的subview(比如网易新闻App主界面,这里有个小的demo,有兴趣的可以看一看),当然尽可能多的使用addChildViewController:也不会有什么问题,毕竟官方这是这么希望的!

(ps:笔者自学iOS开发刚近一年,之前没有任何的OOP的经验,写博客旨在分享所学到的一些东西,文笔驽钝,望请见谅!如果博文有什么问题,希望各位同行不吝指出,愿阅读愉快!)

#import "TPNavigationController.h" #import "TPContainerViewController.h" #import "TPContainerNavigationController.h" #import "UIViewController+TPNavigationExtension.h" @interface TPContainerViewController () @property (nonatomic, strong) __kindof UIViewController *rootViewController; @property (nonatomic, strong) TPContainerNavigationController *containerNavigationController; @end @implementation TPContainerViewController // 包装过程 + (instancetype)wrapViewController:(UIViewController *)viewController { return [[self alloc] initWithViewController:viewController]; } // 包装过程 - (instancetype)initWithViewController:(UIViewController *)viewController { if (self = [super init]) { // 先将viewController包装在TPContainerNavigationController下面 self.containerNavigationController = [TPContainerNavigationController wrapNavigationControllerWithViewController:viewController]; // 再将TPContainerNavigationController包装在TPContainerViewController下面 [self addChildViewController:self.containerNavigationController]; [self.containerNavigationController didMoveToParentViewController:self]; // 记录控制器 self.rootViewController = viewController; self.containerNavigationController.containerViewContorller = self; } return self; } - (void)dealloc { [self.containerNavigationController removeFromParentViewController]; self.containerNavigationController = nil; } - (void)viewDidLoad { [super viewDidLoad]; // 添加视图 [self.view addSubview:self.containerNavigationController.view]; self.containerNavigationController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.containerNavigationController.view.frame = self.view.bounds; } - (void)didMoveToParentViewController:(UIViewController *)parent { if (parent == nil) { [self.containerNavigationController removeFromParentViewController]; self.containerNavigationController = nil; } } - (BOOL)shouldAutorotate { return self.rootViewController.shouldAutorotate; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { return self.rootViewController.preferredInterfaceOrientationForPresentation; } - (BOOL)becomeFirstResponder { return [self.rootViewController becomeFirstResponder]; } - (BOOL)canBecomeFirstResponder { return [self.rootViewController canBecomeFirstResponder]; } - (UIStatusBarStyle)preferredStatusBarStyle { return self.rootViewController.preferredStatusBarStyle; } - (BOOL)prefersStatusBarHidden { return self.rootViewController.prefersStatusBarHidden; } - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { return self.rootViewController.preferredStatusBarUpdateAnimation; } - (BOOL)hidesBottomBarWhenPushed { return self.rootViewController.hidesBottomBarWhenPushed; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return self.rootViewController.supportedInterfaceOrientations; } - (UITabBarItem *)tabBarItem { return self.rootViewController.tabBarItem; } - (NSString *)title { return self.rootViewController.title; } - (UIViewController *)childViewControllerForStatusBarStyle { return self.rootViewController; } - (UIViewController *)childViewControllerForStatusBarHidden { return self.rootViewController; } - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { return self.rootViewController.preferredScreenEdgesDeferringSystemGestures; } - (UIViewController *)rootViewController { TPContainerNavigationController *containerNavController = self.childViewControllers.firstObject; return containerNavController.viewControllers.firstObject; } @end
最新发布
09-27
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值