-
- runtime(√√√√√√√√√√√√√√√√√√)
- runloop(√√√√√√√√√√√√√√√√√√)
- category(√√√√√√√√√√√√√√√√√√)
- protocol(√√√√√√√√√√√√√√√√√√√)
- extension(√√√√√√√√√√√√√√√√√√√)
- property(√√√√√√√√√√√√√√√√√√√√)
- 深复制与浅复制(√√√√√√√√√√√√√√√√)
- NSNotification(√√√√√√√√√√√√√√√√√√)
- Block(√√√√√√√√√√√√√√√√√√√√√√√√√)
- KVO&KVC(√√√√√√√√√√√√√√√√√√√√√)
- 多态性(√√√√√√√√√√√√√√√√√)
- 继承(√√√√√√√√√√√√√√√√√√√√)
- 垃圾回收机制(√√√√√√√√√√√√√√√√)
- 异常处理(√√√√√√√√√√√√√√√√√√√)
- 多线程()
- 作用
- 原理
- 实战应用
- 参考文章
- OC最开始是参考SmartTalk,而且smartTalk是一门动态语言,所以OC可以理解为在C的基础上扩展了runtime库,从而实现具有面向对象性。
- runtime是如何实现让OC具备面向对象性?
- 首先面向对象的一大特点就是把方法与属性捆绑在一起形成对象,所以OC就通过runtime,把C语言的结构体与函数捆绑到一起,到OC代码层我们是直观地编码一门面向对象语言,但实际上就是通过runtime在动态地调用C中结构体中的变量与相应的函数指针实现效果。
- runtime的实际主要做了什么事情?
- 封装:在runtime的封装下,对象的属性可以用结构体表示,而方法可以用C函数实现,并让程序运行时能实现创建、检查、修改类、对象方法。
- 消息传递:通过objc_sendmsg(),找到相应的函数指针与数据进行运算并返回结果。
- 类与对象
- 成员变量与属性
- 方法与消息
- 协议与分类
- …...
- 类与对象:
|
类在runtime中的表示:
struct
objc_class {
Class isa OBJC_ISA_AVAILABILITY; //
isa指针用于指向自身属于的类,在OC中对象-》类对象-》元类,所以isa指针在这里指向原类
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; //指向父类的指针
const
char
*name OBJC2_UNAVAILABLE; //类名
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; //方法列表指针
struct objc_cache *cache OBJC2_UNAVAILABLE; //缓存指针
struct
objc_protocol_list *protocols OBJC2_UNAVAILABLE; //协议指针
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
对象在runtime的表示:
/// Represents an instance of a class.
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class.
typedef
struct
objc_object *
id
;
|
根据上面图,我们就可以很清楚每当发送一个消息,这个消息在对象、类对象、元类中的传递与走向。
|
typedef
struct
objc_cache *Cache OBJC2_UNAVAILABLE;
#define CACHE_BUCKET_NAME(B) ((B)->method_name) #define CACHE_BUCKET_IMP(B) ((B)->method_imp) #define CACHE_BUCKET_VALID(B) (B) #ifndef __LP64__ #define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>> 2 ) & (mask)) #else #define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>> 3 )) & (mask)) #endif
struct
objc_cache {
unsigned
int
mask
/* total = mask + 1 */
OBJC2_UNAVAILABLE; //
unsigned
int
occupied OBJC2_UNAVAILABLE; //
Method buckets[ 1 ] OBJC2_UNAVAILABLE; //
};
关于Method的定义:
typedef
struct
objc_method *Method;
struct
objc_method {
SEL
method_name OBJC2_UNAVAILABLE; //SEL方法子,方法名经过hash后得到的一个字符串,与IMP是一一对应关系
char
*method_types OBJC2_UNAVAILABLE; //方法的类型,动态方法/静态方法
IMP
method_imp OBJC2_UNAVAILABLE; //IMP指针,指向具体的C实现函数
}
|
|
struct
objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE; //变量的数量
#ifdef __LP64__
int space OBJC2_UNAVAILABLE; #endif /* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; //数组,数组单元为
objc_ivar结构体
}
OBJC2_UNAVAILABLE;
/// An opaque type that represents an instance variable.
typedef
struct
objc_ivar *Ivar;
struct
objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; //变量名
char *ivar_type OBJC2_UNAVAILABLE; //变量类型
int ivar_offset OBJC2_UNAVAILABLE; //变量的内存位置偏移量
#ifdef __LP64__
int space OBJC2_UNAVAILABLE; #endif
}
|
|
struct
objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE; //废弃方法列表
int method_count OBJC2_UNAVAILABLE; //方法总数 #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE; //方法列表数组
} OBJC2_UNAVAILABLE;
/// An opaque type that represents a method in a class definition.
typedef
struct
objc_method *Method;
struct
objc_method {
SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE;
}
|
|
struct
objc_protocol_list {
struct objc_protocol_list *next; long count; Protocol *list[ 1 ];
};
#ifdef __OBJC__
@class Protocol; #else typedef struct objc_object Protocol;
#endif
|
|
typedef struct objc_property *objc_property_t;
typedef struct {
const char *name; // 特性名
const char *value; // 特性值
} objc_property_attribute_t;
|
|
struct
objc_category {
char *category_name OBJC2_UNAVAILABLE; //category名
char *class_name OBJC2_UNAVAILABLE; //类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; //实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; //类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; //协议方法列表
} OBJC2_UNAVAILABLE;
|
- 动态方法的解析:(允许动态添加实例方法/类方法,处理未知消息)
- 备用接收者:(把消息转发到具备处理该消息的别的对象里)
- 完整转发:()
|
- (
void
)forwardInvocation:(
NSInvocation
*)anInvocation;
- (
NSMethodSignature
*)methodSignatureForSelector:(
SEL
)aSelector;
|
|
Method swizzling:
通过改变SEL所对应的IMP,从而实现方法的统一修改。
|
|
JSPatch:
通过消息转发机制,实现通过JS往OC中动态添加、修改方法的实现。
|
|
多继承:
通过消息转发机制,实现类似多继承的效果
|
|
为Category添加属性:
在编译期,category不允许自己创建属性,但通过runtime可以实现。
|
|
插件:
不少的xcode插件都是通过利用runtime在运行时注入代码实现的。
|
|
万能控制器跳转:
|
|
自动归档与解档:
本来写归档与接档程序,常规写法就是一个个属性手动写入,但这样会写入大量无聊的重复代码,通过runtime的函数可以大大简化这个过程。
通过class_copyIvarList函数取出相应对象的属性,从而获得ivar_name&ivar_value从而实现归档,通过这种方式能实现像遍历NSDictionary与NSArray一样地去遍历一个对象。
|
|
对象转model
实现方法同上。
|
- runtime 运行时之一 类与对象
- runtime 运行时之二 成员变量与属性
- runtime 运行时之三 方法与消息
- runtime 运行时之四 method swizzling
- runtime 运行时之五 协议与分类
- runtime 运行时之六 拾遗
- Xcode Documentation and API —Objective-C Runtime Reference
- <objc/runtime.h>头文件
- apple open source runtime源码
- 作用
- 原理
- iOS系统用例
- 程序开发用例
- 参考文章
|
function
loop
(
)
{
initialize
(
)
;
do
{
var
message
=
get_next_message
(
)
;
process_message
(
message
)
;
}
while
(
message
!=
quit
)
;
}
|
- RunLoop
- 是由CFRunLoop的基础上封装得来的。
- CFRunLoopModeRef
- 用于描述与封装事件源的类
- 同一时间Runloop只能配置一个mode,若需要切换mode,需要退出在重新设置
- mode items(事件源的种类)
- CFRunLoopSourceRef:通用事件源(包括硬件信息、线程之间的交流等等)
- source0:(只包含回调指针)
- source1:(包含mash port 与回调指针)
- CFRunLoopTimerRef:基于时间的触发器事件源
- CFRunLoopOberverRef:监听Runloop的节点事件而触发的时间源
- CFRunLoopSourceRef:通用事件源(包括硬件信息、线程之间的交流等等)
|
关于source0与source1的区别:
source0需要手动触发,触发过程如下:
source1基于mash port,是自动触发的
mash port用于实现线程或进程之间的交流,一旦某个注册了mash port的runloop线程会自动active来处理相应的事件源头。
具体的mash port原理下面再阐述。
|
|
关于CFRunLoopTimerRef事件源头的补充说明:
但timer事件源被注册成功后,其中就是注册了相应的时间节点,timer事件源能主动触发runloop,但却不是保证事件源能在准确的注册时间点上运行,而且错过时间点的事件源会被忽略,直接执行下一个timer事件源,具体原理看下面的runloop运行逻辑阐述。
|
|
关于CFRunLoopOberverRef事件源头的补充说明:
CFRunLoopOberverRef,每一个Observer都包含一个回调IMP,主要监听以下事件。
typedef
CF_OPTIONS
(
CFOptionFlags
,
CFRunLoopActivity
)
{
kCFRunLoopEntry
=
(
1UL
<<
0
)
,
// 即将进入Loop
kCFRunLoopBeforeTimers
=
(
1UL
<<
1
)
,
// 即将处理 Timer
kCFRunLoopBeforeSources
=
(
1UL
<<
2
)
,
// 即将处理 Source
kCFRunLoopBeforeWaiting
=
(
1UL
<<
5
)
,
// 即将进入休眠
kCFRunLoopAfterWaiting
=
(
1UL
<<
6
)
,
// 刚从休眠中唤醒
kCFRunLoopExit
=
(
1UL
<<
7
)
,
// 即将退出Loop
}
;
|
|
struct
__CFRunLoopMode
{
CFStringRef
_name
;
// Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef
_sources0
;
// Set
CFMutableSetRef
_sources1
;
// Set
CFMutableArrayRef
_observers
;
// Array
CFMutableArrayRef
_timers
;
// Array
.
.
.
}
;
struct
__CFRunLoop
{
CFMutableSetRef
_commonModes
;
// Set
CFMutableSetRef
_commonModeItems
;
// Set<Source/Observer/Timer>
CFRunLoopModeRef
_currentMode
;
// Current Runloop Mode
CFMutableSetRef
_modes
;
// Set
.
.
.
}
;
|
- commonModes:想成为commonModes的modes,需要把name写入
- commonModeItems:设置相应的事件源,那么配置了commonModes的mode就会被统一设置相应的commonModelItems
- currentMode:当前Mode
- modes:runLoop所有的modes
- 图中的step2、step3只是标记任务将要处理,并不是立即处理。
- source0是在step4中执行,而timer、source1在step9中执行。
- Observer在线程的各种状态中被调用。
- 只有source0手动触发、source1 mash port、timer、外部手动的方式来唤醒线程。
- mach_msg()是实现整个runLoop的核心方法,具体原理下面会阐述。
|
typedef
struct
{
mach_msg_header_t
header
;
mach_msg_body_t
body
;
}
mach_msg_base_t
;
typedef
struct
{
mach_msg_bits_t
msgh_bits
;
mach_msg_size_t
msgh_size
;
mach_port_t
msgh_remote_port
;
mach_port_t
msgh_local_port
;
mach_port_name_t
msgh_voucher_port
;
mach_msg_id_t
msgh_id
;
}
mach_msg_header_t
;
mach_msg_return_t
mach_msg
(
mach_msg_header_t *
msg
,
mach_msg_option_t
option
,
mach_msg_size_t
send_size
,
mach_msg_size_t
rcv_size
,
mach_port_name_t
rcv_name
,
mach_msg_timeout_t
timeout
,
mach_port_name_t
notify
)
;
从其的定义可以得知,mach_msg是数据包的实现细节。
|
|
主线程与mainRunloop启动:
但启动app main函数时,iOS系统就会默认创建一个mainRunloop,其具备配置可看文档。
系统会默认注册了5个Mode:
1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
2. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
4: GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
5: kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
所以本质上有用的就只有
kCFRunLoopDefaultMode、
UITrackingRunLoopMode。
|
|
autoReleasePool:
苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
first observe:监听entry,监听是否进入Loop,并调用_objc_autoReleasePoolPush(),创建自动释放池,优先级别最高。
second observe:
|
|
事件响应:
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当有硬件事件发生时:
硬件时间-》IOKit-》IOHIDEvent-》springBoard-》通过mach port转发到相应进程的app->source1触发回调-》
_UIApplicationHandleEventQueue()进行内部分发。
|
|
手势识别:
_UIApplicationHandleEventQueue() 接收到一个手势-》cancel掉旧的手势-》
Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
|
|
界面更新:
通过监听
BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,将之前在上一轮runLoop处理中的界面处理执行相应的绘制与调整。
|
|
定时器与PerformSelector:
NSTimer其实就是CFRunloopTimerRef,把时间触发时间注册到runloop中等待执行。
而performSelector:After实际上就是生成一个NSTimer完成runloop的事件源注册。
所以以上实现都是基于runloop,若在非主线程以外调用,一定注意该线程是否开启了runloop。
|
|
网络请求:
NSURLConnection的工作过程:
|
|
GCD:
当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。
|
|
AFNetworking:
AFNetworking想是想可以在后台线程中接收回调,所以他通过创建一个独立的线程并为其添加runloop,注册了mach port,mach port的存在,让runloop不会被销毁,那么该线程就具备能监听网络回调的基础了。
|
|
AsyncDisplayKit:
UI线程一旦出现繁重的任务而导致界面的卡顿,这三类任务分别是排版、绘制、UI对象操作。
其中UI对象操作必须得在主线程中操作,而排版与绘制可以在后台中进行,所以该框架就是通过runloop建立私有线程,把排版与绘制的操作都放到后台线程去执行。
|
|
|
Category:
- 作用:
- 原理:
|
struct
objc_category {
char *category_name OBJC2_UNAVAILABLE; char *class_name OBJC2_UNAVAILABLE; struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; struct objc_method_list *class_methods OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
|
- 实践:
|
通过将实例方法或者类方法,按照相应分类从类文件中提取出来,横向切割类的组成,方便需要相应的实现部分,提高代码可维护性。
|
|
通过objc_setAssociatedObject或objc_getAssociatedObject的关联引用,实现在runtime中添加属性。
|
|
通过category把方法添加到类的规则,可以实现对系统库方法的重写。
|
|
category可以实现相应的协议方法,为类的主文件减负。
|
- 作用:
- 原理:
|
Protocol于runtime中的定义:
struct
objc_protocol_list {
struct
objc_protocol_list *next;
long count; Protocol *list[ 1 ];
};
#ifdef __OBJC__
@class Protocol; #else typedef struct objc_object Protocol;
#endif
所以本质上,protocol的底层表示就是一个方法列表,当对象A遵守了对象B的协议,那么对象B就能安全地通过对象A调用相应的方法来完成一定的功能,而在内部实现却好像是自己实现的,利用这个特性能实现类似多继承的效果。
所以protocol可以理解把,把适合别的对象处理的事情委托给别的对象处理的一种机制。
|
|
关于协议的继承:
在声明协议的时候,可以通过以下的方式实现协议的继承效果:
@protocol A<B,C,D>
//coding
@end
本质上就是把B,C,D的protocol list方法列表中的IMP指针copy到当前协议的结构体中的protocol。
|
|
关于在protocol中添加属性的作用:
|
|
@required & @optional
@required用于标记必须实现的方法(省缺/默认)
@optional用于标记需要实现的方法(OC2.0出的,使用这个即可实现类似非正式协议的效果)
|
|
非正式协议:
非正式协议的相关概念
Note:
1、以NSObject类的分类作为非正式协议的声明,那么程序中的任何一个实例发送消息,编译也不会出现问题。
2、非正式协议只需要声明,但不一定要实现,所以无论在编译期还是运行时,都无法检查适用性,就是检测到这个协议的所有方法都已经实现了,但可以在运行期通过respond来检测某个方法是否实现了。
3、本质上,NSObject分类实现的非正式协议,是OC1.0时代的实现,在OC2.0中protocol提供了@optional后,使用@optional更加易懂好理解。
|
- 实践作用:
|
委托者模式:delegate的实现
|
|
多继承的效果的实现:
|
- 参考文章:
|
Objective-C编程全解 第十二章 协议
|
- 作用:
- 原理:
- 实践应用:
|
可以用来定义只读属性:
在头文件中定义属性为readonly,而在extension在定义为readwrite,即可实现外部只可读,内部可读可写。
|
- 参考文章:
- 作用
- 原理:
|
Property属性声明规则总结:
|
|
ARC/MRC下的property修饰词:
ARC:assign、weak、strong、copy
MRC:assign、retain、copy、nonatomic、atomic
|
- 实践应用:
- 参考文章:
|
Objective-C编程全解 第七章
|
-
- 与C函数指针类似的点
- block可以作为方法/函数的参数或返回值
- block可以接收参数与返回值
- 与C函数指针不同的点
- block是inline,并且对局部变量是readonly
- 与C函数指针类似的点
|
Block是“带有自动变量值的匿名函数”,其实带有自动变量值的意思就是Block具备捕获自动变量的能力,而且Block的实现其实就是通过C语言处理实现的,是对C语言语法的一种拓展,是非标注的,下面从几个方面来剖析Block的原理。
|
|
1、Block如何捕获变量
1.1、Block捕获的变量类型
C与OC中变量的类型与其存储的区域如下:
(自动)局部变量 ——》栈(stack)
(自动)全局变量 ——》堆(malloc)
(全局)静态变量 ——》静态存储区(.data)
(局部)静态变量 ——》静态存储区(.data)
其中静态存储区的变量的生命周期是属于程序的,所以在程序的整个生命周期也可以访问,因此block也一样可以直接访问。所以block需要捕获的仅仅全局/局部的自动变量。
1.2、block实现自动变量捕获的原理
block通过copy的方法把自动变量保存在block对象的存储区域中(block在OC中是以对象来处理的),有值捕获与指针捕获两种形式。
|
|
2、__block的作用与原理
当block通过值捕获获得自动变量时,在block内是无法对其进行修改的,但可以通过__block的修饰符来让block内对值进行修改。
2.1、__block的原理
通过__block修饰符号,修饰的对象或值被捕获后,会自动生成以下的结构体:
struct __Block_byref_val_0 {
void * isa;
__Block_byref_val_0 * __forwarding;
int __flags;
int __size;
int val; //也可能是 objc *val / int *val
}
从源代码可以得出,其中__forwarding指针用于自指,主要用于实现copy上stack与malloc上的数据兼容,而val的值就是保存原自动变量。
就是block内还是block外都是通过__Block_byref_val_0或对象来访问val,从而实现block外与block内在合理的生命周期内进行访问,而且通过对象封装,就可以通过引用计数的规则管理其生命周期了。
所以__block的本质就是把数值封装成对象,然后block捕获其时,就是指针捕获,从而实现在block内进行读写的需求。
|
|
3、block的生命周期
根据block的内存存储区域,block的生命周期呈现也不一致,其所捕获的自动变量的生命周期同样会跟随着不一致。
3.1、Block的类型
NSConcreteStackBlock:
NSConcreteGlobalBlock:
NSConcreteMallocBlock:
顾名思义,可以得知哪种类型的Block就会存储在相对应的存储区。
3.2、NSConcreteGlobalBlock
满足以下两个条件即为NSConcreteGlobalBlock:
所以决定是否生成NSConcreteStackBlock的关键不是说定义在全局作用域,而是是否捕获了自动变量。
3.3、NSConcreteStackBlock
除了3.2所说的情况,都是生成NSConcreteStackBlock。NSConcreteStackBlock是NSConcreteMallocBlock的基础,只有发生了block_copy的操作才会产生NSConcreteMallocBlock。
3.4、NSConcreteMallocBlock
只有发生block_copy的操作才会把NSConcreteStackBlock转换成NSConcreteMallocBlock,也就是从栈copy道堆中。
在ARC环境下,以下情况会默认调用copy:
MRC环境下需要手动调用,而且重复手动调用copy是没有影响的,因为在第二次调用copy时会采用引用的方式实现。
|
|
4、block捕获的自动变量的生命周期
4.1、值捕获的自动变量
block本身就是一个对象,其生命周期由引用计数来决定,而值捕获后的数值是直接在对象的存储空间后添加的,所以值的生命周期与block的生命周期一致。
4.2、指针捕获的自动变量
指针捕获的自动变量包括指向对象与指向类型变量的两种情况
4.2.1、指向对象
NOTE:其中__block是一种特殊的对象引用。
4.2.2、指向类型变量
这要视所指向类型变量是存储在stack还是malloc而论。
|
|
作为函数的参数
|
|
作为函数的返回
|
|
Objective-C高级编程 iOS与OS X多线程与内存管理
|
- 作用
- 原理
|
-
- 通过归档解档,将对象归档然后再接档可以实现深复制的效果。
- 通过系统方法实现:
|
// [[NSSet alloc] initWithSet:<#(nonnull NSSet *)#> copyItems:<#(BOOL)#>]
// [[NSArray alloc] initWithArray:<#(nonnull NSArray *)#> copyItems:<#(BOOL)#>]
// [[NSDictionary alloc] initWithDictionary:<#(nonnull NSDictionary *)#> copyItems:<#(BOOL)#>]
|
|
- (
id
)copyWithZone:(
nullable
NSZone
*)zone{
//coding... } - ( id )mutableCopyWithZone:( nullable NSZone *)zone{ //coding...
}
|
- 实践应用
- 参考:
|
KVC原理:
但使用KVC的方式来访问一个对象时,的流程如下:
1、检查是否有对应的getter&setter访问器,有则使用其读写实例变量
2、没有访问器时,使用accessInstanceVariablesDirectly来检查是否存在相应的实例变量,有则访问
3、若没有相应的实例变量,将触发setValue:forUndefineKey:,valueForUndefineKey:,该改法是通过NSObject的类别(非正式协议)来实现,其默认实现是抛出异常,可以通过重写实现相应的自定义处理。
4、若有相应的实例变量,会观察值是否对象,不是则使用相应的对象包装值。
|
|
KVC的小众使用规则:
(1)根据键路径进行访问
可以通过valueForKeyPath或setValue:forKeyPath,通过键路径的方式进行数据的访问
(2)一对一关系与一对多关系
在访问的对象是一个数组的时候,会有一对多的情况发生。
#import
"Luozhiwei.h"
@implementation Luozhiwei - ( instancetype )init{ if ( self = [ super init ]) { _a = @"a" ; _array = @[@{ @"name" : @"luo" , @"age" : @"12" } , @{ @"name" : @"zhi" , @"age" : @"13" } , @{ @"name" : @"wei" , @"age" : @"14" }] . mutableCopy ; _dictionary = @{ @"name" : @"luo" , @"age" : @"12" } . mutableCopy ; } return self ;
}
@end
//在别的方法中调用以下方法
会得到array中的所有name值。
|
|
KVO原理:
要实现KVO键值本质上就是在对象的实例变量被修改的时候,实现相应方法的回调。
在OC语言的实现中,是通过继承重写的方式来实现KVO的。
当我们通过符合KVC标准的方式访问实例变量时,则通过
三种方法访问实例变量时,则会触发KVO回调。
当我们通过
[objcA
addObserver
:
@"observe"
forKeyPath
:
@"keyPath"
options
:
NSKeyValueObservingOptionOld
|
NSKeyValueObservingOptionNew
context
:
nil
]方法后,就会把立即生成一个继承于objc的类,类名NSKVONfying_objcA,然后把objcA的isa指针指向
NSKVONfying_objcA,
NSKVONfying_objcA重写了setter方法与accessInstanceVariablesDirectly相应的变量查询方法,在这两个方法中添加入检查到相应修改后就会触发回调方法
- (
void
)observeValueForKeyPath:(
NSString
*)keyPath ofObject:(
id
)object change:(
NSDictionary
<
NSString
*,
id
> *)change context:(
void
*)context。
回调方法使用非正式协议实现,但只声明不实现,所以若注册了KVO的类没有实现回调方法,会引发奔溃。
|
|
Objective-C编程全解
|
|
通知中心的结构大致如上图,其中NotificationCenter负责维护一个名为注册对象列表,里面包括注册通知指针、通知调用方法、通知Key等关键信息。
每当有一个对象发起通知时,通知中心就会根据通知Key,然后到注册对象列表中查找出注册了这个通知的所有对象,然后通过对象指针、调用方法、参数,发起objc_sendmsg(),执行相应操作。
根据以上原理,我们可以得知,对象注册通知,实际上就是往NoticationCenter中的注册对象列表中添加自身的指针、调用的方法、注册的通知消息。
需要注意的是,这个注册对象列表并没有去重的效果,所以有可能因为程序逻辑的不当而造成重复注册,造成收到多余的通知,所以一定要遵守对象不需要的时候要即使注销通知。
|
|
通过NoticationCenter很容易实现跨层的消息发送。
|
|
实现一对多的消息发送。
|
|
多态的使用总结:
|
|
工厂模式
|
|
类族
|
|
Objective-C编程大全 第四章
|
- 概念:
- 原理:
|
iOS中典型的继承用例:
由上图可以看出,UIKit框架的基类是NSObject,所有的类都是直接或者间接地从NSObject继承回来的。
其中NSObject是runtime的入口,由它负责封装runtime库,从而继承它的类就能具备消息发送的能力,这就是为什么所有的类都得继承与NSObject的原因。
其中UIResponder实现了事件响应的相关功能,只有继承了UIResponder的类就能加入响应者链条并事件进行响应处理,所以从图可以看出大部分的图形界面都是继承与UIRespond,所以通过合理的继承设计可以轻松地实现相应功能的扩展,但也不建议滥用继承,因为用得不好很容易产生个各种依赖问题,而且代码的学习周期容易边长,代码不好维护。
还是那句话,该出手时就出手。
|
|
OC的继承实现:
OC的继承首先靠super_class指针实现,在我们定义一个类继承与什么类的时候,就会确定super_class指针的指向,从而决定了在objc_sendmsg()消息发送的走向。
从runtime的类的定义我们可以看出无论是属性的定义还是方法的定义是都公开的,那么OC是怎么实现共有方法与私有方法?
OC是通过文件的组织架构来实现的,正因为如此OC把声明与定义分开,也就分成了.h & .m文件,而能在别的类中导入的只要.h文件,也就是在编译器的辅助下,我们只能调用.h文件所声明的属性与方法,从而实现了公有方法与私有方法。
|
|
关于OC的方法继承的具体表现:
原理如上《OC的继承实现》
|
|
关于OC的属性与实例变量的继承的具体表现:
实例变量的继承类型:
外部访问类的对象可以通过KVC与属性方法来访问,其中KVC能直接绕过私有方法的保护而直接读取相应的内存,而getter与setter方法又只能通过修饰词来决定属性的是否可读,所以上面的词只能控制继承关系的效果。
属性的继承:
原理如上《OC的继承实现》
|
|
多继承的实现讨论:
OC只支持单一继承,但若有实现多继承的效果可以通过protocol、消息转发、组合的方法来实现。
- protocol
通过实现多个protocol限定的方法,可以实现多继承的效果。 优势:轻量,依赖少,灵活定制,只需要实现需要的部分即可。 局限性: 不可重用,每次都得重新实现。
- 消息转发
利用消息转发机制的第二层、第三层,将无法处理的消息转发给相应能处理的对象,然后将别的对象的处理结果作为原方法的结果返回。这种方法与组合的实现原理类似,但有消息转发的壳,让其更加接近多继承。
- 组合
通过将实现相应功能的对象都以变量持有,将相应的处理丢给它们。
|
- 实践应用:
- 参考文章:
|
Objective-C编程大全
|
- 作用
- 原理
- 实践应用
- 参考文章
- 概要:
- 要点:
|
OC异常处理机制:
标准的异常处理语法:
@try
{
[self performSelector:@selector(luozhiwei)]; //异常处理域:在此记录正常的处理
}
@catch
(NSException *exception) {
NSLog(@"%@", exception); //异常句柄:异常处理过程。
}
@finally
{
NSLog(@"finish”); //后处理:无论异常是否发送,都会执行的代码块
}
以上就是一个OC标准的异常处理语句通过@try来实现代码逻辑,通过@catch来捕获@try的异常,无论@catch处理结果如何,最后都会走finally。
关于异常的发生与传播:
若都按照上面的语法实现异常处理,规则是简单的,但一旦引入了自身触发异常的方法后,异常的传播就稍微复杂一些。
[
NSException
raise
:
@"jhasdf"
format
:
@"fjsdkjf”
]; //通过NSException引发异常
@throw
[
NSString
stringWithFormat
:
@"luozhiwei”
]; //@throw抛出异常,可以写入任何对象
其中@catch的参数是可以自己定义的,如@catch(MyClass A),@catch (MyClass B),结合@throw object 语法,即可手动触发相应的catch,进行相应的处理。
但我们需要捕获未知类型的异常,可以通过@catch (…)实现。
在异常处理嵌套的情况下,只需要注意是否有@throw语句控制异常信息传播,其余的与标准异常处理的规则一致。
|
|
断言:
出于调试的目的,有时需要查看程序是否满足约束的条件,可是可以通过断言宏来实现,当相应的条件触发时,就会触发异常的结构称为断言。
断言宏的实现:
#define _NSAssertBody(condition, desc, arg1, arg2, arg3, arg4, arg5) \
do { \ __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ if (!(condition)) { \ NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \ __assert_file__ = __assert_file__ ? __assert_file__ : @ "<Unknown File>" ; \ [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd object:self file:__assert_file__ \ lineNumber:__LINE__ description:(desc), (arg1), (arg2), (arg3), (arg4), (arg5)]; \ } \ __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ } while( 0 ) #endif #if !defined(_NSCAssertBody) #define _NSCAssertBody(condition, desc, arg1, arg2, arg3, arg4, arg5) \ do { \ __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ if (!(condition)) { \ NSString *__assert_fn__ = [NSString stringWithUTF8String:__PRETTY_FUNCTION__]; \ __assert_fn__ = __assert_fn__ ? __assert_fn__ : @ "<Unknown Function>" ; \ NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \ __assert_file__ = __assert_file__ ? __assert_file__ : @ "<Unknown File>" ; \ [[NSAssertionHandler currentHandler] handleFailureInFunction:__assert_fn__ file:__assert_file__ \ lineNumber:__LINE__ description:(desc), (arg1), (arg2), (arg3), (arg4), (arg5)]; \ } \ __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ } while( 0 )
#endif
从上面的代码可以得出断言宏是分两种的一种面向方法,另一种面向函数,而从结构可以看出,里面的本质都是一个do()while(0)结构,为什么要使用这种结构,应该只是为了能构成一个代码域罢了,而且从上代码可以得出,其实现的核心方法如下:
//对应方法的断言宏
// [[NSAssertionHandler currentHandler] handleFailureInMethod:<#(nonnull SEL)#> object:<#(nonnull id)#> file:<#(nonnull NSString *)#> lineNumber:<#(NSInteger)#> description:<#(nullable NSString *), ...#>]
//对应函数的断言宏
// [[NSAssertionHandler currentHandler] handleFailureInFunction:<#(nonnull NSString *)#> file:<#(nonnull NSString *)#> lineNumber:<#(NSInteger)#> description:<#(nullable NSString *), ...#>]
|
- 参考文章:
|
Objective-C编程大全 第18章
|
|
多线程编程本质上就是解决两个问题:并行计算、异步计算。
应用多线程技术,本质上就是为了实现更高效率的程序,提供更快的响应给用户,下面着重以以下四个方面来谈谈iOS的多线程开发。
|
|
多线程的基本概念与原理
|
|
1、进程与线程
进程与线程的关系是密不可分的,在iOS系统中,一个app程序就有且仅有一个进程,但一个进程可以拥有多个线程来并发地执行任务。
1.1、进程概念与作用
1.1.1进程的狭义定义与广义定义
狭义定义:
进程就是正在运行的程序的实例。
广义定义:
进程的严格定义是“计算机中的程序关于某个数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础”
操作系统运行的是各种各样的程序,而一个程序至少有一个进程(主进程),而一个进程至少有一个线程(主线程),所以这个结构关系也说明了为什么进程是操作系统的一个重要的组成。
1.1.2、进程的主要组成及其特点
进程的组成:
进程的特点:
1.1.3、进程的运作原理
进程的状态:
进程的切换:
线程的切换实际上就是Ready-》running或者running-》Ready/Blocked的过程。
说白了就是获取与放弃处理器使用的权限的过程。
当一个进程要使用处理器,首先把上下文环境配置处理器上,上下文环境就是设置一大堆的临时数据的寄存器和PC(程序指针)用于指明程序运行到哪里,然后处理器才继续往下执行。
在进程运行阶段,所有计算的过程中产生的数据会先保存在处理器的临时存储区中,并根据相应的状态的修改而修改相应的寄存器。
当一个进程要让出处理器的使用权限时,会先把所有的处理结果保存会自己的私有堆栈中,并把当前的上下文环境保存好,再回到非激活状态。
1.2、线程概念与作用
线程是进程内假想的持有CPU使用权的执行单位。多线程就是进程允许多个线程以并发的形式执行任务,多个线程是共享进程的私有堆栈的,所以在多线程编程中常见的挑战就是如何安全地读写数据。
线程的本质就是为了完成一个特定的任务的命令集合,进程把线程的命令集合发送到处理器进行执行实现计算。
所以线程自身的没有存储区域的。
|
|
2、并发与并行
|
|
3、线程死锁的原理
形成死锁的原理就是两个在业务上相关的线程形成了一种互相等待的状态,最终这两条线程会在程序的整个运行生命周期内都处于block状态,从而形成死锁,通常是由于同步锁使用不惬当而导致了,遇到这种问题,注意一下同步锁的使用是否恰当即可。
|
|
4、iOS中实现多线程编程的方案
NSOperation是线程安全的,而GCD是线程不安全的。
|
|
iOS同步锁的原理与作用
|
|
1、同步锁的原理
在程序中同步锁会呈现成一个对象或者一个语法,但其本质就是令牌算法,如一个NSLock对象就是一个令牌,哪个线程通过竞争上岗获得令牌则获得处理器的使用权,而其余未获取令牌的线程则会在线程队列里等待进入下一轮的令牌竞争,而运行完的线程则释放令牌并释放线程资源,从而保证对相同资源的读写安全。具体流程如图:
同步锁的作用原理图:
线程的状态和进程的状态也是类似的,如下图所示。
线程的状态图:
当线程进入等待队列时,实际上就会进入blocked的状态,但blocked又有两种获得锁的状态:
|
|
2、iOS同步锁的类型与特点
|
|
Grand Central Dispatch(GCD)的原理与作用
无论是pthread还是NSThread都是直接用线程的概念去管理线程的,就是说它们所创建的对象或者结构体就是与相应的线程是对应的关系。
但iOS4.0后,apple推出GCD,以队列的概念来管理线程,而具体的线程操作(创建、释放、调用等)则由系统根据具体的情况而实现。
通过GCD,编程时只需要关注往相应的队列派遣block封装的任务即可,而无需关心接下来具体的线程操作。
|
|
Grand Central Dispatch的头文件结构:
#include
<os/object.h>
#include
<dispatch/base.h> //相关的基础宏定义
#include
<dispatch/time.h> //定义dispatch的时间单位与创建方法
#include
<dispatch/object.h> //将线程以队列的概念管理,涵盖了线程生命周期的所有方法,如release、retain、active、resume、suspend等等。
#include
<dispatch/queue.h> //队列的创建与调用的相关方法,提供主动调用派遣block的方法。
#include
<dispatch/block.h> //定义block任务块
#include
<dispatch/source.h> //定义source,根据事件源被动触发block任务
#include
<dispatch/group.h> //用组的概念来管理队列,能控制对象的集体执行又或者优先级控制
#include
<dispatch/semaphore.h> //信号量,信号令牌
#include
<dispatch/once.h> //once,线程安全的基础下保证任务在程序周期中只执行一次
#include <dispatch/data.h> //数据模型
#include <dispatch/io.h> //IO,input&output
|
|
关于线程与队列:
无论是pthread或者NSThread都是直接以线程的概念去实现多线程编程的,但这样会提高编程的难度,因为要考虑的东西比较多需要的知识也比较底层,如你必须得精准得知道线程的状态,并且通过各种任务的执行与判断,进行状态的切换之类的操作。
所以Apple基于这点,设计出基于队列的概念来实现多线程编程的GCD(low-level),然后再以GCD定制重写了NSOperation(high-level)。
这样设计的好处是,开发者主需要关注在什么类型的队列上以什么方式派遣了什么任务即可,剩余的关于线程状态的调度与判断处理均交给系统进行优化处理,也正因为如此,线程与队列也不是一一对应的,会根据系统当前的资源情况进行派发。
|
|
线程block任务的派遣方式:
被动派遣的信号源的分类如下:
|
|
任务优先级控制:
通过group与semaphore可以实现调用优先级的调整处理。
|
|
NSOperration的原理与作用
NSOperation在iOS2.0已经推出,当时是基于NSThread实现的,在iOS4.0推出GCD后,则用GCD重写。所以NSOperation可以视为基于GCD的更高一层的定制,对线程操作的添加了不少限制,从而确保线程安全。
若需要自己定制自己的多线程架构则可以实现GCD,否则推荐使用NSOperation。
|
|
faceBook asyncDispaly:
将主界面的渲染、排版、UI对象操作中的,渲染与排版大量放置到后台线程实现,提高主线程的响应效率。
|
1800



被折叠的 条评论
为什么被折叠?



