墨半成霜为您深入剖析UIViewController与UIView(一)

如果你对UIViewController的理解仅限于:一个UIViewController 可以push/pop/present到另外一个ViewController,并且给UIView提供容器的话,你可以读读这篇文章了~
如果你对UIView的理解仅限于:UIView是一个将内容呈现给用户的载体的话,你可以读读这篇文章了。

众所周知UIView是基本上是所有控件(用户能看到的内容)的母类(基类)。那么作为一个基类,他的抽象级别是最高的了,也是最简了。我们来看看UIView基类的头文件声明:

@classUIEvent, UIWindow, UIViewController, UIColor, UIGestureRecognizer, CALayer;
 
NS_CLASS_AVAILABLE_IOS(2_0)@interfaceUIView : UIResponder
       
       
        
         {
  @package
    CALayer        *_layer;
    id             _tapInfo;
    id             _gestureInfo;
    NSMutableArray*_gestureRecognizers;
    NSArray       *_subviewCache;
    float          _charge;
    NSInteger      _tag;
    UIViewController *_viewDelegate;
    NSString        *_backgroundColorSystemColorName;
    struct{
        unsignedintuserInteractionDisabled:1;
        unsignedintimplementsDrawRect:1;
        unsignedintimplementsDidScroll:1;
        unsignedintimplementsMouseTracking:1;
//...此处太长省略部分
        unsignedintstayHiddenAwaitingReuse:1;
        unsignedintstayHiddenAfterReuse:1;
        unsignedintskippedLayoutWhileHiddenForReuse:1;
    } _viewFlags;
}
 
+ (Class)layerClass;                        // default is [CALayer class]. Used when creating the underlying layer for the view.
 
- (id)initWithFrame:(CGRect)frame;         // default initializer
 
@property(nonatomic,getter=isUserInteractionEnabled)BOOLuserInteractionEnabled;  // default is YES. if set to NO, user events (touch, keys) are ignored and removed from the event queue.
@property(nonatomic)                                NSIntegertag;                // default is 0
@property(nonatomic,readonly,retain)                 CALayer  *layer;              // returns view's layer. Will always return a non-nil value. view is layer's delegate
 
@end
       
       




首先我们看到他@了UIEvent,UIWindow,UIViewController,UIColor,UIGestureRecognizer和CALayer,说明任何一个控件在继承UIView的同事,也"认识"了他们,并且我们在API文档中看到这样一句话:
Although there are many good reasons to subclass UIView, it is recommended that you do so only when the basic UIView class or the standard system views do not provide the capabilities that you need. Subclassing requires more work on your part to implement the view and to tune its performance.




即任何可视化的内容都建议你去继承UIView这个母类(你能想到的可见内容是哪些?看看他们的头文件是否都是直接或者间接继承自UIView?能举出反例吗?),如果你非要一意孤行想要自己实现,或者现有的这个基类在某些功能上面不能满足你,那你可能需要花费一大堆工作来完成了(UIView继承自UIResponder,UIResponder继承自NSObject,NSObject已经是OC里面的上帝了,没有父类了),如果你动手能力非常强,当然也可以自己继承NSObject或者UIResponder来实现可视化的Object!。


一路看下来,NSObject在UIResponder父类的基础上添加了一大堆属性和类方法、实例方法。但是与UIButton、UIScrollview、UIlabel等衍生类来讲,即使在这一个层级上多出来了这么多东西,但是他已经是视图级别的最小单位,占用内存最小的类了。

当然,严格意义上来讲,UIView也不算是最小单位,因为UIView的成员:CALayer比他更小,但是如果没有一个UIView来承载他,那么任何Layer的实现都是不能被用户看到的,所以谈到可视化的内容,我们可以理解为CALayer是人的细胞,而UIView是我们的皮肤,如果我们外在的皮肤这个容器,那么细胞是无法单独存在的。

我们先看看他的父类UIResponser做了什么封装?
//
//  UIResponder.h
//  UIKit
//
//  Copyright (c) 2005-2012, Apple Inc. All rights reserved.
//
 
#import 
         
         
          
          
#import 
          
          
           
           
#import 
           
           
            
            
 
NS_CLASS_AVAILABLE_IOS(2_0)@interfaceUIResponder : NSObject{
  @private
}
 
- (UIResponder*)nextResponder;
 
- (BOOL)canBecomeFirstResponder;   // default is NO
- (BOOL)becomeFirstResponder;
 
- (BOOL)canResignFirstResponder;   // default is YES
- (BOOL)resignFirstResponder;
 
- (BOOL)isFirstResponder;
 
// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application.  Failure to
// do so is very likely to lead to incorrect behavior or crashes.
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent *)event;
 
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
 
- (void)remoteControlReceivedWithEvent:(UIEvent *)event NS_AVAILABLE_IOS(4_0);
 
- (BOOL)canPerformAction:(SEL)action withSender:(id)senderNS_AVAILABLE_IOS(3_0);
@property(nonatomic,readonly)NSUndoManager*undoManager NS_AVAILABLE_IOS(3_0);
 
@end
           
           
          
          
         
         




这里我们最熟悉的应该属touches系列函数了吧!那么你看,UIView在继承他父类的时候已经继承了这一组实例方法,所以我们自定义任何一个UIView的时候,不需要任何的导入和声明就可以直接联想出来touchesBegan系列函数,就是UIView的功劳,我们只需要去重载父类的实例方法就可以了。下面的两个接口我只想提出一个大家可以了解了解:
@interfaceUIResponder (UIResponderInputViewAdditions)
 
// Called and presented when object becomes first responder.  Goes up the responder chain.
@property(readonly, retain) UIView *inputView NS_AVAILABLE_IOS(3_2);           
@property(readonly, retain) UIView *inputAccessoryView NS_AVAILABLE_IOS(3_2);
 
// If called while object is first responder, reloads inputView and inputAccessoryView.  Otherwise ignored.
- (void)reloadInputViewsNS_AVAILABLE_IOS(3_2);


前面两个成员,我们在做自定义键盘的时候就得靠他们!一个是输入法本身,另一个是协助用的,所以我们可以对任何view都可以获取到这两个成员,虽然仅仅在UITextField和UITextView上面调用才有明显的作用。再看下面这个实例方法,从字面上解读是重载输入视图,那么其实作用就相当于在我们重新制定了一个view的inputView之后,键盘不会立刻更新,而只有再下一次获得焦点的时候去更新(这里可以理解为他SDK内部没有发送键盘类型切换通知并且重绘keyboard),所以普通的做法是先resignFirstResponder,然后马上又becomeFirstResponder。而聪明的做法是直接调用:
[someTextField setInputView:myKeyBoard];
[someTextField reloadInputViews];

如果你现在用的也是前面这种方式,不妨试试调用这个方法,你会上瘾的。


OK,我们摸清楚了UIView的家族史之后,我们再来细看UIView本身的一些特征。私有成员我们就不讨论了,及时你想改也改不了,除非你自己继承NSObject来写一个你自己的UIView 2.0。

首先我们看看这5行:

@property(nonatomic,readonly,getter=isKeyWindow)BOOLkeyWindow;
- (void)becomeKeyWindow;                              // override point for subclass. Do not call directly
- (void)resignKeyWindow;                              // override point for subclass. Do not call directly
 
- (void)makeKeyWindow;
- (void)makeKeyAndVisible;                            // convenience. most apps call this to show the main window and also make it key. otherwise use view hidden property


makeKeyAndVisible很熟悉对吧?我们记得哪儿调用过?是不是appDelegate中的那个UIWindow调用过呢?

是的,你仔细看看,UIWindow也是UIView的子类,所以这个方法是给我们的window用的,一般来说一个iOS应用只有一个Window,我们在appDelegate中初始化他并让他变为响应的接受者和Z轴最高的window(其他程序也有其他的window,所以我们要让他置顶)。
我在问答板块问过这个问题,这句话的作用是什么,这里我不卖关子了,首先这个方法按词义来讲分为两个目的:makeKey    makeVisible
makeKey的作用就在于发送一个全局通知给NSNotificationCenter,对应的其他object好知道这个window现在处于第一优先响应级别。
makeVisible的作用就相当于我们调整他的Z轴,让他处于所有window的顶部,让我们只能看到他。
所以说这样来讲,我发布的那个问题,我说大家的回答老师不够满意,应该能够理解了吧。我们不妨来做个试验,创建一个单视图工程,在默认的VC的视图中拖拽一个textfiled然后直接运行:
效果我就不说了,现在我们来做这样一件事情。
修改appdegate:

//不置顶Z轴,而仅仅设置为优先响应
    [self.window makeKeyWindow];
     
    //[self.window makeKeyAndVisible];



我们再运行下,看看还能看到我们的内容吗?

1961FA86-8EFA-4727-9F03-0F437078C4D9.png

这就是因为window现在只是处于makeKey但是没有visible(可见)。那么不可见,但是我makeKey了,大家肯定要问我怎么知道这个window是不是第一响应啊?
我们不妨来写这样一段代码再次改写这个方法:
//让window处于可见,但是不处于可响应状态
 [self.window setHidden:NO];
 
// [self.window makeKeyWindow];
 //[self.window makeKeyAndVisible];
 returnYES;



在运行下看看,是不是又出来了?注意这里他提供的make方法只有2个都选,或者只选makekey,没有单独的makeVisible成员方法,但是他有这样一段注释:
- (void)makeKeyAndVisible;                            // convenience. most apps call this to show the main window and also make it key. otherwise use view hidden property


所以单独设置为visible需要显示地调用setHidden:NO来实现。
但是大家再尝试打字,还能打字吗?你会发现不管你怎么按都没有反应。这是因为我们的系统没有收到当前这个window处于可接受响应的这个通知(notifacation),所以你是不能输入字给他接受的。
所以self.window makeKeyAndVisible他做了几件事情?两件事情对吧。

接下来我们还会看到UIWindow中还有一大堆全局NSString常量,这些都是我们经常用到的通知。好,我们再回到UIView中,现在我们捋一捋,一个app只能有一个Window,而在这个window上面可以放任何view!也可以通过实例方法setRootViewController:xxxController来在window上面加内容。这两种方式区别仅仅是一种是mvc的,一种不是mvc的而已(前者缺少了controller),所以我们现在还没有谈论到controller,暂时先不提这个角色的作用。我们尝试做这样一件事情:
//将controller注释起来,我们通过方式1在window上面添加内容
//    self.viewController = [[[WDViewController alloc] initWithNibName:@"WDViewController" bundle:nil] autorelease];
//    self.window.rootViewController = self.viewController;
//    直接在window上添加一个label
    UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(0, 200, 300, 50)];
    [label setText:@"谢谢大家支持Objective-C程序员论坛!"];
    [self.window addSubview:label];
     
    [self.window makeKeyAndVisible];
    returnYES;






运行下?hoho,这就是没有Controller的单纯的2个view?nonono,其实controller的意义在于处理逻辑,你说2个view怎么会没有逻辑就被你看到而不混乱呢?那么这里我们的label不再有一个视图控制器(viewController)来控制逻辑,但是model view 任然没有混乱(label上面的text也可以理解为简单的data model....),这个时候我们能不能理解为appDelegate现在充当了控制器的角色呢?是的,其实任何时候appDelegate他比我们的viewController可以实现更多的逻辑

这个时候我们再不仿假设,这个时候我不想让我的这个view直接被appDelegate这种朝廷重臣来管理,这样太麻烦了,他只是一个label而已!!
所以这里我们可以创建一个NSObject子类LableManager,然后给他一个属性,叫做managedLabel,然后在appDelegate中初始化这个Object:


//
//  WDLableManager.h
//  UIWindowDemo
//
//  Created by Reese on 13-8-21.
//  Copyright (c) 2013年 Reese@dctrain. All rights reserved.
//
 
#import 
           
           
            
            
 
@interfaceWDLableManager : NSObject
//设置一个管理的label对象[/align][align=left]@property (nonatomic,assign) UILabel *managedLabel;
//开始管理这个label
-(void)startManageLabel;
@end







UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(0, 200, 300, 50)];
   //[label setText:@"谢谢大家支持Objective-C程序员论坛!"];
   [self.window addSubview:label];
    
    
   WDLableManager *manager=[[[WDLableManager alloc]init]autorelease];
   [manager setManagedLabel:label];
   [manager startManageLabel];
    
   [self.window makeKeyAndVisible];
   return YES;
           
           



现在发生了一件非常有趣的事情!!现在manager来充当了view的管理者!!所以现在我们的数据绑定逻辑可以在manager中执行了!
那么我现在实现这个管理者的.m文件:
//
//  WDLableManager.m
//  UIWindowDemo
//
//  Created by Reese on 13-8-21.
//  Copyright (c) 2013年 Reese@dctrain. All rights reserved.
//
 
#import "WDLableManager.h"
 
@implementationWDLableManager
 
 
-(void)startManageLabel
{
    [self.managedLabel setText:@"大家好。欢迎大家支持墨半成霜!"];
}
@end

?


ok运行看看?
其实到这里,我们相当于没有使用viewController,但是使用了一个自己写的object来充当了viewController的角色而已!除了做数据绑定,你还可以监听你管理的view什么时候准备好让你管理了?(view did load) 什么时候出现在屏幕上了?(view will appear)等等等等
所以视图控制器的角色是什么?他其实就是一个管理者,可以分管一个view,当然也可以分管这个view上的所有的subviews!所以我们的controller中的self.view和我们这里的managedLabel是不是一个概念呢?

好的 下一回我们再细细分析!



610D84CF-81B8-4591-94A7-5F252B7FFF1B.png


69BB3CE0-3DB8-4E56-9EB2-499BAEB39084.png


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值