IOS学习笔记-perFormSelector,RunLoop,Debug神器,UIResponder,Cocoa Touch事件处理

时间:2022-11-23 13:20:02

1,perFormSelector

当程序中需要一些比较耗时的操作的时候,这时候我们可以开辟多个线程,让子线程去执行比较耗时的操作,主线程继续相应用户的其它操作。比如下载图片等。

1)获取网络图片显示出来的方法:

// 1. url, 确定一个网络上的资源路径
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/5366d0160924ab1828b7c95336fae6cd7b890b34.jpg"];

// 2. 通过url可以下载对应的网络资源, 网络资源传输的都是二进制
NSData *data = [NSData dataWithContentsOfURL:url];

// 3. 二进制转成图片
UIImage *image = [UIImage imageWithData:data];

2)控制器中,实现这个方法时候,当触摸屏幕时候会自动调用这个方法。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{}
 

3)关于perFormSelector

perFormSelector是编译时候动态的去找方法的,在编辑时候是不会进行检查的,如果直接调用方法,会检查方法是否存在,而如果使用perFormSelector调用方法时候系统并不会去检查,如果运行时候方法不存在程序就会崩溃,所以有时候使用了performSelector方法也需要使用 - (BOOL)respondsToSelector:(SEL)aSelector; 来判断。

几个常用的perFormSelector方法和用法:

使用performSelector执行一个方法:
//1,某个对象执行一个方法 属于NSObject类
- (id)performSelector:(SEL)aSelector;
//2,某个对象执行一个方法,含有一个参数 属于NSObject类
- (id)performSelector:(SEL)aSelector withObject:(id)object;
//3,某个对象执行一个方法,含有两个参数 属于NSObject类
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
延迟执行相关:
//4,延迟执行一个方法
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
//5,延迟执行
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

线程相关:
//6,到主线程上去执行一个方法,参数,是否等待完成
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
//7,和6一样
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;

//8,在指定的线程上去执行一个方法
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
//9,在指定的线程上执行一个方法
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// equivalent to the first method with kCFRunLoopCommonModes
//10,开辟新的线程执行一个方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);
// waitUntilDone :表示是否等待 @selector(setImage:) 方法执行完成

 

一个详解的博客: http://blog.csdn.net/lengshengren/article/details/12905581

4)线程间通信

在IOS中,更新UI的操作只能在主线程中完成,如果在子线程中操作很可能会出问题。但是在编程中,有时候需要在子线程中执行耗时操作,耗时操作执行结束之后得到数据需要更新到UI中这时候,比如:  

比如使用 performSelectorInBackground 开辟了一个子线程,执行一个下载数据的操作,然后下载完成之后需要将数据更新到UI中,那么可以在子线程中调用 performSelectorOnMainThread 来到主线程中执行更新操作,这就是线程间的通信。

2,RunLoop

这里讲的太好了,详情请参阅: http://www.cocoachina.com/ios/20150601/11970.html  (已下载保存)

笔记:

2.1 OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF/CF-855.17.tar.gz 下载到整个 CoreFoundation 的源码。为了方便跟踪和查看,你可以新建一个 Xcode 工程,把这堆源码拖进去看。

2.2 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。也就是说,每一个线程都有一个与之对应的Runloop,默认情况下,主线程的Runloop是开启的,子线程的Runloop是关闭状态的。

3,自动释放池

1)

(alloc new retain copy) release  通过这几个方法产生的对象在子线程中需要受到的加到autoreleasepool中,其它方法产生的对象系统会自动的放到自动释放池中去。

//如果没有alloc new retain copy而产生了新对象,里面都是autorelease

//子线程默认情况下是不运行的,所以并不会自动的创建自动释放池。

首先,在子线程中开辟的空间,系统并不会自动的放到自动释放池中,所以需要手动的加入到自动释放池。主线程中开辟的空间系统会自动的加入到自动释放池中去。

如下所示,如果需要使用一个非常非常大的循环,如果直接使用循环,会造成内存暴涨,导致程序崩溃,这时候可以在for循环内部加上一个自动释放池,每次for执行结束都执行到自动释放池外部,自动释放池中的内存将被释放,不会导致内存暴增。

int lagerNum = 1024 * 1024 * 2;

// 有问题,解决办法?

// 1. 在for内部加一个@autoreleasepool,但是性能不好,因为每一个循环都创建和销毁一个池子
// 2. 如果循环的次数不是很大,可以考虑在for循环外面添加autoreleasepool
for (int i = 0; i < lagerNum; i++) {
@autoreleasepool {

// NSLog(@"%d", i);

NSString * str = [NSString stringWithFormat:@"Hello"];
// NSLog(@"%p", str);

str = [str uppercaseString];
// NSLog(@"%p", str);

str = [str stringByAppendingFormat:@"- %@",@"World!"];
// NSLog(@"%p", str);

// (alloc new retain copy) release
// 如果没有alloc new retain copy而产生了新对象,里面都是autorelease
}
}

2)自动释放池的创建和销毁

什么时候创建

1. 程序刚启动的时候,也会创建一个自动释放池
2. 产生事件以后,运行循环开始处理事件,就会创建自动释放池
什么时候销毁的
1. 程序运行结束之前销毁
2. 事件处理结束以后,会销毁自动释放池。
3. 还有在池子满的时候,也会销毁
4. 程序运行到自动释放池之外的时候

4,debug神器,C中的宏定义

int main()
{
printf( "The file is %s.\n", __FILE__ ); //<span style="font-family: Arial, Helvetica, sans-serif;">The file is debug.c.</span>
printf( "The date is %s.\n", __DATE__ ); //<span style="font-family: Arial, Helvetica, sans-serif;">The date is Jun 6 2012</span>
printf( "The time is %s.\n", __TIME__ ); //The time is 09:36:28.
printf( "This is line %d.\n", __LINE__ ); //This is line 15.
printf( "This function is %s.\n", __func__ ); //This function is main.

why_me();

return 0;
}

void why_me()
{
printf( "This function is %s\n", __func__ ); //This function is why_me
printf( "The file is %s.\n", __FILE__ ); //The file is debug.c.
printf( "This is line %d.\n", __LINE__ ); //This is line 27.
}<pre name="code" class="objc">


5,UIResponder

UIResponder中的几个方法

<pre name="code" class="objc">UIResponder处理事件的主要方法
    触摸事件    
- (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;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
    远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;<pre name="code" class="objc">
//其它的方法- (BOOL)canBecomeFirstResponder; // default is NO- (BOOL)becomeFirstResponder;- (BOOL)canResignFirstResponder; // default is YES- (BOOL)resignFirstResponder;- (BOOL)isFirstResponder;

 
 
UIView和UIViewController都直接继承自UIResponder,所以他们都可以接收和添加和处理事件,他们都有这几个touch事件,因为这些事件处于他们共同的父类。 


6,Cocoa Touch事件处理流程--响应者链

1)对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:
<1>、触屏事件(Touch Event)
<2>、运动事件(Motion Event)
<3>、远端控制事件(Remote-Control Event)

这里将以触屏事件(Touch Event)为例,来说明在Cocoa Touch框架中,事件的处理流程。

2),响应者链的处理流程

在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”。

首先,当发生事件响应时,必须知道由谁来响应事件。在IOS中,由响应者链来对事件进行响应,所有事件响应的类都是UIResponder的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。当发生事件时,事件首先被发送给第一响应者,第一响应者往往是事件发生的视图,也就是用户触摸屏幕的地方。事件将沿着响应者链一直向下传递,直到被接受并做出处理。一般来说,第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象viewcontroller(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow对象)再到程序(UIApplication对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。

一个典型的相应路线图如:

First Responser -- > The Window -- >The Application -- > App Delegate

正常的响应者链流程经常被委托(delegation)打断,一个对象(通常是视图)可能将响应工作委托给另一个对象来完成(通常是视图控制器ViewController),这就是为什么做事件响应时在ViewController中必须实现相应协议来实现事件委托。在iOS中,存在UIResponder类,它定义了响应者对象的所有方法。UIApplication、UIView等类都继承了UIResponder类,UIWindow和UIKit中的控件因为继承了UIView,所以也间接继承了UIResponder类,这些类的实例都可以当作响应者。


先来说说响应者对象(Responder Object),顾名思义,指的是有响应和处理事件能力的对象。响应者链就是由一系列的响应者对象构成的一个层次结构。
UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、 UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。图一展示了响应者链的基本构成:

IOS学习笔记-perFormSelector,RunLoop,Debug神器,UIResponder,Cocoa Touch事件处理

从图一中可以看到,响应者链有以下特点:
1、响应者链通常是由视图(UIView)构成的;
2、一个视图的下一个响应者是它视图控制器(UIViewController)(如果有的话),然后再转给它的父视图(Super View);
3、视图控制器(如果有的话)的下一个响应者为其管理的视图的父视图;
4、单例的窗口(UIWindow)的内容视图将指向窗口本身作为它的下一个响应者,需要指出的是,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,因此整个响应者链要简单一点;
5、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环。

3),事件分发(Event Delivery)

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个UIView对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。

UIWindow对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。

iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。
IOS学习笔记-perFormSelector,RunLoop,Debug神器,UIResponder,Cocoa Touch事件处理
UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。
hitTest:withEvent:方法的处理流程如下:
首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
若返回NO,则hitTest:withEvent:返回nil;
若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

IOS学习笔记-perFormSelector,RunLoop,Debug神器,UIResponder,Cocoa Touch事件处理

4)、说明

1、如果最终hit-test没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃;
2、hitTest:withEvent:方法将会忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。
3、我们可以重写hitTest:withEvent:来达到某些特定的目的,下面的链接就是一个有趣的应用举例,当然实际应用中很少用到这些。     http://blog.csdn.net/error/404.html?from=http%3a%2f%2fblog.csdn.net%2fzhaoguodongios%2farticle%2fdetails%2f44082821

5)触摸事件处理过程
  1.用户触摸后,系统先将事件对象(event)由上往下传递(父控件传给子控件),找到最合适的控件来处理事件(递归查找当前控件的最适合子控件)
  2.调用合适控件的touches相关方法
  3.如果调用了super的touches相关方法,就会将事件顺着响应者链条往上传递,传给上一个响应者
  4.接着就会调用上一个响应者的touches方法
  5.只要当前响应者的touches方法中调用了super的touches方法,还会继续往上递归调用,直到不再调用super的touches方法

什么是响应者链条?
  1>响应者链条是由多个响应者对象连接起来的链条
  2>响应者对象是指能够处理事件的对象,即继承UIResponder的对象
  3>利用响应者链条,能让多个控件处理同一个触摸事件
  4>简要叙述响应者链条的往上传递,找到上一个响应者(nextResponder)

6)响应者对象(Responsder Object)

响应者对象是能够响应并且处理事件的对象,UIResponder是所有响应者对象的父类,包括UIApplication、UIView和UIViewController都是UIResponder的子类。也就意味着所有的View和ViewController都是响应者对象。


7,事件的传递过程

0),比如用户使用UIButton控件的addTarget: 方法向运行循环中注册了一个事件处理,设置了处理事件的类,SEL方法和处理类型。如下图,向运行循环注册事件,如果点击btn,执行self中的click方法;

1),用户点击这个UIButton,最先获取点击事件的是手机的电容屏幕,屏幕得到点击事件传给操作系统,操作系统得到点击事件,传递给正在运行的Application;(UIApplication和UIWindow继承自UIView)

2)Application得到点击事件开始查找这个点击事件发生在那个view上,从Application开始向子控件中查找,通过调用view的hitTest:withEvent:方法,判断触发事件的子控件是否是当前的控件;

3)向下查找过程 The Application -- > The Window -- >First Responser;

4)找到第一响应者之后,开始去runloop中找这个第一响应者是不是在runloop中注册了事件,如果注册有事件,那么将直接执行注册的事件,事件的传递到此结束;

5)如果btn没有注册事件,那么这个单击事件会传递给btn的superView,如果父控件可以处理,就处理掉,不再传递,如果不能处理继续向父控件传递,一直传递到UIWindow----->uiApplication,如果UIApplication也不能处理这个事件,那么这个事件将会被丢弃。

IOS学习笔记-perFormSelector,RunLoop,Debug神器,UIResponder,Cocoa Touch事件处理

UIView属性clipsTobounds

如题,有两个view: view1,view2:
view1添加view2到其中,如果view2大于view1,或者view2的坐标不在view1的范围内,view2是盖着view1的,意思就是超出的部份也会画出来.
UIView有一个属性,clipsTobounds 默认情况下是NO,
如果,我们想要view2把超出的那部份隐藏起来的话,就得改变它的父视图也就view1的clipsTobounds属性值。
view1.clipsTobounds = YES;