iOS面试总结-进阶

[toc]
主要是一些视频笔记和面试时候常问到的问题记录。(持续更新)

Runtime

什么是 Runtime?它的作用是什么?

Runtime 是 Objective-C 的运行时系统,它包含一系列的 API,允许在运行时创建类、调用方法、访问属性等。其作用是实现动态消息传递和运行时类型识别。

消息传递、消息转发

消息传递

objc_msgSend(obj, SEL/@selector(aMethod)/);
1、从消息缓存列表里面通过哈希表查找对应的方法(哈希冲突怎么办?应该是通过再哈希的方式解决的)
2、在当前类方法列表中查找
对于已排序好的列表,采用二分查找算法查找方法对应执行函数
对于没有排序的列表,采用一般遍历查找方法对应执行函数
类方法是没有排序的,所以是使用遍历查找方法
3、从父类逐级查找
判断父类是否是nil
有父类
在缓存中查找
缓存中无,则从父类方法列表中查找,直至父类为nil,进入消息转发流程

消息转发

当一个对象接收到无法解读的消息时,Runtime 会调用消息转发机制。这包括三个步骤:动态方法解析、备用接收者和完整转发。开发者可以通过重载 resolveInstanceMethodforwardInvocation 方法来自定义消息的处理过程。

resolveInstanceMethod:
(resolveClassMethod:)
为类添加一个方法,返回YES

forwardingTargetForSelector:
返回一个其他对象去处理这个消息(备用receiver)

forwardInvocation:
如果上面两种情况没有执行,就会执行通过forwardInvocation进行消息转发

方法替换(Method-Swizzling)

Method Swizzing是发生在运行时的,在运行时将一个方法的实现替换成另一个方法的实现;
每个类都维护着一个方法列表,即methodList,methodList中有不同的方法,每个方法中包含了方法的SEL和IMP,方法交换就是将原本的SEL和IMP对应断开,并将SEL和新的IMP生成对应关系;

RunLoop

什么是RunLoop?

RunLoop是通过内部维护的事件循环消息/事件进行管理对象
**事件循环(Event Loop)
没有消息需要处理的时候,休眠以避免资源占用;【用户态】->【内核态】
有消息需要处理的时候,立刻被唤醒【内核态】->【用户态】

RunLoop的数据结构

Runloop和线程是一一对应的

image.png

主线程的runloop自动启动,而子线程的runloop需要手动启动

Timer与RunLoop的面试题

问题:定时器有个RunLoop mode,默认是在defaultMode,scrollView滚动的时候,主线程的RunLoop会转到UITrackingRunLoopMode,这时候定时器就会失效
解决:将定时器添加到CommonMode上
思考:为什么?
NSRunloopCommonModes

  • CommonMode不是实际存在的一种Mode
  • 是同步source/Timer/Observer到多个mode的一种技术解决方案

Block

这篇文章讲的挺透彻
iOS-Block本质

什么是Block?

  • Block是将函数执行上下文封装起来的对象

block的几种形式?

堆block(__NSMallocBlock__)
栈block(__NSStackBlock__),使用外部变量并且未进行copy操作的block是栈block
全局block(__NSGlobalBlock__)不使用外部变量的block是全局block

block变量截获?

  • 局部变量(截获其值)
    基本数据类型
    对象类型(连同所有权修饰符一同截获)
  • 静态局部变量(指针形式截获)
  • 全局变量(不截获)
  • 静态全局变量(不截获)

一般block会在栈区,经过copy之后,会拷贝到堆区,栈区的block的__forwarding指针指向拷贝后的堆区的block,而堆区的__forwarding指针会指向自己

为什么要用__block修饰局部变量?
__block修饰之后的局部变量实际变成了一个结构体,它内部有一个isa指针,这个结构体会被block捕获,成为其成员变量;block内部修改的时候,实际是通过这个结构体的isa指针去修改所修饰的局部变量的值的

弱引用管理

如何添加一个weak变量到弱引用表

一个被声明为__weak的对象指针,经过编译器编译之后,调用objc_initweak(),经过一些列的函数调用(storeWeak()),最终在weak_register_no_lock()函数中进行弱引用变量的添加;具体添加的位置是通过哈希算法进行位置查找,如果说查找对应位置当中已经有当前对象对应的弱引用数组,那么就把新的弱引用变量添加到这个数组当中,如果没有,重新创建一个弱引用数组,然后第0个位置添加上最新的weak指针,后面的都初始化为0或者nil。

weak如何置nil

当一个对象被dealloc之后,在dealloc的内部实现当中,会调用弱引用清除的相关函数weak_clear_no_lock(),在这个函数内部实现当中会根据 当前对象指针 查找弱引用表,把当前对象相对应的弱引用(数组)都拿出来,遍历数组当中所有的弱引用指针,置为nil。

weak自动置nil的原理(简书1,做参考)

runtime维护着一个weak表即hash表,用于存储指向对象的weak指针
Weak表是Hash表,Key是所指对象的地址,Value是Weak指针地址的数组
以对象的地址作为key,去找weak指针
触发调用arr_clear_deallocating 函数 ,根据对象的地址将所有weak指针地址的数组,遍历数组把其中的数据置为nil。

weak自动置nil的原理(简书2,做参考)

一 、实现
runtime在注册类时,会布局一个weak表(hash表),key是所指对象的地址,value是weak指针的地址的数组;当对象释放时,层层调用后,通过arr_clear_deallocating释放;

二、weak实现原理步骤:通过clang可以分析源码;

objc_initWeak//初始化weak;

objc_storeWeak()//修更新指针指向,创建对应的弱引用表;

clearDeallocating//通过key找到weak数组,然后对数组里的weak指针置nil,把这个entry(入口,记录)从weak表删除;

自动释放池问题

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableArray *array = [NSMutableArray array];
    NSLog(@"%@",array);
}

Q: array在什么时候释放?
A: 在当前RunLoop将要结束的时候调用AutoreleasePoolPage::pop()来对其进行释放。
(实际上在每一次的RunLoop循环当中都会在将要结束的时候对前一次创建的AutoreleasePool进行pop操作,同时会push进来一个新的AutoreleasePool)
问题拓展:
要回答这个问题需要知道RunLoop和AutoReleasePool的关系。
Runloop每次循环都是被一个AutoReleasePool包围着的,具体说每次Runloop循环将要结束的时候会释放当前runloop的内存占用。再创建好一个AutoReleasePool给下一次Runloop循环使用。(慕课网6-7

ViewDidLoad是在主线程执行,在该方法中创建的array会加入到当次RunLoop的AutoReleasePool中,array会在当前RunLoop将要结束的时候得到内存释放。

一般错误的回答都是viewDidLoad方法结束就释放了。

AutoreleasePool原理?

数据结构:是以为节点,通过双向链表的形式组合而成。和线程是一一对应的。
objc_autoreleasePoolPush()
objc_autoreleasePoolPop()
objc_autorelease()

AutoreleasePool为什么可以嵌套调用?

A:多层嵌套就是多次插入哨兵对象

AutoreleasePool使用场景?

在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool

组件化

组件化的好处?

  • 业务分层、解耦,使代码变得可维护;
  • 有效的拆分、组织日益庞大的工程代码,使工程目录变得可维护;
  • 便于各业务功能拆分、抽离,实现真正的功能复用;
  • 业务隔离,跨团队开发代码控制和版本风险控制的实现;
  • 模块化对代码的封装性、合理性都有一定的要求,提升开发同学的设计能力;
  • 在维护好各级组件的情况下,随意组合满足不同客户需求;(只需要将之前的多个业务组件模块在新的主App中进行组装即可快速迭代出下一个全新App)

如何实现解耦?

  • 分层
    基础功能组件:按功能分库,不涉及产品业务需求,跟库Library类似,通过良好的接口供上层业务组件调用;不写入产品定制逻辑,通过扩展接口完成定制;
    (网络组件、弹框组件、工具组件、)
    基础UI组件:各个业务模块依赖使用,但需要保持好定制扩展的设计

    业务组件:业务功能间相对独立,相互间没有Model共享的依赖;业务之间的页面调用只能通过UIBus进行跳转;业务之间的逻辑Action调用只能通过服务提供;

  • 中间件:target-action,url-block,protocol-class

https://www.jianshu.com/p/464a8f1ab949

CTMeditor
  • 通过反射机制利用字符串找到相对应的target然后向它发送消息

AvoidCrash

Foundation框架潜在的崩溃的危险比如:

  • 将 nil 插入可变数组中会导致崩溃。
  • 数组越界会导致崩溃。
  • 根据key给字典某个元素重新赋值时,若key为 nil 会导致崩溃。
  • ......
    利用runtime的特性,使用方法替换,在即将发生崩溃的位置给它替换成默认实现,防止崩溃,同时上报这个错误到bugly
    拦截所有检测崩溃类型

    捕获到异常之后的处理(其实就是获取出现异常的堆栈,最后以通知的形式发送出去)
/**
 *  提示崩溃的信息(控制台输出、通知)
 *
 *  @param exception   捕获到的异常
 *  @param defaultToDo 这个框架里默认的做法
 */
+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo {

    //堆栈数据
    NSArray *callStackSymbolsArr = [NSThread callStackSymbols];
    
    //获取在哪个类的哪个方法中实例化的数组  字符串格式 -[类名 方法名]  或者 +[类名 方法名]
    NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbolsArr];
    
    if (mainCallStackSymbolMsg == nil) {
        
        mainCallStackSymbolMsg = @"崩溃方法定位失败,请您查看函数调用栈来排查错误原因";
        
    }
    
    NSString *errorName = exception.name;
    NSString *errorReason = exception.reason;
    //errorReason 可能为 -[__NSCFConstantString avoidCrashCharacterAtIndex:]: Range or index out of bounds
    //将avoidCrash去掉
    errorReason = [errorReason stringByReplacingOccurrencesOfString:@"avoidCrash" withString:@""];
    
    NSString *errorPlace = [NSString stringWithFormat:@"Error Place:%@",mainCallStackSymbolMsg];
    
    NSString *logErrorMessage = [NSString stringWithFormat:@"\n\n%@\n\n%@\n%@\n%@\n%@",AvoidCrashSeparatorWithFlag, errorName, errorReason, errorPlace, defaultToDo];
    
    logErrorMessage = [NSString stringWithFormat:@"%@\n\n%@\n\n",logErrorMessage,AvoidCrashSeparator];
    AvoidCrashLog(@"%@",logErrorMessage);
    
    
    //请忽略下面的赋值,目的只是为了能顺利上传到cocoapods
    logErrorMessage = logErrorMessage;
    
    NSDictionary *errorInfoDic = @{
                                   key_errorName        : errorName,
                                   key_errorReason      : errorReason,
                                   key_errorPlace       : errorPlace,
                                   key_defaultToDo      : defaultToDo,
                                   key_exception        : exception,
                                   key_callStackSymbols : callStackSymbolsArr
                                   };
    
    //将错误信息放在字典里,用通知的形式发送出去
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic];
    });
}

多线程相关问题

参考:https://www.jianshu.com/p/361e8a0a4e7e

  • iOS中的多线程
    . NSThread
    . GCD
    . NSOperationQueue

NSThread - 轻量级别的多线程技术,需要我们自己管理线程

需要我们手动开辟子线程,如果使用init初始化方式则需要手动启动,如果使用构造器方式初始化则会自动启动。

  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread:) object:@"我是参数"];
    // 当使用初始化方法出来的主线程需要start启动
    [thread start];
    // 可以为开辟的子线程起名字
    thread.name = @"NSThread线程";
    // 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5
    thread.threadPriority = 1;
    // 取消当前已经启动的线程
    [thread cancel];
    // 通过遍历构造器开辟子线程
    [NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"构造器方式"];

performSelector:withObject:afterDelay:会在内部创建一个NSTimer,然后添加到当前的RunLoop中,如果当前线程没有开启RunLoop(子线程默认没有开启RunLoop),该方法会失效

[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];

performSelector:withObject:没有添加timer,所以不需要添加子线程RunLoop也可以执行

GCD对比NSOperationQueue

GCD是面向底层的C语言的API,NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象。

它们的区别
  • GCD执行效率更高,而且由于队列中执行的是由block构成的任务,是一个轻量级的数据结构,写起来更方便
  • GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
  • NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序
  • NSOperationQueue因为面向对象,所以支持KVO,可以检测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
    探讨
    实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的且优化完善、运行快速的GCD是首选 如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持 不论是GCD还是NSOperationQueue,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。而NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销

Q:假设有这么场景:有网络请求A、网络请求B,需要AB执行完之后继续进行下一步操作,怎么使用GCD实现?
A:

  1. 信号量(dispatch_semaphore)
- (void)GCD_Semaphore {
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    NSLog(@"1");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"task1, %@",[NSThread currentThread]);
        sleep(1);
        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    NSLog(@"2");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"task2, %@",[NSThread currentThread]);
        sleep(1);
        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    NSLog(@"3, %@",[NSThread currentThread]);
}

打印结果

2021-01-05 23:15:33 1
2021-01-05 23:15:33 task1, <NSThread: 0x600000eecd00>{number = 3, name = (null)}
2021-01-05 23:15:34 2
2021-01-05 23:15:34 task2, <NSThread: 0x600000eecd00>{number = 3, name = (null)}
2021-01-05 23:15:35 3, <NSThread: 0x600000eb01c0>{number = 1, name = main}

这里的打印结果是1->task1->2->task2->3顺序执行,相当于加锁?

  1. dispatch_group(基于dispatch_semaphore实现的)
- (void)GCD_Group {
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"task1");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"task2");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"notify");
    });
    
    NSLog(@"===");
    
}
  1. dispatch_barrier_async(同时也可以用来实现多读单写、加锁、设置最大线程数)
- (void)GCD_barrier {
    
    dispatch_queue_t queue = dispatch_queue_create("barrier_queue", DISPATCH_QUEUE_CONCURRENT);
    // 注意dispatch_barrier_async只在自己创建的并发队列中才有效,在global_queue,串行队列上效果跟dispatch_(a)sync一样
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"task1");
    });
    dispatch_async(queue, ^{
        NSLog(@"task2");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier");
    });
    NSLog(@"===");
    dispatch_async(queue, ^{
        NSLog(@"task3");
    });
    dispatch_async(queue, ^{
        NSLog(@"task4");
    });
}

多读单写

- (id)dataForKey:(NSString *)key {
    __block id data;
    //同步读取指定数据
    dispatch_sync(self.concurrentQueue, ^{
        data = [self.dict objectForKey:key];
    });
    return data;
}
- (void)setData:(id)data forKey:(NSString *)key {
    // 异步栅栏调用设置数据
    dispatch_barrier_async(self.concurrentQueue, ^{
        [self.dict setObject:data forKey:key];
    });
}

单例模式

这篇文章介绍的还不错
https://www.jianshu.com/p/a92c0283f243

什么是单例模式?

简单来说,一个单例类,在整个程序中只有一个实例,并且提供了类方法供全局调用,在编译时初始化这个类,然后一直保存在内存中,直到App退出时由系统自动释放这一部分内存

系统为我们提供的单例类有哪些?

  • UIApplication(应用程序实例类)
  • NSNotificationCenter(消息中心类)
  • NSFileManager(文件管理类)
  • NSUserDefaults(应用程序设置)
  • NSURLCache(请求缓存类)
  • NSHTTPCookieStorage(应用程序cookies池)

单例的存放位置

全局区

变量的存放位置

位置 存放的变量
临时变量(由编译器管理自动创建/分配/释放的,栈中的内存被调用时处于存储空间中,调用完毕后由系统系统自动释放内存)
通过alloc、calloc、malloc或new申请内存,由开发者手动在调用之后通过free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存,在ARC模式下,由系统自动管理。
全局区域 静态变量(编译时分配,APP结束时由系统释放)
常量 常量(编译时分配,APP结束时由系统释放)
代码区 存放代码

创建一个单例的方式

  • 同步锁:NSLock
  • @synchronized(self) {}
  • 信号量 dispatch_semaphore_t
  • 条件锁 NSConditionLock
  • dispatch_once_t

单例注意事项-保证单例只被初始化一次

  1. 对alloc、new、copy、mutableCopy的处理
    因为alloc] init 和 new都是调用的+ (instancetype)allocWithZone:(struct _NSZone *)zone方法,那么我们可以重写这个方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    NSLog(@"allocWithZone");
    @synchronized (self) {
        if (instance == nil) {
            instance = [super allocWithZone:zone];
            return instance;
        }
    }
    return nil;// 这里返回nil,那么后面初始化的对象就是nil了,返回instance的话其实就是同一个单例对象了
}
  1. 直接禁用对应的方法
+(instancetype) new __attribute__((unavailable("OneTimeClass类只能初始化一次")));
-(instancetype) copy __attribute__((unavailable("OneTimeClass类只能初始化一次")));
-(instancetype) mutableCopy  __attribute__((unavailable("OneTimeClass类只能初始化一次")));

(用NS_UNAVAILABLE也可以,但是这个就没有提示了)

还要了解一下FMDB

NSMutableArray数据结构分析

普通C数组是是一段能被方便读写的连续内存空间,使用一段线性内存空间的一个最明显的缺点是,在下标0插入一个元素时,需要移动其它元素,即memmove的原理:
https://blog.csdn.net/qq_27909209/article/details/82689322

image.png

移除元素时同理也要移动其它元素;
当数组非常大的时候可能就会出现问题。
NSMutableArray是一个类簇,[NSMutableArray new]实际返回的是__NSArrayM

(lldb) po [[ NSMutableArray new] class]
__NSArrayM

__NSArrayM使用了环形缓冲区 (circular buffer),这个数据结构相当简单,只是比常规数组或缓冲区复杂点。环形缓冲区的内容能在到达任意一端时绕向另一端。
环形缓冲区有一些非常酷的属性。尤其是,除非缓冲区满了,否则在任意一端插入或删除均不会要求移动任何内存。我们来分析这个类如何充分利用环形缓冲区来使得自身比 C 数组强大得多。我们在这里知道了几个有趣的东西:在删除的时候不会清除指针。最有意思的一点,如果我们在中间进行插入或者删除,只会移动最少的一边的元素。

NSDictionary数据结构

在内部,字典使用哈希表来组织其存储,并在给定相应键的情况下快速访问值

Crash类型

  • Signal
  • NSException
    bugly需要使用符号表解析应该是用了捕捉了Signal异常,Signal异常是需要配合符号表才能解析的,NSException的话可以直接拿到崩溃信息

关于RunLoop防止崩溃

https://cloud.tencent.com/developer/article/1192474
这还有一篇文章可以参考(关于Crash收集)·
http://www.cocoachina.com/articles/12301

图像显示原理

CPU生成位图(bitmap)经由总线在合适的时机传给GPU;GPU拿到位图之后会做相应位图的渲染,包括纹理的合成,之后把结果放到帧缓冲区(Frame Buffer),由视频控制器,根据VSync信号在指定时间之前去提取帧缓冲区当中的内容,最终显示到手机屏幕上。

image.png

如何定位内存泄漏?

  • 静态分析 cmd+shift+B
    会报Warning,定位到对应位置修改即可


    Warning
  • Instruments Leak(cmd+i)
    首先需要对工程进行设置
    Build Settings - Debug Infomation Format 设置成DWARF with dSYM File
    其次需要在真机上运行
    这样子才能定位到Xcode代码具体位置
    具体操作这里就不记录了

冷启动

pre-main

1、减少动态库、合并一些动态库(定期清理不必要的动态库)
2、减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)
3、减少C++虚函数数量
4、Swift尽量使用struct
5、用+initialize方法和dispatch_once取代所有的attribute((constructor))、>C++静态构造器、Objc的+load

main

1、在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在didFinishLaunching方法中
2、监控、埋点、基础功能设置 在willFinishLaunching
3、定位、网络配置、基础SDK 、必须的数据 在 didFinishLaunching

首页渲染

1、避免使用xib
2、首页一般关联业务较多,优先请求和渲染用户可见的页面
3、业务组件,业务相关配置等,在首页渲染完成之后

内存管理方案

NONPOINTER_ISA
散列表
TaggedPointer

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

推荐阅读更多精彩内容

  • 内存管理 1.什么情况使用weak关键字,相比assign有什么不同? 什么情况使用 weak 关键字?在 ARC...
    刺骨寒阅读 662评论 0 1
  • 原文地址:https://blog.csdn.net/wzc10101415/article/details/51...
    sunney0阅读 117评论 0 1
  • 关于面试题 打个比方,如果把找工作理解成考大学,面试就是高考,市面上的“真题”就是模拟试卷。我们会很容易倾向于在面...
    乐逍遥11阅读 466评论 0 1
  • 推荐指数: 6.0 书籍主旨关键词:特权、焦点、注意力、语言联想、情景联想 观点: 1.统计学现在叫数据分析,社会...
    Jenaral阅读 5,700评论 0 5
  • 城空了,有树长出来 我的城死了 铸起它的人,杀死它的人 不愿因为这件事而骄傲 一座城的终结 永远因为终结这件事而显...
    于十六阅读 2,847评论 6 17