Runtime探索
案例1
测试以下代码,能否将⽅法列表及成员属性打印出来?
class LGTeacher {
var age: Int = 18
func teach(){
print("teach")
}
}
let t = LGTeacher()
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(LGTeacher.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodList?[i]{
let methodName = method_getName(method)
print("方法列表:\(methodName)")
}else{
print("not found method")
}
}
var count: UInt32 = 0
let proList = class_copyPropertyList(LGTeacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[i]{
let propertyName = property_getName(property)
print("成员属性:\(property)")
}else{
print("not found property")
}
}
print("test run")
}
test()
//输出以下内容:
//test run
从运行结果来看并没有达到预期,⽅法列表和成员属性都没有打印出来
案例2
修改
案例1
,给方法和属性添加@objc
修饰,能否打印出结果?
class LGTeacher {
@objc var age: Int = 18
@objc func teach(){
print("teach")
}
}
//输出以下内容:
//方法列表:teach
//方法列表:age
//方法列表:setAge:
//成员属性:0x0000000100008510
//test run
从运行结果来看,⽅法列表及成员属性全部被打印出来,但
Class
没有继承NSObject
,所以并不能暴漏给OC
使用
案例3
修改
案例2
,将Class
继承于NSObject
,去掉@objc
修饰,能否打印出结果?
class LGTeacher : NSObject {
var age: Int = 18
func teach(){
print("teach")
}
}
//输出以下内容:
//方法列表:init
//test run
从运行结果来看,只有
init
方法被打印出来。因为继承NSObject
后,swift.h
中默认只有init
方法暴露
案例4
修改
案例3
,Class
继承于NSObject
,同时给方法和属性添加@objc
修饰,能否打印出结果?
class LGTeacher : NSObject {
@objc var age: Int = 18
@objc func teach(){
print("teach")
}
}
//输出以下内容:
//方法列表:teach
//方法列表:init
//方法列表:age
//方法列表:setAge:
//成员属性:0x0000000100008518
从运行结果来看,⽅法列表及成员属性全部被打印出来,同时可供
OC
使用,但对于teach()
方法,依然是V_table
函数表调度,无法使用Runtime
的方法交换,因为方法此时还不具备动态特性
案例5
修改
案例4
,Class
继承于NSObject
,将@objc
修饰改为dynamic
修饰,能否打印出结果?
class LGTeacher : NSObject {
dynamic var age: Int = 18
dynamic func teach(){
print("teach")
}
}
//输出以下内容:
//方法列表:init
//test run
从运行结果来看,还是只有
init
方法被打印出来。因为dynamic
修饰只给方法和属性增加了动态特性,它们依然不能被OC
使用
通过上述案例,得出以下结论:
Swift
是静态语言,所以没有动态特性。⽅法和属性不加任何修饰符的情况下,不具备所谓的Runtime
特性,它的方法调度方式使用V_table
函数表调度- 对于纯
Swift
类,给⽅法和属性添加@objc
修饰后,可以通过Runtime API
获取到⽅法和属性列表,但是在OC
中无法进⾏调度,例如Runtime
的方法交换- 继承⾃
NSObject
的类,如果想要动态获取当前⽅法和属性,必须在其声明前添加@objc
关键字。如果想通过Runtime API
使用它们,例如Runtime
的方法交换,需要添加dynamic
关键字,让它们具备动态特性
objc源码分析
进入
class_copyMethodListclass_copyMethodList
定义,先获取当前的data
,而data
的作用就是存储类的信息
进入
datadata
定义,在objc_class
里打印superclass
,输出的是Swift
中有默认基类_SwiftObject
在
_SwiftObjectSwift
源码中找到_SwiftObject
,发现它实现了NSObject
协议。本质上Swift
为了和OC
进行交互,它保留了OC
的数据结构
回到
methodsobjc
源码,打印methods
,输出一个存放method
的二维数组。使用@objc
修饰的方法就能被获取到,因为Swift
在底层数据结构和OC
保持部分一致
在
swift_class_tobjc
源码中找到swift_class_t
,继承自objc_class
,保留了父类isa
、superclass
、cacheData
、data
四个属性,其次才是自己的属性
必须继承NSObject
的原因:Swift
在底层数据结构和OC
只保持了部分一致,通过NSObject
的声明,标记了当前类是一个和OC
交互的类。可以帮助编译器判断这个类在编译过程中,到底应该走哪些方法的分支。因为上层API
的调用者暴露不同,所以选择不同,在编译器里优化的调用方式也就不同。
元类型
AnyObject
代表任意类的
instance
,类的类型,仅类遵守的协议
class LGTeacher {
var age: Int = 18
}
var t = LGTeacher()
//此时代表的就是当前 LGTeacher 的实例对象
var t1: AnyObject = t
//此时代表的就是 LGTeacher 这个类的类型
var t2: AnyObject = LGTeacher.self
//仅类遵守的协议
protocol JSONMap: AnyObject {}
//class可遵守此协议
class LGJSONMap: JSONMap {}
上述代码中,
t1
代表LGTeacher
的实例对象,t2
代表LGTeacher
类的类型,JSONMap
是仅类遵守的协议,因为LGJSONMap
是Class
类型,所以可以遵守JSONMap
协议
如果是结构体,可以遵守
编译报错JSONMap
协议吗?
JSONMap
是仅类遵守的协议,结构体无法使用,编译报错
Any
代表任意类型,包括
funcation
类型或者Optional
类型
var array: [Any] = [1, "Teacher", true]
Any
包含的类型比AnyObject
更为广泛,可以理解为AnyObject
是Any
的子集。
如果将
编译报错array
数组的元素声明为AnyObject
类型,编译报错
AnyClass
代表任意实例的类型:
AnyObject.Type
public typealias AnyClass = AnyObject.Type
上述代码是
AnyClass
的定义,类型是AnyObject.Type
T.self
T
是实例对象,返回的就是它本身T
是类,那么返回的是Metadata
class LGTeacher {
var age: Int = 18
}
var t = LGTeacher()
//返回实例对象本身
var t1 = t.self
//返回LGTeacher这个类的类型,metadata元类型
var t2 = LGTeacher.self
上述代码中,
t1
返回的是实例对象本身,t2
返回的是LGTeacher.Type
,也就是LGTeacher
这个类的类型
T.Type
⼀种类型,
T.TypeT.self
的类型是T.Type
type(of:)
⽤来获取⼀个值的动态类型
var age: Int = 18
func test(_ value : Any) {
print(type(of: value))
}
test(age)
//输出以下内容:
//Int
上述代码中,
value
的静态类型(static type)
是Any
,是编译期确定好的。而type(of:)
方法⽤来获取⼀个值的动态类型(dynamic type)
,所以输出的是Int
案例1
value
是LGTeacher
类型,实际传入的t
是LGChild
类型,在test
方法内执行value.teahc()
,打印结果是什么?
class LGTeacher {
func teahc() {
print("LGTeacher teahc")
}
}
class LGChild : LGTeacher {
override func teahc() {
print("LGChild teahc")
}
}
func test(_ value : LGTeacher) {
value.teahc();
}
var t = LGChild()
test(t)
//输出以下内容:
//LGChild teahc
上述代码中,
value
编译期类型是LGTeacher
,运行时的实际类型是LGChild
,所以打印结果是LGChild teahc
案例2
value
是TestProtocol
类型,分别传入t1
和t2
,打印结果是什么?
protocol TestProtocol {}
class LGTeacher : TestProtocol {}
func test(_ value : TestProtocol) {
print(type(of: value))
}
var t1 = LGTeacher()
var t2: TestProtocol = LGTeacher()
test(t1)
test(t2)
//输出以下内容:
//LGTeacher
//LGTeacher
上述代码中,分别传入的
t1
和t2
,运行时的实际类型都是LGTeacher
,所以打印的type(of:)
都是LGTeacher
案例3
value
是泛型,分别传入t1
和t2
,打印结果是什么?
protocol TestProtocol {}
class LGTeacher: TestProtocol {}
func test<T>(_ value : T) {
print(type(of: value))
}
var t1 = LGTeacher()
var t2: TestProtocol = LGTeacher()
test(t1)
test(t2)
//输出以下内容:
//LGTeacher
//TestProtocol
上述代码中,
test
方法的参数改为泛型,两次打印type(of:)
的结果不一样了,t1
输出LGTeacher
,t2
输出TestProtocol
。因为当泛型和协议同时参与,编译器无法推导出准确类型,需要在调用type(of:)
时将value
转换为Any
func test<T>(_ value : T) {
print(type(of: value as Any))
}
//输出以下内容:
//LGTeacher
//LGTeacher
修改后的代码,打印结果都是
LGTeacher
案例4
编译报错 如图所示,因为
class_getClassMethod
方法cls
参数要求传入AnyClass
类型,如果传入t.self
,可以获取方法列表吗?
t
是实例对象,t.self
返回的就是它本身,是LGTeacher
类型。而参数cls
要求传入AnyClass
类型,也就是需要LGTeacher.Type
类型,所以类型不符,编译报错
网友评论