1.什么是runtime ? 什么是runloop? 两者有什么区别?分别在什么情况下用到?
Runloop是事件接收和分发机制的一个实现。
Runloop提供了一种异步执行代码的机制,不能并行执行任务。
在主队列中,Main RunLoop直接配合任务的执行,负责处理UI事件、定时器以及其他内核相关事件。
RunLoop的主要目的:保证程序执行的线程不会被系统终止。
什么时候使用Runloop ? 当需要和该线程进行交互的时候才会使用Runloop.
每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后去run它。
一般情况下我们是没有必要去启用线程的RunLoop的,除非你在一个单独的线程中需要长久的检测某个事件。
主线程��默认有Runloop。当自己启动一个线程,如果只是用于处理单一的事件,则该线程在执行完之后就退出了。所以当我们需要让该线程监听某项事务时,就得让线程一直不退出,runloop就是这么一个循环,没有事件的时候,一直卡着,有事件来临了,执行其对应的函数。
RunLoop,正如其名所示,是线程进入和被线程用来相应事件以及调用事件处理函数的地方.需要在代码中使用控制语句实现RunLoop的循环,也就是说,需要代码提供while或者for循环来驱动RunLoop.
在这个循环中,使用一个runLoop对象[NSRunloop currentRunloop]执行接收消息,调用对应的处理函数.
Runloop接收两种源事件:input sources和timer sources。
input sources 传递异步事件,通常是来自其他线程和不同的程序中的消息;
(输入源有3种类型)Selector源:如例子按钮事件中的performSelector,当在子线程中执行Selector时,目标线程必须RunLoop处于开启状态,不然Selector就一直处于休眠状态;
基于端口的输入源:就是之前提到的Source1。通过内置的端口相关的对象和函数,创建配置基于端口的输入源。 例如可以使用NSPort的方法把该端口添加到 RunLoop;
自定义输入源:创建custom输入源,必须使用Core Foundation里面的CFRunLoopSourceRef类型相关的函数来创建,并自定义自己的行为和消息传递机制;
timer sources(定时器) 传递同步事件(重复执行或者在特定时间上触发)。
如:<pre>self.autoDismissTimer = [NSTimer timerWithTimeInterval:8.0 target:self selector:@selector(autoDismissView:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.autoDismissTimer forMode:NSDefaultRunLoopMode];
</pre>
除了处理input sources,Runloop 也会产生一些关于本身行为的notificaiton。注册成为Runloop的observer,可以接收到这些notification,做一些额外的处理。(使用CoreFoundation来成为runloop的observer)。
Runloop工作的特点:
1>当有时间发生时,Runloop会根据具体的事件类型通知应用程序作出相应;
2>当没有事件发生时,Runloop会进入休眠状态,从而达到省电的目的;
3>当事件再次发生时,Runloop会被重新唤醒,处理事件.
提示:一般在开发中很少会主动创建Runloop,而通常会把事件添加到Runloop中.
CFRunLoopModeRef有5种形式:
kCFRunLoopDefaultMode
默认模式,通常主线程在这个模式下运行
UITrackingRunLoopMode
界面跟踪Mode,用于追踪Scrollview触摸滑动时的状态。
kCFRunLoopCommonModes
占位符,带有Common标记的字符串,比较特殊的一个mode;
UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不在使用。
GSEventReceiveRunLoop:内部Mode,接收系事件。
RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 )。编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
通知原理:
1、App 向 iOS 系统申请推送权限
2、iOS 系统向 APNs(Apple Push Notification Service) 请求手机 device token,并告诉 App,能接受推送的通知。
3、App 将手机的 device token 传给后端
后端向 APNs 推送通知
4、APNs 将响应通知推送给响应手机
3、ARC的内部实现
NSAutoreleasePool(只能在MRC下使用)
ARC背后的引用计数主要依赖于这三个方法:
retain 增加引用计数
release 降低引用计数,引用计数为0的时候,释放对象。
autorelease 在当前的auto release pool结束后,降低引用计数。
MRC机制时代
“谁开辟申请,谁及时合理释放” 面对自己申请的内存空间是要及时进行回收的:
不及时释放会造成什么结果?
对象存储在栈上,可能会大量的占用内存,内存不足造成程序闪退(也就是所说的内存泄露)
不合理释放会造成什么后果?
提前释放掉,倘若后面继续对该对象进行引用操作,会出现崩溃,出现EXC_BAD_ACCESS操作已经释放掉的对象的崩溃提示。(也就是所说的野指针)
4、备忘录设计模式 简介
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将对象恢复到原先保存的的状态。——《设计模式》
创建协议的原因是因为备忘录存储的数据不一定是什么类型的,我们要让备忘录能存储很多类型的数据,需要让需要备忘的类实现一套统一的接口。
协议包含两个方法,一个获取当前状态的方法,和一个恢复状态的方法。需要使用备忘录的类在这两个方法里实现存储和恢复的逻辑。
<pre>
import <Foundation/Foundation.h>
@protocol MementoCenterProtocol <NSObject>
/*
获得状态
/
-(id)getStatus;
/
设置状态
*/
-(void)setStatus:(id)data;
@end
</pre>
第2步 创建备忘录类
备忘录中心类只负责数据的存储和恢复。传过来的object必须是继承上面协议的对象,这样就可以调用协议里的方法获得需要备忘的数据了。类里有两个方法。
根据key保存状态
<pre>
/**
-
保存备忘录
-
@param object object
-
@param key key
/
+(void)saveMementoObject:(id<MementoCenterProtocol>)object withKey:(NSString)key{
if (object==nil || key==nil) {
return;
}
id data = [object getStatus];NSData *tmpData = [FastCoder dataWithRootObject:data];
// 进行存储
if (tmpData) {[[NSUserDefaults standardUserDefaults] setObject:tmpData forKey:key];
}
}
根据key获得object状态数据
/**
-
获得保存的状态数据
-
@param key key
-
@return 状态
/
+(id)getMementObjectWithKey:(NSString)key{
if (key==nil) {
return nil;
}
id data = nil;
NSData *tmpData = [[NSUserDefaults standardUserDefaults] objectForKey:key];if (tmpData) {
data = [FastCoder objectWithData:tmpData];
}
return data;
}
这两个方法里面用到了一个类FastCoder的方法,FastCoder是一个Cocoa object和object graph的高性能二进制序列化格式,可以作为Property Lists和JSON的替代选择。FastCoder git地址。
</pre>
第3步 创建工具类
如果我们直接调用备忘录的方法的话,会暴露出我们实现的细节,也会暴露出我们要备忘的object。所以我们创建一个工具类,来再封装一层。这样我们就不会暴露具体的实现细节了。
<pre>
/**
- 备忘状态
- @param key 键值
*/
- (void)saveStateWithKey:(NSString *)key;
/**
- 恢复状态
- @param key 键值
*/
-
(void)recoverFromStateWithKey:(NSString *)key;
实现: -
(void)saveStateWithKey:(NSString *)key{
if (key==nil) {
return;
}id<MementoCenterProtocol> obj = (id<MementoCenterProtocol>)self;
if ([obj respondsToSelector:@selector(getStatus)]) {
[MementoCenter saveMementoObject:obj withKey:key];
}
} -
(void)recoverFromStateWithKey:(NSString *)key{
if (key==nil) {
return;
}id state = [MementoCenter getMementObjectWithKey:key];
id<MementoCenterProtocol> obj = (id<MementoCenterProtocol>)self;
if ([obj respondsToSelector:@selector(setStatus:)]) {
[obj setStatus:state];
}
}
</pre>
第4步 创建测试类
测试类里只有一个字符串属性,我们来恢复这个属性。测试类要实现上面协议的方法。
<pre>
-(id)getStatus{return self.valueStr.length? self.valueStr: @"当前运动:10.00KM";
}
-(void)setStatus:(id)data{
if (data) {
self.valueStr = (NSString*)data;
}
}
</pre>
第5步 测试
<pre>
TestView *texeView = [[TestView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:texeView];
texeView.valueStr = @"idage";
NSLog(@"恢复前 ===%@",texeView.valueStr );
//保存
[texeView saveStateWithKey:@"test"];
texeView.valueStr = @"idage163";
//恢复
[texeView recoverFromStateWithKey:@"test"];
NSLog(@"恢复后 ===%@",texeView.valueStr );
</pre>
这样我们就是实现了既能保存和恢复状态,也不会暴露保存和恢复的细节。
4、观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
注意:为什么用队列管理事件,而不用栈?
队列先进先出,能保证先产生的事件先处理。栈先进后出。
事件分类
对于 iOS 设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:
触屏事件(Touch Event)
运动事件(Motion Event)
远端控制事件(Remote-Control Event)
所有事件响应的类都是 UIResponder 的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。当发生事件时,事件首先被发送给第一响应者,第一响应者往往是事件发生的视图,也就是用户触摸屏幕的地方。事件将沿着响应者链一直向下传递,直到被接受并做出处理。一般来说,第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象 ViewController(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow 对象)再到程序(UIApplication 对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。
一个典型的事件响应路线如下:
First Responser --> The Window --> The Application --> nil(丢弃)
Cocoa 中封装了 NSThread, NSOperation, GCD 三种多线程编程方式,他们各有所长。
NSThread
NSThread 是一个控制线程执行的对象,通过它我们可以方便的得到一个线程并控制它。NSThread 的线程之间的并发控制,是需要我们自己来控制的,可以通过 NSCondition 实现。它的缺点是需要自己维护线程的生命周期和线程的同步和互斥等,优点是轻量,灵活。
NSOperation
NSOperation 是一个抽象类,它封装了线程的细节实现,不需要自己管理线程的生命周期和线程的同步和互斥等。只是需要关注自己的业务逻辑处理,需要和 NSOperationQueue 一起使用。使用 NSOperation 时,你可以很方便的设置线程之间的依赖关系。这在略微复杂的业务需求中尤为重要。
GCD
GCD(Grand Central Dispatch) 是 Apple 开发的一个多核编程的解决方法。在 iOS4.0 开始之后才能使用。GCD 是一个可以替代 NSThread 的很高效和强大的技术。当实现简单的需求时,GCD 是一个不错的选择。
同步和异步添加,与队列是串行队列和并行队列没有关系。可以同步地给并行队列添加任务,也可以异步地给串行队列添加任务。同步和异步添加只影响是不是阻塞当前线程,和任务的串行或并行执行没有关系
如果在任务 block 中创建了大量对象,可以考虑在 block 中添加 autorelease pool。尽管每个 queue 自身都会有 autorelease pool 来管理内存,但是 pool 进行 drain 的具体时间是没办法确定的。如果应用对于内存占用比较敏感,可以自己创建 autorelease pool 来进行内存管理
Dispatch Queue 本身是线程安全的,换句话说,你可以从系统的任何一个线程给 queue 添加任务,不需要考虑加锁和同步问题
避免在任务中使用锁,如果使用锁的话可能会阻碍 queue 中其他 task 的运行
不建议获取 dispatch_queue 底层所使用的 thread 的有关信息,也不建议在 queue 中再使用 pthread 系函数
5.OC 和 Swift 中是如何用枚举的?
我们大体可以将枚举分为两种方式,一种是普通的枚举,还有一种不普通的枚举。第二种应该说是枚举中的各种可能同时存在,如果换成代码的语言就是枚举中的各个值有可能进行位操作运算的
OC中将这两种分别用NS_ENUM与NS_OPTIONS来区分,而Swift中则用普通的enum以及OptionSetType来搞定
首先我们先来看看NS_ENUM以及NS_OPTIONS是个什么东东吧~
<pre>#if (__cplusplus && __cplusplus >= 201103L
&& (__has_extension(cxx_strong_enums)
|| __has_feature(objc_fixed_enum))
) ||
(!__cplusplus && __has_feature(objc_fixed_enum))
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#if (__cplusplus)
#define NS_OPTIONS(_type, _name) _type _name; enum : _type
#else
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
else
#define NS_ENUM(_type, _name) _type _name; enum
#define NS_OPTIONS(_type, _name) _type _name; enum
endif</pre>
- 枚举介绍
enum 类型
<pre>typedef enum
{
TMEnumTestOne,
TMEnumTestTwo,
} TMEnumTest;
enum TMEnumTest
{
TMEnumTestOne,
TMEnumTestTwo,
};
typedef enum TMEnumTest TMEnumTest;
这两种写法,将枚举描述为一个类型TMEnumTest。
但是这种写法也有局限性,在作为函数参数时,如果传入NSInteger类型的数据,则需要强制转换,否则会有类型不匹配的警告,所以这种方式也不推荐。如下图,
</pre>

老式enum写法
<pre>enum
{
TMEnumTestOne,
TMEnumTestTwo,
};
typedef NSUInteger TMEnumTest;</pre>
但是这种写法不会检查枚举的正确性,是好是坏需要自己判断,如下图。本人推荐这种写法。

新式enum写法
<pre>typedef NS_ENUM(NSUInteger, TMEnumTest)
{
TMEnumTestOne,
TMEnumTestTwo,
};
typedef NS_OPTIONS(NSUInteger, TMEnumTest)
{
TMEnumTestOne = 0,
TMEnumTestTwo = 1 << 0,
};
</pre>
这种写法同样作为函数参数时,传入的值不需要进行强制类型转换。但是也同样不会检查枚举的正确性,和 老式enum写法 一样,如下图

但是不同的是,新式enum写法 在使用switch时,会进行值的检测,如果枚举中无此值,则会弹出警告,如下图。这种方式见仁见智了。

NS_ENUM和NS_OPTIONS本质是一样的,仅仅从字面上来区分用途。NS_ENUM是通用情况,NS_OPTIONS一般用来定义位枚举。
<pre>
NS_OPTIONS一般用来定义位移相关操作的枚举值,我们可以参考UIKit.Framework的头文件,可以看到大量的枚举定义。
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,//默认从0开始
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
</pre>
-
枚举最大值
对于一般的枚举,要获取枚举的最大值是很难的,因为随着枚举的扩充,最大值在不断变化,这时推荐使用一个固定的枚举表示最大值,例如:
<pre>typedef NS_ENUM(NSUInteger, TMEnumTest)
{
TMEnumTestOne,
TMEnumTestTwo,TMEnumTestMax, // 表示最大值
};
这样就可以使用TMEnumTestMax表示枚举TMEnumTest的最大值了。
</pre>
- 位枚举
位枚举是一种特殊的枚举,在apple的类中使用的很多,例如NSStringCompareOptions等。
<pre>typedef NS_OPTIONS(NSUInteger, TMEnumTest)
{
TMEnumTestOne = 0, // 0
TMEnumTestTwo = 1 << 0, // 1
TMEnumTestThree = 1 << 1, // 2
TMEnumTestFour = 1 << 2, // 4
};</pre>
位枚举的特点是可以使用位运算来处理枚举值,实际使用中可以用一个变量存储多个枚举值,表示互不影响的多个设置。
<pre>- (void) test
{
TMEnumTest test = TMEnumTestTwo|TMEnumTestThree; // 3
/* 添加TMEnumTestFour到test中(如test已经包含TMEnumTestFour,则test值不变)
当然这里也可以使用test += TMEnumTestFour,
但是注意 + 不能在test中已经包含TMEnumTestFour的情况下使用。
*/
test |= TMEnumTestFour; // 7
/* 将TMEnumTestThree从test中去除(如test不包含TMEnumTestThree,则test值不变)
当然这里也可以使用test -= TMEnumTestThree,
但是注意 - 不能在test中不包含TMEnumTestThree的情况下使用。
*/
test &= ~TMEnumTestThree; // 5
// 判断 TMEnumTestFour枚举 是否被包含
if ((test & TMEnumTestFour) == TMEnumTestFour)
{
NSLog(@"YES");
}
/* 判断 TMEnumTestFour枚举 是否被包含
因为 (test & TMEnumTestFour) 的结果不是 0,就是TMEnumTestFour本身,
而 TMEnumTestFour > 0 ,所以可以使用简易的判断
*/
if (test & TMEnumTestFour)
{
NSLog(@"YES");
}
}</pre>
OC 结构体、枚举、类属性,这三者之间有什么区别吗,还有各自的应用在哪里?
结构体,美剧 这不是oc的东西,大多数语言都有的数据组织结构;而类就是一个指向结构体的指针,所以类属性,也就是结构体中的属性。
结构体往往是描述一个或是一类具体的东西的可以包括属性、行为。
枚举一般是用来列出某一个值的所有可选项,比如摇杆支持的方向 上、下、左、右; 那么选值的时候可以单选 也可以多选(上下, 上左下右), 也是限制取值范围的一种手段。
(本文章,每晚会更新一到两个知识点)有不对的地方欢迎指正。
网友评论