杂谈转载

一、什么是运行时(Runtime)?

  • 运行时是苹果提供的纯C语言的开发库(运行时是一种非常牛逼、开发中经常用到的底层技术)

二、运行时的作用?

  • 能获得某个类的所有成员变量
  • 能获得某个类的所有属性
  • 能获得某个类的所有方法
  • 交换方法实现
  • 能动态添加一个成员变量
  • 能动态添加一个属性
  • 能动态添加一个方法

三、案例:运行时获取成员变量名称

  • 1、分析
#import <Foundation/Foundation.h>
#import "XMGPerson.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 成员变量的数量
        unsigned int outCount = 0;

        // 获得所有的成员变量
        // ivars是一个指向成员变量的指针
        // ivars默认指向第0个成员变量(最前面)
        Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount);

        // 遍历所有的成员变量
        for (int i = 0; i<outCount; i++) {
            // 取出i位置对应的成员变量
//            Ivar ivar = *(ivars + i);
            Ivar ivar = ivars[i];
            // 获得成员变量的名字
            NSLog(@"%s", ivar_getName(ivar));
        }

        // 如果函数名中包含了copy\new\retain\create等字眼,那么这个函数返回的数据就需要手动释放
        free(ivars);

//        Ivar ivar = *ivars;
//        Ivar ivar2 = *(ivars + 1);
//        NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2));

        // 一个Ivar就代表一个成员变量

        // int *p; 指向int类型的变量
        // Ivar *ivars; 指向Ivar类型的变量
    }
    return 0;
}
  • 2、获取UITextFiled成员变量的名称

Snip20151027_1.png

 // 成员变量的数量
    unsigned int outCount = 0;

    // 获得所有的成员变量
    Ivar *ivars = class_copyIvarList([UITextField class], &outCount);

    // 遍历所有的成员变量
    for (int i = 0; i<outCount; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 获得成员变量的名字
        NSLog(@"%s", ivar_getName(ivar));
    }

    // 如果函数名中包含了copy\new\retain\create等字眼,那么这个函数返回的数据就需要手动释放
    free(ivars);

四、iOS底层

1、The Runtime 简单介绍

  • Objective-C是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。

Messages

  • 执行一个方法,有些语言,编译器会执行一些额外的优化和错误检查,因为调用关系很直接也很明显。但对于消息分发来说,就不那么明显了。在发消息前不必知道某个对象是否能够处理消息。你把消息发给它,它可能会处理,也可能转给其他的Object来处理。一个消息不必对应一个方法,一个对象可能实现一个方法来处理多条消息。
  • 在Objective-C中,消息是通过objc_msgSend()这个runtime方法及相近的方法来实现的。这个方法需要一个target,selector,还有一些参数。理论上来说,编译器只是把消息分发变成objc_msgSend来执行。比如下面这两行代码是等价的。
[array insertObject:foo atIndex:5];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

Objects, Classes, MetaClasses

  • 大多数面向对象的语言里有 classes 和 objects 的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects,也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。
typedef struct objc_object {
    Class isa;
} *id;
  • object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型: metaclasses。一个 metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass。如果想更清楚地了解objects,classes以及metaclasses是如何一起工作地,可以阅读这篇文章。

Methods, Selectors and IMPs

  • 我们知道了运行时会发消息给对象。我们也知道一个对象的class保存了方法列表。那么这些消息是如何映射到方法的,这些方法又是如何被执行的呢?
  • 第一个问题的答案很简单。class的方法列表其实是一个字典,key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样我们就能玩出些花样。
  • IMP通常是指向方法的指针,第一个参数是self,类型为id,第二个参数是_cmd,类型为SEL,余下的是方法的参数。这也是self和_cmd被定义的地方。下面演示了Method和IMP
- (id)doSomethingWithInt:(int)aInt{}

id doSomethingWithInt(id self, SEL _cmd, int aInt){}
  • 现在我们知道了objects,classes,selectors,IMPs以及消息分发,那么运行时到底能做什么呢?

运行时到底能做什么呢?

  • 作用:

    • 创建、修改、自省classes和objects
    • 消息分发
  • 之前已经提过消息分发,不过这只是一小部分功能。所有的运行时方法都有特定的前缀。下面是一些有意思的方法:

class

  • class开头的方法是用来修改和自省classes。
  • 方法如:
    • 能拿到一个class的所有内容

      class_addIvar, class_addMethod, class_addProperty和class_addProtocol允许重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList
    • 返回单个内容
      class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty
    • 一些通用的自省方法
      class_conformsToProtocol, class_respondsToSelector, class_getSuperclass
    • 创建一个object
      class_createInstance来创建一个object

ivar

  • 这些方法能让你得到名字,内存地址和Objective-C type encoding。

method

  • 这些方法主要用来自省,比如:
method_getName, method_getImplementation, method_getReturnType等等
  • 也有一些修改的方法,包括:

    method_setImplementation和method_exchangeImplementations

objc

  • 一旦拿到了object,你就可以对它做一些自省和修改。你可以get/set ivar, 使用object_copy和object_dispose来copy和free object的内存。不仅是拿到一个class,而是可以使用object_setClass来改变一个object的class。

property

  • 属性保存了很大一部分信息。除了拿到名字,你还可以使用property_getAttributes来发现property的更多信息,如返回值、是否为atomic、getter/setter名字、是否为dynamic、背后使用的ivar名字、是否为弱引用。

protocol

  • Protocols有点像classes,但是精简版的,运行时的方法是一样的。你可以获取method, property, protocol列表, 检查是否实现了其他的protocol。

sel

  • 最后我们有一些方法可以处理 selectors,比如获取名字,注册一个selector等等。

2、运行时能干什么?(举例)

2.1 Classes And Selectors From Strings

  • 比较基础的一个动态特性是通过String来生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起来很简单:

    Class stringclass = NSClassFromString(@"NSString")
  • 于是我们就得到了一个string class。接下来:
    NSString *myString = [stringclass stringWithString:@"Hello World"];
  • 为什么要这么做呢?直接使用Class不是更方便?通常情况下是,但有些场景下这个方法会很有用。首先,可以得知是否存在某个class,NSClassFromString 会返回nil,如果运行时不存在该class的话。
  • 另一个使用场景是根据不同的输入返回不同的class或method。比如你在解析一些数据,每个数据项都有要解析的字符串以及自身的类型(String,Number,Array)。你可以在一个方法里搞定这些,也可以使用多个方法。其中一个方法是获取type,然后使用if来调用匹配的方法。另一种是根据type来生成一个selector,然后调用之。以下是两种实现方式:
- (void)parseObject:(id)object {
    for (id data in object) {
        if ([[data type] isEqualToString:@"String"]) {
            [self parseString:[data value]];
        } else if ([[data type] isEqualToString:@"Number"]) {
            [self parseNumber:[data value]];
        } else if ([[data type] isEqualToString:@"Array"]) {
            [self parseArray:[data value]];
        }
    }
}
- (void)parseObjectDynamic:(id)object {
    for (id data in object) {
        [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]];
    }
}
- (void)parseString:(NSString *)aString {}
- (void)parseNumber:(NSString *)aNumber {}
- (void)parseArray:(NSString *)aArray {}
  • 可一看到,你可以把7行带if的代码变成1行。将来如果有新的类型,只需增加实现方法即可,而不用再去添加新的 else if。

2.2 Method Swizzling

  • 之前我们讲过,方法由两个部分组成。Selector相当于一个方法的id;IMP是方法的实现。这样分开的一个便利之处是selector和IMP之间的对应关系可以被改变。比如一个 IMP 可以有多个 selectors 指向它。
  • 而 Method Swizzling 可以交换两个方法的实现。或许你会问“什么情况下会需要这个呢?”。我们先来看下Objective-C中,两种扩展class的途径。首先是 subclassing。你可以重写某个方法,调用父类的实现,这也意味着你必须使用这个subclass的实例,但如果继承了某个Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。这种情况下,你会想添加一个方法到NSArray,也就是使用Category。99%的情况下这是OK的,但如果你重写了某个方法,就没有机会再调用原先的实现了。
  • Method Swizzling 可以搞定这个问题。你可以重写某个方法而不用继承,同时还可以调用原先的实现。通常的做法是在category中添加一个方法(当然也可以是一个全新的class)。可以通过method_exchangeImplementations这个运行时方法来交换实现。来看一个demo,这个demo演示了如何重写addObject:方法来纪录每一个新添加的对象。
#import  <objc/runtime.h>

@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end

@implementation NSMutableArray (LoggingAddObject)

+ (void)load {
    Method addobject = class_getInstanceMethod(self, @selector(addObject:));
    Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
    method_exchangeImplementations(addObject, logAddObject);

}

- (void)logAddObject:(id)aobject {
    [self logAddObject:aObject];
    NSLog(@"Added object %@ to array %@", aObject, self);
}

@end
  • 我们把方法交换放到了load中,这个方法只会被调用一次,而且是运行时载入。如果指向临时用一下,可以放到别的地方。注意到一个很明显的递归调用logAddObject:。这也是Method Swizzling容易把我们搞混的地方,因为我们已经交换了方法的实现,所以其实调用的是addObject:

动态继承、交换

  • 我们可以在运行时创建新的class,这个特性用得不多,但其实它还是很强大的。你能通过它创建新的子类,并添加新的方法。
  • 但这样的一个子类有什么用呢?别忘了Objective-C的一个关键点:object内部有一个叫做isa的变量指向它的class。这个变量可以被改变,而不需要重新创建。然后就可以添加新的ivar和方法了。可以通过以下命令来修改一个object的class.
    object_setClass(myObject, [MySubclass class]);
  • 这可以用在Key Value Observing。当你开始observing an object时,Cocoa会创建这个object的class的subclass,然后将这个object的isa指向新创建的subclass。

动态方法处理

  • 目前为止,我们讨论了方法交换,以及已有方法的处理。那么当你发送了一个object无法处理的消息时会发生什么呢?很明显,"it breaks"。大多数情况下确实如此,但Cocoa和runtime也提供了一些应对方法。
  • 首先是动态方法处理。通常来说,处理一个方法,运行时寻找匹配的selector然后执行之。有时,你只想在运行时才创建某个方法,比如有些信息只有在运行时才能得到。要实现这个效果,你需要重写+resolveInstanceMethod: 和/或 +resolveClassMethod:。如果确实增加了一个方法,记得返回YES。
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
    if (aSelector == @selector(myDynamicMethod)) {
        class_addMethod(self, aSelector, (IMP)myDynamicIMP, "[email protected]:");
        return YES;
    }
    return [super resolveInstanceMethod:aSelector];
}
  • 那Cocoa在什么场景下会使用这些方法呢?Core Data用得很多。NSManagedObjects有许多在运行时添加的属性用来处理get/set属性和关系。那如果Model在运行时被改变了呢?

消息转发

  • 如果 resolve method 返回NO,运行时就进入下一步骤:消息转发。有两种常见用例。1) 将消息转发到另一个可以处理该消息的object。2) 将多个消息转发到同一个方法。
  • 消息转发分两步。首先,运行时调用-forwardingTargetForSelector:,如果只是想把消息发送到另一个object,那么就使用这个方法,因为更高效。如果想要修改消息,那么就要使用-forwardInvocation:,运行时将消息打包成NSInvocation,然后返回给你处理。处理完之后,调用invokeWithTarget:。
  • Cocoa有几处地方用到了消息转发,主要的两个地方是代理(Proxies)和响应链(Responder Chain)。NSProxy是一个轻量级的class,它的作用就是转发消息到另一个object。如果想要惰性加载object的某个属性会很有用。NSUndoManager也有用到,不过是截取消息,之后再执行,而不是转发到其他的地方。
  • 响应链是关于Cocoa如何处理与发送事件与行为到对应的对象。比如说,使用Cmd+C执行了copy命令,会发送-copy:到响应链。首先是First Responder,通常是当前的UI。如果没有处理该消息,则转发到下一个-nextResponder。这么一直下去直到找到能够处理该消息的object,或者没有找到,报错。

使用Block作为Method IMP

  • iOS 4.3带来了很多新的runtime方法。除了对properties和protocols的加强,还带来一组新的以 imp 开头的方法。通常一个 IMP 是一个指向方法实现的指针,头两个参数为 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 带来了block,imp_implementationWithBlock() 能让我们使用block作为 IMP,下面这个代码片段展示了如何使用block来添加新的方法。
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
    NSLog(@"Hello %@", string);
});
class_addMethod([MYclass class], @selector(sayHello:), myIMP, "[email protected]:@");
  • 可以看到,Objective-C 表面看起来挺简单,但还是很灵活的,可以带来很多可能性。动态语言的优势在于在不扩展语言本身的情况下做很多很灵巧的事情。比如Key Value Observing,提供了优雅的API可以与已有的代码无缝结合,而不需要新增语言级别的特性。
时间: 2024-10-12 08:11:09

杂谈转载的相关文章

【转载】javascript 杂谈之哪种写法你更喜欢?

转载自:http://www.cnblogs.com/baochuan/archive/2012/04/30/2473771.html 思维导图 介绍 老是在写js,你平时是怎么写你的js呢?更喜欢哪种方式呢? 今天就把常见的跟大家唠叨唠叨.——年纪大了,就这一爱好(平时也喜欢说胡话),别闲我烦! 直接初始化法 优点: 1.userData本身就是对象实例.——上来就实例化一个类出来,也不怕浏览器受不了. 2.代码紧凑. 3.编程效率高. 缺点: 1.代码的重用性比较差. 2.不符合面向对象的编

创建型模式杂谈

[本文首发于cnblogs    作者:byeyear    Email:[email protected]    转载请注明] 本文是关于创建型模式的杂谈,全文的组织结构比较松散,但基本上还是依着原文的编写顺序来谈. A. 约定 使用”产品“来指代中间产品(如Room.Door.Wall等): 使用”成品“来指代最终的产品(如Maze). B. 创建型模式分类 创建型模式可分为两类:类创建型模式和对象创建型模式. B.1 类创建型模式使用继承改变被实例化的类,例如Factory Method,

Android开发之方向传感器的使用? (2011-10-13 20:56:05)转载▼

Android开发之方向传感器的使用 (2011-10-13 20:56:05) 转载▼ 标签: android 传感器 方向 指南针 杂谈 分类: 3GAndroid学习笔记 Android中的方向传感器在生活中是一个很好的应用,典型的例子是指南针的使用,我们先来简单介绍一下传感器中三个参数x,y,z的含义,以一幅图来说明.                                      图 1 补充说明:图中的坐标轴x,y,z和传感器中的X,Y,Z没有任何联系! 如上图所示,绿色部

[python爬虫] Selenium定向爬取海量精美图片及搜索引擎杂谈

我自认为这是自己写过博客中一篇比较优秀的文章,同时也是在深夜凌晨2点满怀着激情和愉悦之心完成的.首先通过这篇文章,你能学到以下几点:        1.可以了解Python简单爬取图片的一些思路和方法        2.学习Selenium自动.测试分析动态网页和正则表达式的区别和共同点        3.了解作者最近学习得比较多的搜索引擎和知识图谱的整体框架        4.同时作者最近找工作,里面的一些杂谈和建议也许对即将成为应届生的你有所帮助        5.当然,最重要的是你也可以尝

转载::POJ 2991 线段树+计算几何(有c++结构体操作)

POJ 2991 线段树+计算几何 (2011-02-27 21:13:44) 转载▼ 标签: 杂谈 分类: OI 话说这一题真的是很恶心很恶心,不过确实改变了我对线段树的一些看法,算是很经典的题目. 题意:有一个吊车由很多个不同长度的线段组成,一开始是一条长直线起点在(0,0),尾节点在(0,sum[n]),每条线段之间的夹角的初始值是180度.然后有一些操作a. b将第a条线段和a+1之间的夹角变成b度,经过每一次操作都要求出尾节点的坐标. 首先要用到一个计算几何的知识(没学过..请教而来)

black-hole《杂谈如何绕过WAF》阅读笔记

0×01开场白 这个议题呢,主要是教大家一个思路,而不是把现成准备好的代码放给大家. 可能在大家眼中WAF(Web应用防火墙)就是"不要脸"的代名词.如果没有他,我们的"世界"可能会更加美好.但是事与愿违.没有它,你让各大网站怎么活.但是呢,我是站在你们的这一边的,所以,今天我们就来谈谈如何绕过WAF吧.之所以叫做"杂谈",是因为我在本次演讲里,会涉及到webkit.nginx&apache等.下面正式开始:) 0×02直视WAF: 作为

Java中的Random()函数-----转载

Java中的Random()函数 (2013-01-24 21:01:04) 转载▼ 标签: java random 随机函数 杂谈 分类: Java 今天在做Java练习的时候注意到了Java里面的一个随机函数——Random,刚开始只是知道这个函数具有随机取值的作用,于是上网搜索了资料一番,做了一下一些关于Random函数的总结: Java中存在着两种Random函数: 一.java.lang.Math.Random; 调用这个Math.Random()函数能够返回带正号的double值,该

开篇—杂谈WebGIS

1.前言 我相信大家对百度地图,谷歌地图等相关应用已经是非常熟悉了.通过这些应用,我们可以浏览地图.定位我们自己的位置.查找我们想知道的兴趣点.搜索交通路线等等.而其实,这些功能便是WebGIS的一些最基本的功能. 那么什么是WebGIS呢,说到这里,我还是首先给大家讲讲什么是GIS. 2.GIS简介 2.1 GIS的名词解释 GIS的全名是Geographic Information System,中文全名是地理信息系统.它是在计算机硬.软件系统支持下,对整个或部分地球表层(包括大气层)空间中

《跨界杂谈》华为印象之混搭儿(八)美人痣与小酒窝

2015-03-02 雷震子 雷震子 雷震子 微信号 功能介绍 围绕<跨界>书的撰写 ,IT.云计算等话题聊心得 人无完人,或多或少都会有些缺陷,即使在理想化的艺术作品里也是如此,但缺陷如果恰到好处,反而会起到映衬人的整体美感的效果,比如维纳斯雕像,被称作残缺美. 美人痣与小酒窝儿 "痣"是一种皮肤疾病,属于肿瘤的一种,在非诚勿扰II里,甚至要了主人公李香山(孙红雷饰演)的命,但大部分人都不会太在意,也没啥大碍,而且这个痣在女孩脸上长的到位,大小合适,那就有一种美感,历史人