iOS Runtime基础

RuntimeObject-C的一种特性,本人并不感冒。不过这块内容却很流行,也是Object-C动态特性的来源,被认为是比Swift好的地方。
平时用不用是一回事,知道这些基础知识还是有好处的,至少跟人聊的时候能打上话。
动态特性带来了方便,但是同时也带来了很大的安全隐患,在使用的时候尽量谨慎一点。
另外,这是底层的函数,像ARC这种偷懒用的好特性就没有了,要注意内存泄漏问题。(基本上无法避免)

Runtime全方位装逼指南
RuntimeLearn
这篇文章写得比较好,基础概念写得比较清晰,值得优先读

iOS动态性(二)可复用而且高度解耦的用户统计埋点实现
统计埋点确实是一个比较典型的应用,这篇文章写得比较清楚

对象、类、元类

  • Object-C是一种面向对象的语言。“一切皆对象”是本质的一点。Object-C就是借助实例,类,元类三级结构来实现这一点的。

  • isa指针是Object-C采用c语言的"结构体"来实现面向对象的方法。

  • 实例的isa指向对应的类,类的isa指向元类。

  • 元类的isa都指向根元类,根元类的isa指向自己。

  • 类和元类除了isa指针,还有一个super_class指针,指向父类(父元类)。根类的父类指向nil

  • 元类保存静态变量和静态类方法

  • 跟元类的父类指向根类,根类的isa指针指向根元类

Object-C类图.jpg

消息发送

  • Objective-C 中的方法调用,不是简单的方法调用,而是发送消息,也就是说,其实[receiver message] 会被编译器转化为: objc_msgSend(receiver, selector)

  • 这个是Objective-C动态特性的本质;在函数调用之前插入一个消息转发,按照对象(id),函数(SEL),参数三级结构实现动态特性。

  • 一些函数定义

void objc_msgSend(void /* id self, SEL op, ... */ );
typedef struct objc_selector *SEL;
typedef struct objc_object *id;

// 下面几个都是将字符串转换为函数指针SEL;根据使用场景选择方便的
// 这个c字符串
SEL 变量名 = sel_registerName(const char *str); // 在c的模块中推荐用
// 下面两个是NSString
SEL 变量名 = NSSelectorFromString(NSString *aSelectorName); // 推荐用这个
SEL 变量名 = @selector(NSString *aSelectorName); // 这个用得比较多,不过难理解,不是很推荐
  • 例子,有个类TestClass,有如下方法和调用
- (void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight{
    NSLog(@"size is %.2f * %.2f",aWidth, aHeight);
}

TestClass *testObject = [[TestClass alloc] init];
[testObject showSizeWithWidth:110.5f andHeight:200.0f]

也可以用下面的调用方式:

((void (*) (id, SEL, float, float)) objc_msgSend) (testObject, sel_registerName("showSizeWithWidth:andHeight:"), 110.5f, 200.0f);
  • 这个就是Object-C动态特性的来源。id、SEL都是一些指向结构体的指针,objc_msgSend的类型是void,在具体使用的时候需要强制转化为需要的类型。(参数类型,返回值类型都要考虑到)。这里有很大的安全隐患,代码难懂,很容易出错。所以本人一直不建议用。

  • 编译器会根据情况在 objc_msgSend,objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper_stret或 objc_msgSend_fpret 五个方法中选择一个来调用。如果消息是传递给超类,那么会调用 objc_msgSendSuper方法,如果消息返回值是数据结构,就会调用 objc_msgSendSuper_stret 方法,如果返回值是浮点数,则调用 objc_msgSend_fpret方法。

类的本质

  • Class 也是一个结构体指针类型
typedef struct objc_class *Class;

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
  • ivars:指向该类的成员变量列表。大多数情况可以认为这个也是类的属性列表。不过有说法认为有另外的属性列表,只是这里看不出来。

  • methodLists:指向该类的实例方法列表,它将方法选择器和方法实现地址联系起来。这也是Category实现的原理,同样解释了 Category不能添加属性的原因。

  • protocols:指向该类的协议列表。

这里没有单独的属性列表,给理解带来了困难。如果都是基本类型,可以认为成员变量列表就是属性列表,比如下面的“自动归档”部分处理的那样。
另外一种说法是有单独的属性列表,只是这里没有显示出来。比如“字典转模型”,就使用了属性列表。
class_copyPropertyList和class_copyIvarList的区别

远程调用

  • 有些时候,比如首页,显示的内容由后台决定,实现所谓的“千人千面”

  • 这里的实现基础,就是类Class、方法SEL与字符串NSString的互转

FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);

FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);
  • 有了Class之后,就可以通过id object = [[Class alloc] init];得到对象

  • 有对象和SEL之后,就可以通过函数执行

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
  • 客户端先把调用的类和方法在本地实现好,后台发送类名和方法名的字符串,然后根据字符串,实现动态调用。

  • 本人经历过的四五个App中有一个是采用这种方案实现动态首页的。另外,网上也有对这个问题的详细描述。下面这篇链接是比较好的一篇。

iOS应用架构谈 组件化方案
CTMediator

  • 关于组建化,本人更偏向于蘑菇街的方案。原因是这个方案url的编码更自由一点,对应关系可以自定义。另外,有蘑菇街的实践也是一个考虑原因。

蘑菇街 App 的组件化之路
MGJRouter

对象关联

  • Category可以添加方法,但是怎么样添加属性呢?答案是通过对象关联的方法。

  • Category中的属性,只会生成settergetter方法,不会生成成员变量

  • 关联的属性和一个全局变量关联,那个key一般是一个静态全局变量

  • 相关函数:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object); // 移除所有关联属性,不要轻易使用

// 属性的修饰符,根据情况设置
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

这方面的资料很多,使用也相对简单,比如下面就有一篇:
iOS-OC-Runtime使用小谈(objc_setAssociatedObject)

自动归档

  • 自动归档主要是实现NSCoding协议
@protocol NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER

@end
  • 相关函数
// 把成员变量当做属性,获取列表
Ivar *class_copyIvarList(Class cls, unsigned int *outCount);
// 获得成员变量的名字,带_前缀;这是c字符串
const char *ivar_getName(Ivar v) ;
// 通过KVC获得成员变量的值
- (nullable id)valueForKey:(NSString *)key;
// 通过KVC设置成员变量的值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

字典与模型互转

  • 在类的头文件中定义的属性,不包括额外定义的成员变量

  • 使用属性列表函数,而不是成员列表函数

// 属性列表,不是成员变量列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
// 获取属性的名字
const char *property_getName(objc_property_t property) ;
  • 可以使用KVC,也可以使用objc_msgSend调用gettersetter函数进行值的读取和设置

字典转模型,自动归档等推荐的第三方库为YYModel,实际用过,确实很方便
YYModel

方法动态解析

给一个对象发消息,就是执行objc_msgSend(id, SEL, ...)函数。使用很小心,id、SEL都正确的情况下,当然没问题。但是,如果出错了呢?
是的,崩溃,崩溃信息一般如下:
unrecognized selector sent to instance ...

  • 不是SEL找不到吗?下面这个函数就是给机会,修改SEL参数
+ (BOOL)resolveInstanceMethod:(SEL)sel;
  • 这个对象没有,其他对象可能有啊。下面这个函数就是给机会,修改id参数
- (id)forwardingTargetForSelector:(SEL)aSelector;
  • 其他对象也没有这个SEL,那么再给机会,id、SEL都改,完全自定义。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 最后,就会抛出异常,就是常说的崩溃
- (void)doesNotRecognizeSelector:(SEL)aSelector;
  • 这块内容具体的应用场景在实际工作中还没有遇到过。上面这些函数都在NSObject.h文件中定义,是基类中的函数。

继承自NSObject的不常用又很有用的函数(2)

方法交换

  • 这个是有使用场景的,最常见的场景是“统计埋点”

iOS动态性(二)可复用而且高度解耦的用户统计埋点实现

  • 这项技术有一个专门的名字叫Method Swizzling,为什么这么叫,原因不清楚。

Objective-C的hook方案(一): Method Swizzling

  • 主要用到的函数:
Method class_getInstanceMethod(Class cls, SEL name);
void method_exchangeImplementations(Method m1, Method m2);

IMP method_getImplementation(Method m);
IMP method_setImplementation(Method m, IMP imp); 
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types); 
  • 可以考虑用来解决崩溃的问题,比如下面的文章

使用method-swizzling让程序更健壮

  • 方法交换需要放在 +(void)load方法中,并且要用dispatch_once进行保护。道理很简单,交换偶数次不就被还原了吗?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • objc_getAssociatedObject返回与给定键的特定对象关联的值。ID objc_getAssoci...
    有一种再见叫青春阅读 1,554评论 0 7
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 743评论 0 1
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,537评论 33 466
  • 今天去广州出了个差,计划好的东西又泡了汤,其实这件事我去不去关系不大,但人生就是这么无奈,自己权力不到,说话没...
    aweness阅读 46评论 0 0