swift Runtime

Runtime简称运行时。OC就是运行时机制,也就是在程序运行时候的一些机制,其中最主要的是消息机制。对于我们熟悉的C语言,函数的调用在编译的时候会决定调用哪个函数。但对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

 也就有了下面这两点结论:
 1、在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
 2、在编译阶段,C语言调用未实现的函数就会报错。

我们定义一个纯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类的函数的调用已经不是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")
    }
}

嗯,我们利用了dynamic(英文单词动态的意思)关键字,在第一个变量和方法的定义前面我们添加了这个关键字,那添加了这个关键字之后又什么变化呢?我们再通过最开始我们获取纯Swift类的代码获取一下试试,看结果!

结果:

可以看到这里是获取到了变量了的。(这里是获取属性没有写获取方法代码所以是值拿到变量没有拿到方法)

aBoll 这个变量前面是添加了dynamic关键字的,我们获取到了。在aInt这个变量前面我们是没有添加的,所以可以看到我们是没有获取到这个变量的,那关键的就是我们要理解:dynamic 关键字的含义:

首先有 @objc 这个关键字,它是用来将Swift的API导出来给 Object-C 和 Runtime 使用的,如果你类继承自OC的类,这个标识符就会被自动加进去,加了这标识符的属性、方法无法保证都会被运行时调用,因为Swift会做静态优化,想要完全被声明成动态调用,必须使用 dynamic 标识符修饰,当然添加了 dynamic 的时候,它会自己在加上@objc这个标识符。

这样我们就理解了dynamic这个关键字,知道了它的作用,那我们接下来就是尝试着多使用一下 Swift Runtime。

Swift Runtime

1、获取方法:

    let methodList = class_copyMethodList(object_getClass(SwiftClass), &count)
    for ind in 0..<numericCast(count) {
        let method = method_getName(methodList![ind])
        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
}
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353