RunTime

Runtime消息传递:

一个对象的方法像这样[obj foo],编译器转成消息发送objc_msgSend(obj, foo),Runtime时执行的流程是这样的:

首先,通过obj的isa指针找到它的class;

在class的method list找foo;

如果class中没到foo,继续往它的superclass中找 ;

一旦找到foo这个函数,就去执行它的实现IMP。、

但这种实现有个问题,效率低。但一个class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class 中另一个重要成员objc_cache 做的事情 - 再找到foo 之后,把foo 的method_name 作为key ,method_imp作为value 给存起来。当再次收到foo 消息的时候,可以直接在cache 里找到,避免去遍历objc_method_list。从前面的源代码可以看到objc_cache是存在objc_class 结构体中的。

缓存查找是Hash查找  排序的方法方法列表查找是二分查找   未排序的方法方法列表查找是遍历查找

对象(object),类(class),方法(method)这几个的结构体:

//对象

struct objc_object {

    Class isa  OBJC_ISA_AVAILABILITY;

};

//类

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;

//方法列表

struct objc_method_list {

    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                        OBJC2_UNAVAILABLE;

#ifdef __LP64__

    int space                                                OBJC2_UNAVAILABLE;

#endif

    /* variable length structure */

    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;

}                                                            OBJC2_UNAVAILABLE;

//方法

struct objc_method {

    SEL method_name                                          OBJC2_UNAVAILABLE;

    char *method_types                                      OBJC2_UNAVAILABLE;

    IMP method_imp                                          OBJC2_UNAVAILABLE;

}

消息传递用到的一些概念:

isa指针:

指针型isa :isa的值代表class的地址

非指针型isa:isa的值的部分代表class的地址

实例对象的 isa 指向类对象

类对象的 isa 指向元类对象

元类对象的 isa 指向元类的基类

类对象(objc_class)

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。

objc_class结构体的定义如下:

struct objc_class {

    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

    Class _Nullable super_class                              OBJC2_UNAVAILABLE;

    const char * _Nonnull name                              OBJC2_UNAVAILABLE;

    long version                                            OBJC2_UNAVAILABLE;

    long info                                                OBJC2_UNAVAILABLE;

    long instance_size                                      OBJC2_UNAVAILABLE;

    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;

    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;

    struct objc_cache * _Nonnull cache                      OBJC2_UNAVAILABLE;

    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

实例(objc_object)

/// Represents an instance of a class.

struct objc_object {

    Class isa  OBJC_ISA_AVAILABILITY;

};

/// A pointer to an instance of a class.

typedef struct objc_object *id;

元类(Meta Class)

就是从isa指针指向的结构体创建,类对象的isa指针指向的我们称之为元类(metaclass)

通过上图我们可以看出整个体系构成了一个自闭环,struct objc_object结构体实例它的isa指针指向类对象,

类对象的isa指针指向了元类,super_class指针指向了父类的类对象,根类指向nil

而元类的super_class指针指向了父类的元类,那元类的isa指针又指向了自己。

Method(objc_method)

runtime.h

/// An opaque type that represents a method in a class definition.代表类定义中一个方法的不透明类型

typedef struct objc_method *Method;

struct objc_method {

    SEL method_name                                          OBJC2_UNAVAILABLE;

    char *method_types                                      OBJC2_UNAVAILABLE;

    IMP method_imp                                          OBJC2_UNAVAILABLE;

Method和我们平时理解的函数是一致的,就是表示能够独立完成一个功能的一段代码

SEL(objc_selector)

selector是SEL的一个实例。

IMP

类缓存(objc_cache) 局部性原理

Category(objc_category)


Runtime消息转发:

动态方法解析 

首先,Objective-C运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    //执行foo函数

    [self performSelector:@selector(foo:)];

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(foo:)) {//如果是执行foo函数,就动态解析,指定新的IMP

        class_addMethod([self class], sel, (IMP)fooMethod, "v@:");

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

void fooMethod(id obj, SEL _cmd) {

    NSLog(@"Doing foo");//新的foo函数

}

备用接收者

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    return YES;//返回YES,进入下一步转发

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

    if (aSelector == @selector(foo)) {

        return [Person new];//返回Person对象,让Person对象接收这个消息

    }

    return [super forwardingTargetForSelector:aSelector];

}

完整消息转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。

首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil ,Runtime则会发出 -doesNotRecognizeSelector: 消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation 对象并发送 -forwardInvocation:消息给目标对象。

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    return YES;//返回YES,进入下一步转发

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

    return nil;//返回nil,进入下一步转发

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {

        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation

    }


    return [super methodSignatureForSelector:aSelector];

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

    SEL sel = anInvocation.selector;

    Person *p = [Person new];

    if([p respondsToSelector:sel]) {

        [anInvocation invokeWithTarget:p];

    }

    else {

        [self doesNotRecognizeSelector:sel];

    }

}

Runtime应用:

$\color{red}{关联对象(Objective-C Associated Objects)给分类增加属性}$

//关联对象

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)

本质原理:

关联对象由AssociationsManage管理并在AssociationsHashMap中存储,所有对象的关联内容都存在一个全局的容器中。

方法魔法(Method Swizzling)方法添加和替换和KVO实现

@implementation ViewController

+ (void)load {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        Class class = [self class];

        SEL originalSelector = @selector(viewDidLoad);

        SEL swizzledSelector = @selector(jkviewDidLoad);


        Method originalMethod = class_getInstanceMethod(class,originalSelector);

        Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);


        //judge the method named  swizzledMethod is already existed.

        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

        // if swizzledMethod is already existed.

        if (didAddMethod) {

            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));

        }

        else {

            method_exchangeImplementations(originalMethod, swizzledMethod);

        }

    });

}

- (void)jkviewDidLoad {

    NSLog(@"替换的方法");


    [self jkviewDidLoad];

}

- (void)viewDidLoad {

    NSLog(@"自带的方法");


    [super viewDidLoad];

}

@end

消息转发(热更新)解决Bug(JSPatch)

JSPatch 是一个 iOS 动态更新框架,只需在项目中引入极小的引擎,就可以使用 JavaScript 调用任何 Objective-C 原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug。

关于消息转发,前面已经讲到过了,消息转发分为三级,我们可以在每级实现替换功能,实现消息转发,从而不会造成崩溃。JSPatch不仅能够实现消息转发,还可以实现方法添加、替换能一系列功能。

实现NSCoding的自动归档和自动解档

- (id)initWithCoder:(NSCoder *)aDecoder {

    if (self = [super init]) {

        unsigned int outCount;

        Ivar * ivars = class_copyIvarList([self class], &outCount);

        for (int i = 0; i < outCount; i ++) {

            Ivar ivar = ivars[i];

            NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];

            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];

        }

    }

    return self;

}

- (void)encodeWithCoder:(NSCoder *)aCoder {

    unsigned int outCount;

    Ivar * ivars = class_copyIvarList([self class], &outCount);

    for (int i = 0; i < outCount; i ++) {

        Ivar ivar = ivars[i];

        NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];

        [aCoder encodeObject:[self valueForKey:key] forKey:key];

    }

}

实现字典和模型的自动转换(MJExtension)

objc_property_t * properties = class_copyPropertyList([self class], &outCount);

        for (int i = 0; i < outCount; i ++) {

            objc_property_t property = properties[i];

            //通过property_getName函数获得属性的名字

            NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];

            [keys addObject:propertyName];

            //通过property_getAttributes函数可以获得属性的名字和@encode编码

            NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];

            [attributes addObject:propertyAttribute];

        }


面试题:

1. class_copyIvarList & class_copyPropertyList区别?

交互两个方法的现实有什么风险?

第一种实现:

void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector){

  Method originalMethod = class_getInstanceMethod(cls, origSelector);

  Method swizzledMethod = class_getInstanceMethod(cls, newSelector);

  IMP previousIMP = class_replaceMethod(cls, origSelector, method_getImplementation(swizzledMethod),

                                                method_getTypeEncoding(swizzledMethod));

  class_replaceMethod(cls, newSelector, previousIMP,method_getTypeEncoding(originalMethod));

}

第一种方法,用class_replaceMethod()实现的。由于A中不存在hookPrint方法,class_replaceMethod会调用class_addMethod方法,而class_addMethod会把Base的hookPrint实现添加到当前类,print的实现最终会和hookPrint的实现交换。B中俩方法都不存在,也会添加俩方法,最终交换俩方法的实现。最终打印的时候,会调用Base的hookPrint方法。

如果调用[a hookPrint:@"hello_k"];那么最终实现的应该是A类中print的实现。结果是:A obj print say:hello_k。

第二种实现:

void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector){

  Method originalMethod = class_getInstanceMethod(cls, origSelector);

  Method swizzledMethod = class_getInstanceMethod(cls, newSelector);

  method_exchangeImplementations(originalMethod, swizzledMethod);

}

第二种方法,用method_exchangeImplementations实现。由于A没有实现hookPrint方法,在调用

[A twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)]的时候,将A的print实现与Base的hookPrint实现交换了。

接着调用 [B twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)]的时候,由于B类啥都没实现,它只能将Base的print实现与base的hookPrint交换了。最终,调用[b print:@"hello2"]的时候,调用的是代码中A类的print方法的实现。

俩中方法都用到了class_getInstanceMethod(cls, selector)方法,这个方法有个特点:如果这个类中没有实现selector这个方法,它返回的是它某父类的 Method 对象(沿着继承链找到为止)。

当我们写的类没有继承的关系的时候,俩种方法都没什么问题。当有继承关系又不确定方法实现没实现,最好用class_replaceMethod方法。当啥都不确定的时候,老老实实地用class_replaceMethod 吧,安全无痛苦

2. Aspects实现的核心原理是什么?

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector

                          withOptions:(AspectOptions)options

                            usingBlock:(id)block

                                error:(NSError **)error;、

- (id<AspectToken>)aspect_hookSelector:(SEL)selector

                          withOptions:(AspectOptions)options

                            usingBlock:(id)block

                                error:(NSError **)error;

Aspects就是利用了消息转发机制,通过hook第三层的转发方法forwardInvocation:,然后根据切面的时机来动态调用block。接下来详细分析巧妙的设计

Aspects或者Runtime方法交换使用前提:

把"retain", "release", "autorelease", "forwardInvocation:这几个加入集合中,判断集合中是否包含传入的selector,如果包含返回NO,这也说明Aspects不能对这几个函数进行hook操作;

判断selector是不是dealloc方法,如果是切面时机必须是AspectPositionBefore,要不然就会报错并返回NO,dealloc之后对象就销毁,所以切片时机只能是在原方法调用之前调用

判断类和实例对象是否可以响应传入的selector,不能就返回NO

判断是不是元类,如果是元类,判断方法有没有被hook过,如果没有就保存数据,一个方法在一个类的层级里面只能hook一次

2.上报机制

在收集到埋点信息之后,会有一个上报到服务器进行统计分析的机制,比如实时发送、启动时发送、最小间隔时间发送等。服务器在接收到这些数据信息之后,按自己整理的计算统计规则得出最后的数据报表,提供给相关人员对项目进行分析使用。

面试题:

1. 动态绑定?

2.什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?

3._objc_msgForward函数是做什么的,直接调用它将会发生什么?

_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

在“消息传递”过程中,objc_msgSend的动作比较清晰:首先在类中的缓存查找IMP(没缓存则初始化缓存),如果没找到,则向父类的类查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替IMP。最后,执行这个IMP。

直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序崩溃,但是如果用得好,能做很多非常酷的事。

一旦调用_objc_msgForward,将跳过查找IMP的过程,直接触发“消息转发”,

如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:

“我没有在这个对象里找到这个方法的实现”

有哪些场景需要直接调用_objc_msgForward?最常见的场景是:你想获取某方法所对应的NSInvocation对象。表示说明:

JSPatch就是直接调用_objc_msgForward来实现其核心功能的:

JSPatch以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具有热更新的能力。

4. objc中向一个nil对象发送消息将会发生什么?

先说结论:OC中向nil发消息,程序是不会崩溃的

因为OC的函数都是通过objc_msgSend进行消息发送来实现的,相对于C和C++来说,对于空指针的操作会引起crash问题,而objc_msgSend会通过判断self来决定是否发送消息,如果self为nil,那么selector也会为空,直接返回,不会出现问题。视方法返回值,向nil发消息可能会返回nil(返回值为对象),0(返回值为一些基础数据)或0X0(返回值为id)等。但对于[NSNull null]对象发送消息时,是会crash的,因为NSNull类只有一个null方法

5. objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?

在OC中,给对象发送消息的语法是:id returnValue = [someObject messageName:parameter];

编译器看到此消息后,将其转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数叫做objc_msgSend,它的原型如下

void objc_msgSend(id self, SEL cmd, ...)

因此,上述以OC形式展现出来的函数就会转化成如下函数:

id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);

这个函数会在接收者所属的类中搜寻其“方法列表”,如果能找到与选择子名称相符的方法,就去实现代码,如果找不到就沿着继承体系继续向上查找。如果找到了就执行,如果最终还是找不到,就执行消息转发操作。

6. 什么时候会报unrecognized selector的异常?

当调用该对象上某个方法,而该对象上没有实现该方法的时候,可以通过“消息转发”进行解决。

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中查找方法运行,如果,在最上方的父类中依然找到相应的方法时,程序在运行时会挂掉并引发异常,但无法识别的选择器已发送到XXX。但是在这之前,objc的运行时会通过三次拯救程序崩溃的机会:

1. 方法解析

objc时运行会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则,运行时就会移到下一步,消息转发

2. 快速转发

如果目标对象实现了-forwardingTargetForSelector:,运行时这时就会调用这个方法,给你把这个消息转发给其他对象的机会。只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。

3.正常转发

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,运行时就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

7. 一个objc对象的isa的指针指向什么?有什么作用?

对象的isa指针指向所属的类

类的isa指针指向了所属的元类

元类的isa指向了根元类,根元类指向了自己。

isa帮助一个对象找到它的方法。isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

8. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

1.不能向编译后得到的类增加实例变量

2.能向运行时创建的类中添加实例变量

解释:

1.编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量

2.运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.

9.runtime如何实现weak变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

10.给类添加一个属性后,在类结构体里哪些元素会发生变化?

11.运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?

12.有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)

13.runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)?

每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,实际上选择器实质上就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现。

参考NSObject上面的方法:

-(IMP)methodForSelector :( SEL)aSelector;+(IMP)instanceMethodForSelector :( SEL)aSelector;

14.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要

15. 一个对象的isa指针指向什么?有什么作用?

16.objc_msgForward 函数是做什么的,直接调用会怎么样?

17.class_copyIvarList & class_copyPropertyList区别?

18.交互两个方法的现实有什么风险?

19.Aspects实现的核心原理是什么?哪些方法不能被hook

(1)不允许hookretain、release、autorelease、forwardInvocation:

(2)允许hookdealloc,但是只能在dealloc执行前,这都是为了程序的安全性设置的

(3)检查这个方法是否存在,不存在则不能hook

(4)Aspects对于hook的生效作用域做了区分:所有实例对象&某个具体实例对象。对于所有实例对象在整个继承链中,同一个方法只能被hook一次,这么做的目的是为了规避循环调用的问题

15.在运行时创建类的方法objc_allocateClassPair的方法名尾部为什么是pair(成对的意思)?

20. 消息转发三种方法的防护优劣?

https://www.jianshu.com/p/9fac4ed527e3

可以利用消息转发机制来做文章。那么问题来了,在这三个步骤里面,选择哪一步去改造比较合适呢。

这里我们选择了第二步forwardingTargetForSelector来做文章。原因如下:

resolveInstanceMethod 需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余的

forwardInvocation可以通过NSInvocation的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation对象,并且forwardInvocation的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写

forwardingTargetForSelector可以将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写

选择了forwardingTargetForSelector之后,可以将NSObject的该方法重写,做以下几步的处理:

1.动态创建一个桩类

2.动态为桩类添加对应的Selector,用一个通用的返回0的函数来实现该SEL的IMP

3.将消息直接转发到这个桩类对象上。

注意如果对象的类本事如果重写了forwardInvocation方法的话,就不应该对forwardingTargetForSelector进行重写了,否则会影响到该类型的对象原本的消息转发流程。

通过重写NSObject的forwardingTargetForSelector方法,我们就可以将无法识别的方法进行拦截并且将消息转发到安全的桩类对象中,从而可以使app继续正常运行。

21. 无埋点实现的原理?

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