你肯定也想过


在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 ?的更多相关文章

  1. Swift Runtime动态性分析

    Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objec ...

  2. Swift -> RunTime(动态性) 问题 浅析

    Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objec ...

  3. Google可能会用苹果的Swift 为什么?

    Google可能会用苹果的Swift 为什么? 2014 年夏天,苹果在 WWDC 大会上宣布了全新的程序语言 Swift,主要用来开发 iOS 与 OSX 应用. 去年年底,苹果将 Swift 开源 ...

  4. 什么是runtime?什么是webgl?

    一 什么是Runtime? Egret官方解释:https://www.egret.com/products/runtime.html 二.什么是WebGL渲染? egret官方解释:http://d ...

  5. Swift :?和 !

    Swift语言使用var定义变量,但和别的语言不同,Swift里不会自动给变量赋初始值, 也就是说变量不会有默认值,所以要求使用变量之前必须要对其初始化 .如果在使用变量之前不进行初始化就会报错: v ...

  6. iOS(Swift)-Runtime之关于页面跳转的捷径【Runtime获取当前ViewController,很常用】

    写在前面 在我们操作页面跳转时,如果当前的类不是UIViewcontroller(下面用VC表示),你会不会写一个代理,或者block给VC传递信息,然后在VC里面进行 ///假如targetVc是将 ...

  7. 如何在SCENEKIT使用SWIFT RUNTIME动态加载COLLADA文件

    问题:今天接到一个项目,负责弄需求的美眉跟我讲能不能做一个原型能够加载Collada文件,流程如下: 用户用app下载Collada 压缩包(如内购项目) 压缩包解压 展示Collada文件里的内容 ...

  8. 25.怎样创建一个Swift项目?

    经历前面三部分的学习之后,我们对于Swift的有了基本的了解,知道它的基础语法,也知道了类.结构体.枚举.协议.扩展等等内容.但知道上面这些内容,并不代表我们就能很好的进行实际的项目开发了,本部分内容 ...

  9. swift中 ?和 !的区别

      可选类型(?)与强制解析运算符(!) ?是一种判断后再拆包的语法糖 !是一种强制拆包的语法糖   当你不确定有值的时候就可以用  ? 当你确定有值的时候可以用  !     ?的几种使用场景:1. ...

随机推荐

  1. ueditor编辑器插件 chrome中图片上传框延时问题

    最近在项目中使用ueditor插件进行文字的在线编辑功能时,发现这个插件的图片上传弹框在chrome浏览器延迟非常的厉害.经过多方搜索,终于解决.现将解决方案记录如下: 1.修改/Ueditor/di ...

  2. Yii2之事件

    众所周知,yii的三大特性是:属性.事件.行为,上一篇博文简单讲解了yii中的属性,本文接着讲讲yii的事件. 事件是代码解耦的一种方式,设计业务流程的一种模式.在yii2.0中,通过Yii\base ...

  3. LeetCode 461. Hamming Distance (汉明距离)

    The Hamming distance between two integers is the number of positions at which the corresponding bits ...

  4. Android开发中的OpenCV霍夫直线检测(Imgproc.HoughLines()&Imgproc.HoughLinesP())

    本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃   //2017-04-21更新: 很多网友希望能得到源码,由于在公司做的,所以不太方便传出来 ...

  5. 一条咸鱼的梦--python

    毕业到现在已经有一年多了,或者说已经工作了一年多,这样以一个社会人的说法比较贴切吧.工作的这段时间里,我曾经有无数次的在问我该干什么,我想干什么,这好像一个深奥的哲学问题,好像并不是只有我一个毕业生思 ...

  6. JavaScript系列----一切皆是对象

    1.判断对象类型 1.1.typeof 运算符 首先要认识到,typepof是一个运算符,其运算需要一个参数,返回值是参数的类型. typeof使用方法 typeof parameter //使用方法 ...

  7. SQL注入详解

    SQL是一种将SQL代码添加到输入参数中,传递到SQL服务器解析并执行的一种攻击手段 产生的原因 web开发人员无法保证所有输入都已经安全过滤 攻击者利用发送给SQL服务器的输入数据,构造可执行的SQ ...

  8. 《项目架构那点儿事》——浅析web层struts2的构建

    [前言]所谓快速开发,实质上为了节省项目的开支成本,减少程序员的开发时 间,固然就形成了种种二次封装的框架,也就是造轮子,然后我们的程序就按照这个轮子去画瓢,这里我就把公司这几次开发系统的框架源码贴出 ...

  9. 一:详解 HTTP 协议

    本篇文章篇幅比较长,先来个思维导图预览一下. 一张图带你看完本篇文章 一.概述 1.计算机网络体系结构分层 计算机网络体系结构分层 2.TCP/IP 通信传输流 利用 TCP/IP 协议族进行网络通信 ...

  10. MarkdownPad2使用高亮插件

    MarkdownPad 2有插入代码块的功能,但样式却不尽人意,但又不想换个编辑器,找了挺多相关资料,最后在MarkdownPad 2集成prettify高亮插件. 如下相关资料: [HTML] Pr ...