看 YYModel 源码的一些收获

编程随想:学习技术的三部曲

  • WHAT
    “WHAT”也就是“What is it?”——这是最简单的层次。在这个层次,你要搞清楚某个东东是 什么样子的?有什么用?有什么功能特性?有什么语法?……
  • HOW
    所谓的“HOW”就是“How to do?”。在这个层次,你要搞清楚某个东西,其内部是如何运作 的?如何实现的?……
  • WHY
    一般来说,只有想清楚HOW之后,才能继续去考虑WHY。所谓的“WHY”,就是搞清楚某个东西为什么设计成这样?为什么不是另外的样子?这样的设计有什么讲究?……
    说实在的,善于问“为什么”有一定的天赋成分?好像某个科学大牛曾经说过“提出问题有时候 比解决问题更难”。一般来说,只有当你深刻理解了某个东西,才能够针对这个东东的设计问 出一些问题。所以,先把HOW的问题搞清楚,再来考虑WHY的问题。

关于源码学习自己的一些感悟

  1. 第一层:熟练使用;
  2. 第二层:读懂代码;
  3. 第三层:通晓原理;
  4. 第四层:如何设计;

自己学到了什么,还留有什么问题;

关于分享

关于线下演讲分享和线上文章分享,我一直觉得技术领域要学东西的话线上文章分享是最好的形式,一是它传播广,触达用户多;二是耗时少,写一篇文章或看一篇文章都比听一个分享花的时间少很多;三是可沉淀,读者可以反复看细节,可以沉淀下来不断被人搜索到。 – Bang (JSPatch 作者)

YYModel 作者性能优化的几个 Tip:

  1. 缓存

    Model JSON 转换过程中需要很多类的元数据,如果数据足够小,则全部缓存到内存中。

  2. 查表

    当遇到多项选择的条件时,要尽量使用查表法实现,比如 switch/case,C Array,如果查表条件是对象,则可以用 NSDictionary 来实现。

  3. 避免 KVC

    Key-Value Coding 使用起来非常方便,但性能上要差于直接调用 Getter/Setter,所以如果能避免 KVC 而用 Getter/Setter 代替,性能会有较大提升。

  4. 避免 Getter/Setter 调用

    如果能直接访问 ivar,则尽量使用 ivar 而不要使用 Getter/Setter 这样也能节省一部分开销。

  5. 避免多余的内存管理方法

    在 ARC 条件下,默认声明的对象是 strong 类型的,赋值时有可能会产生 retain/release 调用,如果一个变量在其生命周期内不会被释放,则使用 unsafe_unretained 会节省很大的开销。

    访问具有 weak 属性的变量时,实际上会调用 objc_loadWeak() 和 objc_storeWeak() 来完成,这也会带来很大的开销,所以要避免使用 weak 属性。

    创建和使用对象时,要尽量避免对象进入 autoreleasepool,以避免额外的资源开销。

  6. 遍历容器类时,选择更高效的方法

    相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦。

  7. 尽量用纯 C 函数、内联函数

    使用纯 C 函数可以避免 ObjC 的消息发送带来的开销。如果 C 函数比较小,使用 inline 可以避免一部分压栈弹栈等函数调用的开销。

  8. 减少遍历的循环次数

    在 JSON 和 Model 转换前,Model 的属性个数和 JSON 的属性个数都是已知的,这时选择数量较少的那一方进行遍历,会节省很多时间。


YYModel 源码学习篇

把不熟练的一些代码挑出来,弄清楚

NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END

为了防止写一大堆 nonnull(不可为nil),Foundation 还提供了一对儿宏,包在里面的对象默认加 nonnull 修饰符,只需要把 nullable 的指出来就行

1
2
3
4
5
6
7
NS_ASSUME_NONNULL_BEGIN
@interface Sark : NSObject
@property (nonatomic, copy, nullable) NSString *workingCompany;
@property (nonatomic, copy) NSArray *friends;
- (nullable NSString *)gayFriend;
@end
NS_ASSUME_NONNULL_END

#define force_inline inline attribute((always_inline))

1
2
3
4
5
6
7
__attribute__((always_inline))的意思是强制内联,所有加了__attribute__((always_inline))的函数再被调用时不会被编译成函数调用而是直接扩展到调用函数体内,比如我定义了函数
__attribute__((always_inline)) void a()
void b(){a();}
b调用a函数的汇编代码不会是跳转到a执行,而是a函数的代码直接在b内成为b的一部分。
  • 普通函数
    • 调用时,会出现流程跳转来回
      • 首先跳到被调用函数;
      • 被调用函数执行完之后,又会跳会主调函数中;
  • 内联函数
    • 调用时,直接 将内联函数的代码全部复制到调用的地点;
    • 内联函数的定义必须出现在调用之前;
    • 牺牲内存空间,来提高函数执行速度;
  • 内联函数的不足
    • 通常,编译器比程序设计者更清楚对于一个特定的函数是否合适进行内联扩展;一些情况下,对于程序员指定的某些内联函数,编译器可能更倾向于不使用内联甚至根本无法完成内联。
      • 代码比较长的,即使声明为inline,也不会最终内联
      • 而有的一些比较短的小函数,即使没有声明inline,也会由c/c++编译器最终内联
      • 而如果函数使用强制内联,那么最终就一定是内联
    • 对于一些开发中的函数,它们可能从原来的不适合内联扩展变得适合或者倒过来。尽管内联函数或者非内联函数的转换易于宏的转换,但增加的维护开支还是使得它的优点显得更不突出了。
    • 对于基于C的编译系统,内联函数的使用可能大大增加编译时间,因为每个调用该函数的地方都需要替换成函数体,代码量的增加也同时带来了潜在的编译时间的增加。

函数调用中堆栈的个人理解

NSCoder

iOS开发-数据存储NSCoder
NSCoding是一个protocol. 如果实现了NSCoding,需要实现其中的两个方法:

1
2
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER

方法中的主要的参数就是NSCoder,它是archivie字节流的抽象类.可以将数据写入一个coder,也可以从coder中读取我们写入的数据. NSCoder是一个抽象类,不能直接使用它来创建对象. 但是可以通过其子类NSKeyedUnarchiver从字节流中读取数据,NSKeyedArchiver将对象写入到字节流。

@package

@package变量,对于framework内部,相当于@protected, 对于framework外部,相当于@privat。

这个特性,很适合用于开发第三方的静态类库,因为多数人并不希望让别人知道自己属性的值。

.(点)和->(箭头)的区别

.(点语法)是访问类的属性,本质是调用set、get方法。

->是访问成员变量,但成员变量默认受保护,所以常常报错,手动设为public即可解决

Core Foundation框架和Cocoa Foundation框架区别

Core Foundation框架 (CoreFoundation.framework) 是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能。下面列举该框架支持进行管理的数据以及可提供的服务:

  • 群体数据类型 (数组、集合等)
  • 程序包
  • 字符串管理
  • 日期和时间管理
  • 原始数据块管理
  • 偏好管理
  • URL及数据流操作
  • 线程和RunLoop
  • 端口和soket通讯
    Core Foundation框架和Foundation框架紧密相关,它们为相同功能提供接口,但Foundation框架提供Objective-C接口。如果您将Foundation对象和Core Foundation类型掺杂使用,则可利用两个框架之间的 “toll-free bridging”。所谓的Toll-free bridging是说您可以在某个框架的方法或函数同时使用Core Foundatio和Foundation 框架中的某些类型。很多数据类型支持这一特性,其中包括群体和字符串数据类型。每个框架的类和类型描述都会对某个对象是否为 toll-free bridged,应和什么对象桥接进行说明。

CoreFoundation 对于 ios开发的作用

对于corefoundation一直存在疑问,一直感觉他是作为ios开发的底层接口,可是为什么很多开发者习惯于实用corefoundation接口而不是用NS库,比如CFUUIDRef,NSUUID,都可以产生UUID,但是看到的大部分代码都是使用CFUUIDRef,这是为什么呢,直接使用corefoundation接口有什么好处呢?

核心是和其他加框架和架构方便“沟通”。
The programming interfaces of Core Foundation objects have been designed for ease of use and reuse. At a general level, Core Foundation:

Enables sharing of code and data among various frameworks and libraries

Makes some degree of operating-system independence possible

Supports internationalization with Unicode strings

Provides common API and other useful capabilities, including a plug-in architecture, XML property lists, and preferences

Core Foundation makes it possible for the different frameworks and libraries on OS X to share code and data. Applications, libraries, and frameworks can define C routines that incorporate Core Foundation types in their external interfaces; they can thus communicate data—as Core Foundation objects—to each other through these interfaces.

__bridge 相关知识点

bridge,bridge_transfer和__bridge_retained详解

  • __bridge只做类型转换,但是不修改对象(内存)管理权;

  • __bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease或者相关方法来释放对象;

  • __bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

CFMutableDictionaryRef with ARC

如何创建并使用一个 CFMutableDictionaryRef

1
2
3
4
5
6
7
8
CFMutableDictionaryRef myDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
NSString *key = @"someKey";
NSNumber *value = [NSNumber numberWithInt: 1];
CFDictionarySetValue(myDict, (__bridge void *)key, (__bridge void *)value);
id dictValue = (__bridge id)CFDictionaryGetValue(myDict, (__bridge void *)key);
CFDictionaryRemoveValue(myDict, (__bridge void *)key);

如何根据 NSMutableDictionary 创建 CFMutableDictionaryRef

1
2
3
4
5
6
7
8
NSMutableDictionary *myDict = [NSMutableDictionary dictionary];
NSString *key = @"someKey";
NSNumber *value = [NSNumber numberWithInt: 1];
[myDict setObject:value forKey:key];
CFMutableDictionaryRef myCFDict = CFBridgingRetain(myDict);
// use myCFDict here
CFRelease(myCFDict);

CoreFoundation、CFDictionaryApplyFunction

Context

1
2
3
4
typedef struct {
void* object;
void* value;
}MyConext;

回调c函数

1
2
3
4
5
static void MyContextCllbackFunc(const void *_key, const void *_value, void *context) {
NSLog(@"%@", _key);
NSLog(@"%@", _value);
NSLog(@"%p", context);
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
NSDictionary *dict = @{
@"key1" : @"value",
@"key2" : @"value",
@"key3" : @"value",
};
User *user = [User new];
MyConext context = {0};
context.object = (__bridge void *)(user);
context.value = @"value";
CFDictionaryApplyFunction((CFDictionaryRef)dict, MyContextCllbackFunc, &context);

输出

1
2
3
4
5
6
7
8
9
2016-07-13 16:07:29.556 LJFSourceCodeLearn[5521:241563] key1
2016-07-13 16:07:29.556 LJFSourceCodeLearn[5521:241563] value
2016-07-13 16:07:29.556 LJFSourceCodeLearn[5521:241563] 0x7fff51eb8b88
2016-07-13 16:07:29.557 LJFSourceCodeLearn[5521:241563] key3
2016-07-13 16:07:29.557 LJFSourceCodeLearn[5521:241563] value
2016-07-13 16:07:29.557 LJFSourceCodeLearn[5521:241563] 0x7fff51eb8b88
2016-07-13 16:07:29.557 LJFSourceCodeLearn[5521:241563] key2
2016-07-13 16:07:29.557 LJFSourceCodeLearn[5521:241563] value
2016-07-13 16:07:29.557 LJFSourceCodeLearn[5521:241563] 0x7fff51eb8b88

类似还有CFArrayApplyFunction使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSMutableArray *array = [NSMutableArray new];
for (int i = 0; i < 3; i++) {
RuntimeViewController *user = [RuntimeViewController new];
user.name = [NSString stringWithFormat:@"%d%d%d", i,i,i];
[array addObject:user];
}
//Context结构体实例
MyConext context = {0};
context.object = @"hahaha";
context.value = @"hello world...";
//遍历Array数组元素,每一次传入一个函数中进行处理
CFArrayApplyFunction((CFArrayRef)array,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)array)),
MyContextCllbackFunc2,
&context);

C函数

1
2
3
4
5
6
7
static void MyContextCllbackFunc2(const void *user, void *context) {
RuntimeViewController *aUser = (__bridge RuntimeViewController *)(user);
MyConext *ctx = context;
NSLog(@"aUser.name = %@", aUser.name);
NSLog(@"ctx = %p", ctx);
}

输出

1
2
3
4
5
6
2016-07-13 16:07:29.557 LJFSourceCodeLearn[5521:241563] aUser.name = 000
2016-07-13 16:07:29.558 LJFSourceCodeLearn[5521:241563] ctx = 0x7fff51eb8bd8
2016-07-13 16:07:29.558 LJFSourceCodeLearn[5521:241563] aUser.name = 111
2016-07-13 16:07:29.558 LJFSourceCodeLearn[5521:241563] ctx = 0x7fff51eb8bd8
2016-07-13 16:07:29.558 LJFSourceCodeLearn[5521:241563] aUser.name = 222
2016-07-13 16:07:29.558 LJFSourceCodeLearn[5521:241563] ctx = 0x7fff51eb8bd8

__unsafe_unretained 修饰符

ARC 修饰符 相关知识点
unsafe_unretained是跟weak类似的用法,但是__unsafe_unretained会更易造成野指针,

并且需要注意的是,尽管ARC式的内存管理是编译器的工作,但是附有_unsafe_unretained修饰符的变量不属于编译器的内存管理对象

kCFNull

Nil NSNull NULL kCFNull

kCFNull,其宏定义,其实就是NSNull的单例

1
const CFNullRef kCFNull; // the singleton null instance

测试下

1
2
NSNull *null1 = (id)kCFNull;
NSNull *null2 = [NSNull null];

输出信息

1
2
NSNull *) null1 = 0x0000000107b10af0
(NSNull *) null2 = 0x0000000107b10af0

其实 kCFNull这个宏 === [NSNull null]这一句代码

isnan 和 isinf 函数

math.h 文件中定义
isnan(x) not a number 此宏返回一个非零值;
isinf(x) 当x是正无穷是返回1,当x是负无穷时返回-1。

附录:iOS 常用数学函数

Dispatch Semaphore

信号量(dispatch_semaphore)实现GCD下的并发和同步;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc] init];
// for (int i = 0; i<100000; ++i)
// {
// dispatch_async(queue, ^{
// [array addObject:@(i)];
// });
// }
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i<10000; ++i)
{
dispatch_async(queue, ^{
NSLog(@"所在线程%@,是否是主线程:%d,addObject i = %d",[NSThread currentThread],[[NSThread currentThread] isMainThread],i);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:@(i)];
dispatch_semaphore_signal(semaphore);
});
}
// NSLog(@"%@",array);

看到这有同学会问,把array的原子属性设置为 atomic 不就行了吗?

atomic 和 nonatomic 有什么区别?

Atomic

  • 是默认的
  • 会保证 CPU 能在别的线程来访问这个属性之前,先执行完当前流程
  • 速度不快,因为要保证操作整体完成

Non-Atomic

  • 不是默认的
  • 更快
  • 线程不安全
  • 如有两个线程访问同一个属性,会出现无法预料的结果

假设有一个 atomic 的属性 “name”,如果线程 A 调[self setName:@”A”],线程 B 调[self setName:@”B”],线程 C 调[self name],那么所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。因此,属性 name 是读/写安全的。

但是,如果有另一个线程 D 同时在调[name release],那可能就会crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。

如果 name 属性是 nonatomic 的,那么上面例子里的所有线程 A、B、C、D 都可以同时执行,可能导致无法预料的结果。如果是 atomic 的,那么 A、B、C 会串行,而 D 还是并行的。

所以访问 NSMutableArray 线程是不安全的。

Thread Safety Summary & Thread-Unsafe Summary

回顾一下多线程相关知识

0x01:进程和线程的区别

进程:系统分配资源的基本单位(比如CPU,内存等),进程有自己的空间,如果一个进程崩溃不会引起其它进程的崩溃。

线程:程序执行的最小单位,线程没有自己独立的空间,多个线程共享的是进程的地址空间,当然处理一些基本的如程序计数器,一组寄存器和栈等。
如果一个线程崩溃,它所在的进程就崩溃了。

0x02:并行和并发

在Linux和window下,CPU的分配是根据线程数来的,如果

1
2
总线程数<= CPU数量:并行运行
总线程数> CPU数量:并发运行

并行指的是,当你的CPU核数比线程数多的话,则会将每个线程都分在一个CPU核里进行处理。

并发指的是,当你的CPU核数比线程数少的话,则会利用“时间片轮转进程调度算法”,对每个线程进行同等的运行。

并行要求并发,但并发并不能保证并行。

0x03:GCD复习

  • GCD 术语
    • Serial vs. Concurrent 串行 vs. 并发
      任务串行执行就是每次只有一个任务被执行,任务并发执行就是在同一时间可以有多个任务被执行。
    • Synchronous vs. Asynchronous 同步 vs. 异步
      一个同步函数只在完成了它预定的任务后才返回。一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。
    • Critical Section 临界区
      一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。
    • Race Condition 竞态条件
      这种状况是指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,例如程序的并发任务执行的确切顺序。竞态条件可导致无法预测的行为,而不能通过代码检查立即发现。
    • Deadlock 死锁
      两个(有时更多)东西——在大多数情况下,是线程——所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。
    • Thread Safe 线程安全
      线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。
    • Context Switch 上下文切换
      一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。
  • Syn
  • Asyn
  • SerialQueue
  • ConcurrentQueue
  • After
  • Group
  • Barrier
  • Apply
  • Once
  • FunctionPoint
  • MainThread
  • Supspend
  • Resume
  • Semaphore
  • Set_target_queue

Obejctive-C runtime 编码类型

type Encoding

我们使用的一些数据类型,实际上最终由编译器解析成类似T@”Car”,&,N,V_car这样的一串字符

  • objc_property_attribute_t 属性特征值(编码)结构体
    • 属性的编码都是以T字符开始,获取属性声明的Ivar的 数据类型,如: T@User获取自定义类型User;
    • V字符,获取属性声明的Ivar的 变量名;
    • 以及其他字符,获取对应的数据 对应下表;
1
2
3
4
5
Person类的所有属性(Ivar)的 `最终数据类型` 由编译器编译成一串特定格式的字符
属性名 = car , 属性@encode()编码 = T@"Car",&,N,V_car
属性名 = name , 属性@encode()编码 = T@"NSString",C,N,V_name
属性名 = age , 属性@encode()编码 = Tq,N,V_age
1
2
自定义类型
@property Car *car; >>>>>> T@"Car",&,N,V_car
1
2
Foundation类型
@property NSString *name; >>>>>> T@"NSString",C,N,V_name
1
2
基本数据类型
@property NSInteger age; >>>>>> Tq,N,V_age
  • 解析最前面T开头的一段字符串
    • T@User >>> 属性变量类型是 User;
    • Ti >>> 属性变量类型是 int;
    • TB >>> 属性变量类型是 bool、BOOL;
    • TD >>> 属性变量类型是 Double;
    • Tf >>> 属性变量类型是 float;
    • 等等;

苹果会对我们在代码中使用的所有数据类型,进行额外的一次编码,编码成最终iOS系统所能识别的数据类型.

Runtime MetaClass

Objective-c 中的 每一个类 如: Person类 其实包含两部分

  • 普通类(类本身)
    • 如 person 类本身(我们创建的类)
  • 元类(系统创建)
    • Meta Person类,由系统默认创建
    • 这个创建Meta Person类的过程,我们不知道而已

关于meta class的系统方法

  • 判断一个objc_class实例是否是Meta类的Class

    1
    BOOL class_isMetaClass(Class cls)
  • 获取一个NSObject类对应的Meta类的Class

    1
    Class objc_getMetaClass(const char *name)

类 与 Meta类

  • 类,存放对象的相关数据
    • 实例成员变量
    • 实例方法
  • Meta类,存放类的先关数据
    • 类的成员变量(static 类成员变量)
    • 类方法(+开头的方法)
  • 区分 对象方法 与 类方法 调用过程
    • 向一个对象发送消息
      • 通过对象的 isa指针找到 对象所属类对应的的 objc_class结构体实例
      • 然后开始查找objc_class实例的 method_list 查找 Method
    • 向一个类发送消息
      • 通过类Class的 isa指针找到 Meta类对应的 objc_class结构体实例
      • 然后从objc_class实例的 method_list 查找 Method

结合 Demo 中 Runtime_MataClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#import "RuntimeViewController.h"
@interface RuntimeViewController ()
@end
void ReportFunction(id self, SEL _cmd)
{
//1. 对象
NSLog(@"This object is %p.", self);
//2. 对象所属的类
NSLog(@"Class is %@", [self class]);
//3. 所属类的父类
NSLog(@"super is %@.",[self superclass]);
//4. 每一个类都有两部分
//类的第一部分、类本身
//类的第二部分、元类
Class currentClass = [self class];
for (int i = 1; i < 10; i++)//i的次数随便改都可以
{
NSLog(@"Following the isa pointer times = %d, ClassValue = %@, ClassAddress = %p", i, currentClass, currentClass);
//通过Class的 isa指针 找到 MetaClass
currentClass = object_getClass(currentClass);
}
//5. NSObject类本身
NSLog(@"NSObject's class is %p", [NSObject class]);
//6. NSObject类的元类
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}
@implementation RuntimeViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setOSProps];
//运行时创建类
[self createClass];
[self imp_implementationWithBlock];
}
- (void)setOSProps
{
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.title = @"Runtime Meta Learn";
self.navigationController.navigationBar.translucent = YES;
}
- (void)createClass
{
//1. 创建一个Class
Class MyClass = objc_allocateClassPair([NSObject class],
"myclass",
0);
//2. 添加一个NSString的变量,第四个参数是对其方式,第五个参数是参数类型
if (class_addIvar(MyClass, "itest", sizeof(NSString *), 0, "@")) {
NSLog(@"add ivar success");
}
//3. 添加一个函数
class_addMethod(MyClass,
@selector(report),
(IMP)ReportFunction,
"v@:");
//4. 注册这个类到runtime系统中就可以使用他了
objc_registerClassPair(MyClass);
//5. 测试创建的类
[self test:MyClass];
}
- (void)report {
//什么都不做,只是为了OC对象能够调用到c函数
}
- (void)test:(Class)class {
//1.
id obj = [[class alloc] init];
//2.
[obj report];
}

控制台输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2016-07-12 11:37:23.281 LJFSourceCodeLearn[1353:75617] add ivar success
2016-07-12 11:37:23.281 LJFSourceCodeLearn[1353:75617] This object is 0x7ff42bc291d0.
2016-07-12 11:37:23.281 LJFSourceCodeLearn[1353:75617] Class is myclass
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] super is NSObject.
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 1, ClassValue = myclass, ClassAddress = 0x7ff42bf0f180
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 2, ClassValue = myclass, ClassAddress = 0x7ff42bf36550
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 3, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 4, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 5, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 6, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 7, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 8, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 9, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] NSObject's class is 0x110ecb170
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] NSObject's meta class is 0x110ecb198
  • 方法object_getClass(id obj)会根据 Class的私有成员变量isa指针 找到一个类,有两种情况:
    • 对象的isa >>> 所属类
    • 类的isa >>> Meta 类
    • Meta类的isa >>> Meta NSObject
    • Meta NSObject的isa >>> 永远指向自己,形成环路
  • times = 2时、根据myclass->isa指针,找到其对应的 Meta myclass
  • times = 3时、根据 Meta myclass->isa 指针,找到了 Meta NSObject
  • times = 4,5,6,7,8…、根据 元类NSObject 的 isa指针,最后都是指向自己

使用imp_implementationWithBlock()替代上面例子需要一个辅助的static c函数来完成运行时创建Class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
- (void)imp_implementationWithBlock
{
//////////////////////////////////////////////////////
///创建一个类
//////////////////////////////////////////////////////
Class People = objc_allocateClassPair([NSObject class], "People", 0);
//////////////////////////////////////////////////////
///添加两个变量
//////////////////////////////////////////////////////
if (class_addIvar(People, "name", sizeof(NSString *), 0, @encode(NSString *))) {
NSLog(@"add ivar name success");
}
if (class_addIvar(People, "age", sizeof(int), 0, @encode(int))) {
NSLog(@"add ivar age success");
}
//////////////////////////////////////////////////////
///创建方法的SEL
//////////////////////////////////////////////////////
SEL selector = sel_registerName("talk:");
//////////////////////////////////////////////////////
///创建方法的IMP指针,并指向Block给出的代码
//////////////////////////////////////////////////////
IMP impl = imp_implementationWithBlock(^(id self, NSString *arg1){
//age变量值
//通过KVC
//int age = (int)[[self valueForKey:@"age"] integerValue];
//通过Ivar
Ivar ageIvar = class_getInstanceVariable([self class], "age");
int age = (int)[object_getIvar(self, ageIvar) integerValue];
//name变量值
//通过KVC
//NSString *name = [self valueForKey:@"name"];
//通过Ivar
Ivar nameIvar = class_getInstanceVariable([self class], "name");
NSString *name = object_getIvar(self, nameIvar);
NSLog(@"age = %d, name = %@, msgSay = %@", age, name, arg1);
});
//////////////////////////////////////////////////////
///添加一个方法, 将SEL与IMP组装成一个Method结构体实例,添加到Class中的 method_list数组
//////////////////////////////////////////////////////
class_addMethod(People, selector, impl, "v@:@");
//////////////////////////////////////////////////////
///注册这个类到系统
//////////////////////////////////////////////////////
objc_registerClassPair(People);
//////////////////////////////////////////////////////
///生成一个实例
//////////////////////////////////////////////////////
id instanceP = [[People alloc] init];
//////////////////////////////////////////////////////
///给Ivar赋值
//////////////////////////////////////////////////////
//通过KVC赋值
[instanceP setValue:@"变量字符串值" forKey:@"name"];
//[instanceP setValue:@19 forKey:@"age"];
//通知Ivar赋值
Ivar ageIvar = class_getInstanceVariable(People, "age");
object_setIvar(instanceP, ageIvar, @19);
//////////////////////////////////////////////////////
///发送消息
//////////////////////////////////////////////////////
((void (*)(id, SEL, NSString *))(void *) objc_msgSend)(instanceP, selector, @"参数值");
//////////////////////////////////////////////////////
///释放对象、销毁类
//////////////////////////////////////////////////////
instanceP = nil;
objc_disposeClassPair(People);
}
  • 方法的编码格式:
    • v@: >>> 返回值void,参数1:self,参数2:SEL >>> - (void)funcName;
    • v@:@ >>> 返回值void,参数1:self,参数2:SEL, 参数3:NSString >>> - (void)funcName:(NSSring )name;
  • 对应的Objective-C方法的SEL也应该是 >>> talk:带一个参数
  • imp_implementationWithBlock()接收一个添加方法被调用时的回调Block
    • 其格式为: method_return_type ^(id self, method_args …)

Meta Class 元类

1
2
3
Class objc_allocateClassPair(Class superclass,
const char *name,
size_t extraBytes)
  • 函数名 objc_allocateClassPair.. 中的 ClassPair意思是说一对Class.
  • 但是该函数只返回了一个Class
  • 没有被返回的另一个Class就是 MetaClass 元类,由系统创建.
  • Meta Class与普通类Class 都是结构体 objc_class实例

Objective-c中的 对象 与 类

经常写获取一个对象的类型Class的代码

1
Class cls = [Person class];

就用到了Class这个数据结构,其定义为如下,可以得到Class是 结构体objc_class实例的 指针类型

1
typedef struct objc_class *Class;

那么能够成为一个类(Class)的数据结构为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct objc_class {
/*
又指向一个结构体objc_class实例
类的isa指针,指向类的 元类 MetaClass
*/
Class isa OBJC_ISA_AVAILABILITY;
#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 *` */

而Class数据结构中最重要的一个数据项是 Class isa; 这样的isa指针.

对象

能够成为一个对象(Object)的数据结构如下,可以看到对象数据结构中最重要的就是Class isa;指针项

1
2
3
4
5
6
7
struct objc_object {
/**
对象的isa指针,指向其 所属类本身(不是元类)
*/
Class isa OBJC_ISA_AVAILABILITY;
};

Class是struct objc_class的指针类型,那么struct objc_object也有其对于的指针类型

  • 调用类方法与对象方法
    • 给对象发送消息时,消息是在对象所属类的方法列表method_list或cache中查询Method
    • 给类发消息时,消息是在这个类的元类 meta class的方法列表或缓存中查询Method

所以也就是说对象方法与类方法存放地点的不同

  • 对象方法、存放在 类本身
  • 类方法、存放在 类的元类MetaClass

所以,元类是必不可少的,它存储了类的所有类方法

对象、类、元类三者之间的关系图

  • 对象的 isa指针 指向 所属类
  • 每一个类,都有一个 isa指针 指向一个 唯一的 MetaClass
  • 每一个Meta Class,拥有的 isa指针 都指向 顶层 NSObject Meta Class
  • 最上层的Meta Class(Meta NSObject)的isa指针指向自己,形成一个 回路
  • 类本身的 super_class 指向其父类,如果该类为根类则值为 nil
  • 每一个 MetaClass 的 super_class指针 都指向其 原本Class的superClass对应的 MetaClass
    • 特列: 但是最上层的 Meta Class 的 super_class 指针,指向的却是 NSObject 原本 Class

所有的 类 NSObject子类 都是 objc_class结构体 的实例

NSObject类获取Class的源码如下:

1
2
3
+ (Class)class {
return self;
}
  • Class是 objc_class结构体实例的指针变量类型
  • 那么+[NSObject class]返回就是一个 objc_class结构体实例
  • 而 self 一般是 一个指向对象地址的指针
  • 而此时的 self 所代表的就是 NSObject类本身

那么可以猜测: NSObject 是 objc_class的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation RuntimeViewController
- (void)viewDidLoad {
[super viewDidLoad];
//名字
NSLog(@"%@", [RuntimeViewController class]);
//多次输出其地址
NSLog(@"%p", [self class]);
NSLog(@"%p", [self class]);
NSLog(@"%p", [RuntimeViewController class]);
}

输出结果

1
2
3
4
2016-07-12 13:30:27.264 LJFSourceCodeLearn[1640:121986] RuntimeViewController
2016-07-12 13:30:27.265 LJFSourceCodeLearn[1640:121986] 0x101d03cd0
2016-07-12 13:30:27.265 LJFSourceCodeLearn[1640:121986] 0x101d03cd0
2016-07-12 13:30:27.265 LJFSourceCodeLearn[1640:121986] 0x101d03cd0

每一个NSObject类在运行阶段,是按照一个单例保存


才疏学浅,有理解错误的地方,欢迎指出。

李剑飞 wechat
欢迎订阅我的微信公众号"剑飞说"!
坚持原创技术分享,您的支持将鼓励我继续创作!