Swift Runtime ?
你肯定也想过
在OC中相信每一个iOS开发都知道Runtime, 现在Swift也更新到4.0版本了,要是你也学习过Swift的话你可能也会想过这样一个问题,OC大家都知道是有动态性的,你能通过Runtime 的API获取你想要的属性方法等等,那Swift呢?是不是也和OC一样呢?
这个问题在我看Swift的时候也有想过,带着这个问题就总结出了今天这篇文章。
先说说这个Runtime,在自己之前的文章中有总结过关于OC的Runtime以及它API的一些基本的方法和在项目中具体的使用,在这里再大概的提一下Runtime的基本的概念:
RunTime简称运行时。OC就是运行时机制,也就是在程序运行时候的一些机制,其中最主要的是消息机制。对于我们熟悉的C语言,函数的调用在编译的时候会决定调用哪个函数。但对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
也就有了下面这两点结论:
1、在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
2、在编译阶段,C语言调用未实现的函数就会报错。
看看Swift Runtime
先不直接丢出结论,从下面的简单的代码入手,一步步的找出我们想要的答案:
我们定义一个纯Swift的类 TestASwiftClass ,代码如下:
class TestASwiftClass{
var aBoll :Bool = true
var aInt : Int = 0
func testReturnVoidWithaId(aId : UIView) {
print("TestASwiftClass.testReturnVoidWithaId")
}
}
代码也是很简单,我们定义了两个变量一个方法,下面我们再写一个继承自 UIViewController 的 ViewController ,代码如下:
class ViewController: UIViewController{
let testStringOne = "testStringOne"
let testStringTwo = "testStringTwo"
let testStringThr = "testStringThr"
var count:UInt32 = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let SwiftClass = TestASwiftClass()
let proList = class_copyPropertyList(object_getClass(SwiftClass),&count)
for i in 0..<numericCast(count) {
let property = property_getName(proList?[i]);
print("属性成员属性:%@",String.init(utf8String: property!) ?? "没有找到你要的属性");
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
上面的代码也很简单,我们在ViewController中添加了一些变量,然后通过Runtime的方法尝试着先来获取一下我们最上面定义的纯Swift类TestASwiftClass的属性,你运行上面代码你就会发现:
什么都没有!!!为什么??
下面我们先给出答案,用它来解释一下为什么我们通过上面Runtime的API没有获取到任何东西,然后再接着用OC来证明一下我们说的结论:
C 语言是在函数编译的时候决定调用那个函数,在编译阶段,C要是调用了没有实现的函数就会报错。
OC 的函数是属于动态调用,在编译的时候是不能决定真正去调用那个函数的,只有在运行的时候才能决定去调用哪一个函数 ,在编译阶段,OC可以调用任何的函数,即使这个函数没有实现,只要声明过也就不会报错。
Swift 纯Swift类的函数的调用已经不是OC的运行时发送消息,和C类似,在编译阶段就确定了调用哪一个函数,所以纯Swift的类我们是没办法通过运行时去获取到它的属性和方法的。
Swift 对于继承自OC的类,为了兼容OC,凡是继承与OC的都是保留了它的特性的,所以可以使用Runtime获取到它的属性和方法等等其他我们在OC中获得的东西。
针对上面给出的结论,我们看看Swift对于继承自OC的类是不是保留了OC所有的特性呢?再看下面代码,只是做一个简单的修改,把通过object_getClass方法获取的对象写成self:
let proList = class_copyPropertyList(object_getClass(self),&count)
for i in 0..<numericCast(count) { let property = property_getName(proList?[i]);
print("属性成员属性:%@",String.init(utf8String: property!) ?? "没有找到你要的属性");
}
通过上面的方法我们获取到的日志如下:

可以看到我们获取到了我们在ViewController中定义的变量。这样也就证明了的确是上面答案说的那样。
那这样就又衍生出一个问题
那Swiftw就没办法利用Runtime了吗?
想一想,要是真的Swift没办法利用Runtime,那是一件得多让人失望的事!答案也肯定是否定的,我们还是能让Swift用Runtime的。看下面的代码:
class TestASwiftClass{
dynamic var aBoll :Bool = true
var aInt : Int = 0
dynamic func testReturnVoidWithaId(aId : UIView) {
print("TestASwiftClass.testReturnVoidWithaId")
}
}
上面还是我们定义的 TestASwiftClass 类,不同的地方不知道大家注意到没?
嗯,我们利用了dynamic(英文单词动态的意思)关键字,在第一个变量和方法的定义前面我们添加了这个关键字,那添加了这个关键字之后又什么变化呢?我们再通过最开始我们获取纯Swift类的代码获取一下试试,看结果!

结果:
可以看到这里是获取到了变量了的。(这里是获取属性没有写获取方法代码所以是值拿到变量没有拿到方法)
aBoll 这个变量前面是添加了dynamic关键字的,我们获取到了。在aInt这个变量前面我们是没有添加的,所以可以看到我们是没有获取到这个变量的,那关键的就是我们要理解:dynamic 关键字的含义:
首先有 @objc 这个关键字,它是用来将Swift的API导出来给 Object-C 和 Runtime 使用的,如果你类继承自OC的类,这个标识符就会被自动加进去,加了这标识符的属性、方法无法保证都会被运行时调用,因为Swift会做静态优化,想要完全被声明成动态调用,必须使用 dynamic 标识符修饰,当然添加了 dynamic 的时候,它会自己在加上@objc这个标识符。
这样我们就理解了dynamic这个关键字,知道了它的作用,那我们接下来就是尝试着多使用一下 Swift Runtime。
Swift Runtime
上面解释了这个关键字之后关于Swift的Runtime方面的只是就有了一个基本的了解了,下面的这些代码就像我们整理OC Runtime 那样也整理出来:
1、获取方法:
let mthList = class_copyMethodList(object_getClass(SwiftClass),&count)
for index in 0..<numericCast(count) { let method = method_getName(mthList?[index])
print("属性成员方法:%@",String.init(NSStringFromSelector(method!)) ?? "没有找到你要的方法")
}
2、属性成员变量
let IvarList = class_copyIvarList(object_getClass(SwiftClass),&count)
for index in 0..<numericCast(count) { let Ivar = ivar_getName(IvarList?[index])
print("属性成员变量:%@",String.init(utf8String: Ivar!) ?? "没有找到你想要的成员变量")
}
3、协议列表
let protocalList = class_copyProtocolList(object_getClass(self),&count)
for index in 0..<numericCast(count) { let protocal = protocol_getName(protocalList?[index])
print("协议:%@",String.init(utf8String: protocal!) ?? "没有找到你想要的协议")
}
4、方法交换
这个就是Runtime的一个重点了,仔细说一说。
OC的动态性最常用的其实就是方法的替换,将某个类的方法替换成自己定义的类,从而达到Hook的作用。(以前面试有人问过OC怎样Hook一个消息,那时候太懵懂,不知道怎么说!不知道大家有没有遇到过?)
对于纯粹的Swift类,由于前面的测试你知道无法拿到类的属性饭方法等,也就没办法进行方法的替换,但是对于继承自NSObject的类,由于集成了OC的所有特性,所以是可以利用Runtime的属性来进行方法替换,记得我们前面说的dynamic关键字。
func ChangeMethod() -> Void {
// 获取交换之前的方法
let originaMethodC = class_getInstanceMethod(object_getClass(self), #selector(self.originaMethod))
// 获取交换之后的方法
let swizzeMethodC = class_getInstanceMethod(object_getClass(self), #selector(self.swizzeMethod))
//替换类中已有方法的实现,如果该方法不存在添加该方法
//获取方法的Type字符串(包含参数类型和返回值类型)
//class_replaceMethod(object_getClass(self), #selector(self.swizzeMethod), method_getImplementation(originaMethodC), method_getTypeEncoding(originaMethodC))
print("你交换两个方法的实现")
method_exchangeImplementations(originaMethodC, swizzeMethodC)
}
dynamic func originaMethod() -> Void {
print("我是交换之前的方法")
}
dynamic func swizzeMethod() -> Void {
print("我是交换之后的方法")
}
5、关联属性
说上面的方法Hook比较重要的话,这个关联属性也是比较重要的,在前面我总结OC的Runtime的时候在方法的添加这里专门有提过一个Demo,我们把这个Demo重新整理一下,导航的渐变就是利用Runtime给导航添加属性来实现的。
extension UINavigationBar {
var navigationGradualChangeBackgroundView:UIView?{
get{
return objc_getAssociatedObject(self, &self.navigationGradualChangeBackgroundView) as? UIView;
}
set{
objc_setAssociatedObject(self, &self.navigationGradualChangeBackgroundView, navigationGradualChangeBackgroundView, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
func setNavigationBackgroundColor (backgroundColor: UIColor) -> Void {
if (self.navigationGradualChangeBackgroundView == nil) {
self.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationGradualChangeBackgroundView = UIView.init(frame: CGRect.init(x: 0, y: -20, width: SCREENWIDTH, height: self.bounds.size.height + 20))
self.navigationGradualChangeBackgroundView!.isUserInteractionEnabled = false
self.insertSubview(self.navigationGradualChangeBackgroundView!, at: 0)
}
self.navigationGradualChangeBackgroundView!.backgroundColor = backgroundColor
}
func removeNavigationBackgroundColor() -> Void {
self.setBackgroundImage(nil, for: UIBarMetrics.default)
self.navigationGradualChangeBackgroundView!.removeFromSuperview()
self.navigationGradualChangeBackgroundView = nil
}
}
1、上面是给UINavigationBar添加扩展来写的,注意Swift的写法和OC的区别。
2、在应用这点知识的时候,可以直接在ScrollView滚动的代理方法里面通过滚动距离的改变透明度生成你需要的Color,然后直接就在它的代理方法中调用setNavigationBackgroundColor方法即可。
看个其他的例子
在整理资料的时候,发现了一篇文章: iOS---防止UIButton重复点击的三种实现方式
在最后面说道的利用Runtime的方法解决的时候,最后是这样一段代码:

说明:
可以看到最后是直接把自己定义的方法和系统的方法交换了,重点就是自己方法里面的实现!
可以看到在自己定义的方法前面加了时间判断,最后还是调用了方法本身!这样就有了一个问题。你用自己的方法代替了系统的方法,加入了自己的一些东西,最有没有再去调用系统的方法?你不知道系统方法实现的具体内容却直接用自己的方法规代替了,那系统按钮的功能肯定是受到影响的!大家应该能理解我说的意思。那我们就得记得一点:
切记: 我们使用 Method Swizzling(方法交换) 的目的通常都是为了给程序增加功能,而不是完全地替换某个功能,所以我们一般都需要在自定义的实现中调用原始的实现。
针对这一点特别说明一下,怎么修改的其实原文下面的同学也有给出了答案的,具体的内容建议大家看看这篇文章,应该会有收获!
Objective-C Method Swizzling 的最佳实践
Swift Runtime ?的更多相关文章
- Swift Runtime动态性分析
Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objec ...
- Swift -> RunTime(动态性) 问题 浅析
Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objec ...
- Google可能会用苹果的Swift 为什么?
Google可能会用苹果的Swift 为什么? 2014 年夏天,苹果在 WWDC 大会上宣布了全新的程序语言 Swift,主要用来开发 iOS 与 OSX 应用. 去年年底,苹果将 Swift 开源 ...
- 什么是runtime?什么是webgl?
一 什么是Runtime? Egret官方解释:https://www.egret.com/products/runtime.html 二.什么是WebGL渲染? egret官方解释:http://d ...
- Swift :?和 !
Swift语言使用var定义变量,但和别的语言不同,Swift里不会自动给变量赋初始值, 也就是说变量不会有默认值,所以要求使用变量之前必须要对其初始化 .如果在使用变量之前不进行初始化就会报错: v ...
- iOS(Swift)-Runtime之关于页面跳转的捷径【Runtime获取当前ViewController,很常用】
写在前面 在我们操作页面跳转时,如果当前的类不是UIViewcontroller(下面用VC表示),你会不会写一个代理,或者block给VC传递信息,然后在VC里面进行 ///假如targetVc是将 ...
- 如何在SCENEKIT使用SWIFT RUNTIME动态加载COLLADA文件
问题:今天接到一个项目,负责弄需求的美眉跟我讲能不能做一个原型能够加载Collada文件,流程如下: 用户用app下载Collada 压缩包(如内购项目) 压缩包解压 展示Collada文件里的内容 ...
- 25.怎样创建一个Swift项目?
经历前面三部分的学习之后,我们对于Swift的有了基本的了解,知道它的基础语法,也知道了类.结构体.枚举.协议.扩展等等内容.但知道上面这些内容,并不代表我们就能很好的进行实际的项目开发了,本部分内容 ...
- swift中 ?和 !的区别
可选类型(?)与强制解析运算符(!) ?是一种判断后再拆包的语法糖 !是一种强制拆包的语法糖 当你不确定有值的时候就可以用 ? 当你确定有值的时候可以用 ! ?的几种使用场景:1. ...
随机推荐
- 一张表搞懂各种 Docker 监控方案 - 每天5分钟玩转 Docker 容器技术(86)
前面我们已经介绍了ps/top/stats.Sysdig.Weave Scope.cAdvisor 和 Prometheus 多种容器监控工具和方案,是时候做一个比较了.下面将从五个方面来对比它们之间 ...
- JavaScrpit中异步请求Ajax实现
在前端页面开发的过程中,经常使用到Ajax请求,异步提交表单数据,或者异步刷新页面. 一般来说,使用Jquery中的$.ajax,$.post,$.getJSON,非常方便,但是有的时候,我们只因为需 ...
- Ionic3 启动非常慢
优化: ionic cordova build android --prod --release
- USACO奶牛赛跑(逆序对)
Description 约翰有 N 头奶牛,他为这些奶牛准备了一个周长为 C 的环形跑牛场.所有奶牛从起点同时起跑,奶牛在比赛中总是以匀速前进的,第 i 头牛的速度为 Vi.只要有一头奶牛跑完 L 圈 ...
- ssh分发秘钥时出现错误“Permission denied (publickey,gssapi-keyex,gssapi-with-mic)”
因为公司的服务器连接是通过xshell公钥和密码连接的,今天在ssh分发秘钥的时候出现了,下面的错误: [root@iZ2ze97cumk8opqm28h8Z .ssh]# ssh-copy-id - ...
- scroll 区域滚动
网页内都有快速滚动和回弹的效果: overflow: scroll; -webkit-overflow-scrolling: touch; 实际上,Safari用了原生控件来实现,对于有-webk ...
- mysql将字符串转化为数字
我的字段为内容为数字,但是类型为字符串,需要使用CASE转换即可 SELECT MAX(CAST(C_id AS UNSIGNED)) INTO id 即查询出来最大的C_id,否则会按照字符串查询最 ...
- 解决Cordova安装Cannot find module 'bplist-parser'问题
安装完cordova出现找不到必须的模块 出现原因不明,不过如果出现这个问题,我们遇到缺什么模块就安装什么模块就可以了.如图所示
- Intellij16创建Spring-Mybatis项目创(填)建(坑)记录,解决IDEA下找不到xml文件的问题
转入Intellij已经有1个月了,编程效率确实比Eclipse快了很多,而且可以直接使用Maven,然后就想写个小项目玩玩,架构搭建完后,想着万事俱备,又不是第一次玩框架,照葫芦画瓢撑死半天就能完成 ...
- Java设计模式相关面试
1.接口是什么?为什么要使用接口而不是直接使用具体类? 接口用于定义 API.它定义了类必须得遵循的规则.同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使 ...