iOS 面试题及答案20道41~60(三)

续写iOS 面试题及答案20道21~40(二)

41.谈谈对OC和Swift动态特性的理解

runtime其实就是OC的动态机制。runtime执行的是编译后的代码,这时它可以动态加载对象、添加方法、修改属性、传递信息等。具体过程是,在OC中,对像调用方法时,如[self.tableview reload],经历了两个过程。

  1. 编译阶段: 编译器会将OC代码翻译成objc_msgSend(self.tableview,@slector(reload)),把消息发送给self.tableview。
  2. 运行阶段: 接收者(self.tableview)会响应这个消息,其间可能会直接执行,转发消息,也可能会找不到方法导致程序崩溃。

所以,整个流程是:编译器翻译->给接收者发送消息->接收者响应消息。

接收者如何去响应消息,就发生在运行时。runtime执行的是编译后的代码,而代码的具体实现是运行时才能够确定,这时它就可以动态加载对象、添加方法、修改属性、传递消息等。runtime的运行机制就是OC的动态特性。

Swift公认的是一个静态语言,它的动态特性都是通过桥接OC来实现的,像一些动态的特性其实是可以用Swift的面向协议的编程那样使用的。

例子:
在OC中我们去动态的调用一个方法,首先需要判断这个方法是否存在然后再去调用它,这就是纯正的动态特性:代码

OC的动态特性

UserModel类的实现
@implementation UserModel
- (void)todoSomething{
    NSLog(@"正在做什么事情。");
}
@end

动态调用UserModel的方法。
UserModel *user = [UserModel new];
if([user respondsToSelector:NSSelectorFromString(@"todoSomething")]){
    [user performSelector:NSSelectorFromString(@"todoSomething")];
}

Swift的面向协议编程

面向协议的实现代码
protocol TodoProtocol {
    func todosomething()
}

class UserModel: TodoProtocol {
    func todosomething() {
        print("要做的事情")
    }
}

调用代码
let user:UserModel = UserModel()
if let user1 = user as? TodoProtocol {
    user1.todosomething()
    print("实现了这个协议")
}else{
    print("没有实现这个协议")
}
这样来使用,这个协议的方法,其实这步骤判断不需要因为swift中的协议的方法都是required的没有optional。

42.谈谈as、as!、as? 含义是什么?

1)as的三种使用方式

  • as第一种用法是将派生类转换成基类,也就是说子类直接转换成父类。 向上转型。

代码:

class Animal{
    var name:String
    init(_ name:String){
        self.name = name
    }
}
class catanimal : Animal {
}
class doganimal: Animal {
}
//as 向上强转 子转父
let cat = catanimal("汤姆") as Animal
let dog = doganimal("泰克") as Animal

showAnimalName(cat)
showAnimalName(dog)

如果是父类转换成子类,那么这种形式在Swift是会报错的,就算是加上!强转a!运行的时候也会崩溃。

  • as第二种用法消除多义性,数据类型转换

比如说

let AnimalAge = 3 as Int  //3 有可能是Int 也有可能是CGFloat 也可能是long Int
let AnimalWeight = 20 as CGFloat
let AnimalHeight = (50 / 2) as Double
  • as的第三种用法switch语句中进行模式匹配,通过switch愈发检测对象的类型,根据对象类型进行处理。
switch animalType{
    case let animal as catanimal:
        print("是catanimal类型")
    case let animal as doganimal:
        print("是doganimal类型")
    default: break
}

2)as!的用法

向下转型,强制转换类型,有个缺点就是如果转化失败就会报错误。
一般形式下可以这么使用,

catanimal是Animal的子类。

let animal:Animal = catanimal("汤姆")
let cat = animal as!catanimal

上述代码就是将类型强制转化,首先要注意一点,在强转之前一定要判断animal这个类是用catanimal这个类初始化的, 如果不这样做的话,是用Animal初始化的,那么在第二行强制转化的话就会报错。

3)as?的用法

as?跟as!是一样的用法,只不过as?如果转化不成功的话会直接返回一个nil对象,成功的话返回可选类型。如果说能%100的会成功转化那么请用as!如果说不能的话请用as?

使用例子:

let animal:Animal = catanimal("汤姆")
if let cat = animal as? catanimal {
    print("是catanimal类型")
}else{
    print("nil")
}

43.查看下述代码输出是什么?为什么?

protocol  Police{
    func HandlingCases()
}

extension Police{
    func HandlingCases(){
        print("Police doing Handling cases!" );
    }
}

struct Judge:Police{
    func HandlingCases(){
        print("Judge doing Handling cases!" );
    }
}

调用代码
let police1:Police = Judge()
let police2:Judge = Judge()
    
police1.HandlingCases()
police2.HandlingCases()

两个输出的内容都是Judge doing Handling cases!,
因为在Swift中protocol声明了某个方法,在没有extension扩展协议的情况下必须在实现类中实现该方法,swift中的协议的方法都是required的,如果extension中实现了该方法,则在实现类Judge中可以不用去实现,一旦实现,那么还是以实现类Judge的实现方法为准。
HandlingCases方法在Police协议中声明了,police1虽然声明的是Police但是实际实现还是Judge所以根据实际情况是调用了JudgeHandlingCases实现方法。同样道理police2也是如此。

但是如果说在Police中没有生命方法HandlingCases,其他不变的情况下,那么police1police2的输出就会不一样了。因为police1的实际类型是PolicePolice中并没有声明HandlingCases,但是在类扩展中有实现该方法,实际类调用实际的方法,那么就会调用扩展类中实现HandlingCases,而police2的实际类型是Judge那肯定就会调用Judge中的实现方法。

所以他们的输出是:

police1输出 Police doing Handling cases!

police2输出 Judge doing Handling cases!

44.message send如果找不到对象,则会如何进行后续处理

这种形式一般会有两种情况:1)对象是nil; 2)对象不为nil但是就是找不到对应的方法;

1)对象为空的时候,在OC中向一个nil的对象发送消息是可以的,如果这个方法的返回值是对象那么返回的是nil,如果返回值是结构体,那么就是0.
2)对象不为空,找不到方法,就会崩溃,报错。

45.method swizzling 是什么?

每一个类都会维护一个方法列表,并且方法名跟方法实现是一一对应的,也就是SEL(方法名)和IMP(方法实现的指针)的对应关系,

method swizzling的意义就是运用runtime的特性跟方法来进行SEL和IMP这一对的IMP进行更换操作。如果SELa对应IMPa SELb对应IMPb 使用method swizzling后可以成为SELa对应IMPb SELb对应IMPa。

代码实现

在本类实现2个方法:
- (void)onefunc{
    
    NSLog(@"one");
    
}

- (void)twofunc{
    
    NSLog(@"two");
    
}

在本类中调用代码
SEL one = @selector(onefunc);
    
Method OneMethod = class_getInstanceMethod([self class], one);
    
SEL two = @selector(twofunc);
    
Method TwoMethod = class_getInstanceMethod([self class], two);
    
method_exchangeImplementations(OneMethod, TwoMethod);

这样就可以看到调用onefunc会打印two, 调用twofunc会打印one

46.Swift和OCjective-C的自省(Introspection)有什么不同

自省在Objective-C中就是:判断一个对象是否属于某个类的操作。它有两种形式。
[objc isKindOfClass:[SomeClass class]];
[objc isMemberOfClass:[SomeClass class]];

第一个判断isKindOfClass是判断objc是否为SomeClass或者其子类的实例对象。
第二个判断isMemberOfClass是判断objc仅仅是SomeClass这个类(非子类,当前类)的实例对象,并且不能检测任何类都是基于NSObject类。

这两种判断的前提是objc必须是NSObject的子类。

Swift中只有isKindOfClass类似的方法is,很多的类并不是继承自NSObject,不过比OC的功能更加强大,is可以判断enum、struct类型

自省操作一般和动态类型一起出现,比如说OC中的id类型,以及Swift中的可选类型、anyobject。

cat是animal的子类
id animal = catInstance;

if([animal isKindOfClass:[animal class]]){
    NSLog(@"是 animal class");
    if(animal isMemberOfClass:[cat class]){
        NSLog(@"是 cat class");
    }
}else if([animal isKindOfClass:[any(其他类) class]]){
    NSLog(@"是 其他 class");
}

47.能否通过Category给已有的类添加属性(property)

无论是Swift还是OC都可以用Category来添加属性,只不过添加的方式不一样。

Objective-C:
OC中通过Category中直接添加属性(property)会报错,提示找不到getter和setter方法,那是因为在Category中不会自动生成这两个方法,解决的方法就是运用runtime,关联对象的形式来添加属性,主要涉及到的两个函数是,objc_getAssocicatedObject和objc_setAssociatedObject.
objc_getAssocicatedObject两个参数: 本类实例对象、 关联属性的key
objc_setAssociatedObject中的方法有4个参数,分别是 本类实例对象、关联属性的key、新值、关联策略。

@interface UserModel : NSObject
@end
@implementation UserModel
@end


@interface UserModel(en)
@end

#import "UserModel+En.h"
#import <objc/runtime.h>

static void *EnNameKey = &EnNameKey;

@implementation UserModel(en)

-(void)setEnName:(NSString *)EnName{
    objc_setAssociatedObject(self, &EnNameKey, EnName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)EnName{
    return objc_getAssociatedObject(self, &EnNameKey);
}

@end

Swift跟Objective-C的使用方式差不多,如下代码

class UserModel {
    var Name:String = "小明"
}
var EnNameKey:Void?
extension UserModel{
    
    var EnName:String? {
        
        get{
            return objc_getAssociatedObject(self, &EnNameKey) as? String
        }
    
        set{
            objc_setAssociatedObject(self, &EnNameKey, "你的英文名字", objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        
    }
}

48.谈谈看对Xcode你有多少理解。

iOS开的IDE是Xcode,它是apple开发的主流工具,目前Xcode已经更新到了9个版本。功能蕴含了开发、测试、性能分析、文档查询、源代码管理等多方面功能。

C、C++与Objective-C密不可分,自动化用Ruby,熟悉的工具:fastlane、cocoapods、Automation工具的脚本大多是用javaScript,刚发布的coreML采用的模型工具则是用Python开发的。最新的Xcode采用完全由Swift重写的Source Editor,在代码修改、补全、模拟器运行方面有了很大的提升,目前,Xcode最大的缺点是稳定性还不够。

Xcode工具想用的熟练,则必须从Intruments性能分析和LLDB调试,一步一步进行由浅入深、Swift最新的Playground也是一个不错的工具。

49.LLDB中的p和po的区别

  • p是exor的缩写。它的工作是把接受到的参数在当前环境下运行编译,人后打印出对应的值。
  • Po即expr-o-。操作跟p相同,如果接收的是一个指针,那么它会调用对象的description方法并打印;如果接收到的参数是一个core foundation对象,那么它会调用CFShow方法并打印,如果两个方法都调用失败,那么po打印出和p相同的内容。
  • Po相对于p来说可以多打印一些内容,一般用p即可,毕竟打印的东西越少越快,效率越高。如果需要查看详情就用Po。

50.description方法是干什么用的?

这个是iOS对象默认的一个方法,它的输出格式一般为<类名: 对象的内存地址>
也可以自定义这个输出格式,NSObject类所包含。

51.Xcode中的Buildtime issues和Runtime issues指的是什么?

Buildtime issues一般分为三类:编译器识别出的警告(Warning)、错误(Error)和静态分析(Static Code Analysis)。前两者一般经常会遇到,不用多说,静态分析也可以分为三种:1)未初始化变量,未使用数据和API使用错误。

Swift代码:

class CusViewController: UIViewController {
    override func viewDidLoad() {
        let PeopleList:[UserModel]
        let somePeopleList = PeopleList
        let otherPeopleList:[UserModel]?
    }
}

分析代码:
PeopleList并没有初始化就去赋值给somePeopleList所以是未初始化报错。
otherPeopleList并没有使用,那么就会出现未使用的数据,在viewDidLoad中没有使用super.viewDidLoad()那么就是Api调用错误。

Runtime issues也有三类错误:线程问题、UI布局和渲染问题、内存问题。线程的相关问题最多,最常见的就是数据的竞争。

var num = 0
let addnum1 = 10,addnum2 = 100
    
DispatchQueue.global().async {
    for _ in 0...10{
        num += addnum1
    }
}
    
for _ in 0...10 {
    num += addnum2
}

两个线程都对num进行写操作,这样的话,谁先操作,那么值就会根据方法+几 因为数据直接就存在了争抢关系,当然最终结果是一样的,但是中间的先后顺序就会打乱了。

UI布局和渲染上面的时候尺寸设定、布局没有给全,渲染设定模糊,因而造成的autolayout无法渲染。
内存上的问题就是内存泄漏,比如循环引用等。

52.App启动时间过长,该怎么去优化?

导致App启动时间过长的原因有多种,从理论上面来讲有两种情况:1)main函数加在之前;2)main还是加载之后。

main 函数加载之前,如果想要分析这块儿的代码,需要去Xcode中添加DYLD_PRINT_STATISTICS环境变量,并将其值设置为1,这样就可以得到如下的启动日志:
还有很多的静态变量,如果想查看的话,在终端man dyld会打印出你要的静态变量的列表,可以一个一个的去打印看看。
例子:

Total pre-main time: 339.26 milliseconds (100.0%) //总的时间  毫秒
         dylib loading time: 154.24 milliseconds (45.4%) //库加载
        rebase/binding time:  78.42 milliseconds (23.1%) //重定向/绑定
            ObjC setup time:  69.27 milliseconds (20.4%) //对象设置
           initializer time:  37.18 milliseconds (10.9%) //初始化
           slowest intializers :
             libSystem.B.dylib :   8.49 milliseconds (2.5%) //系统库
    libMainThreadChecker.dylib :  17.09 milliseconds (5.0%) //系统库

从上面打印的内容来看,大致就是上面的四个方面: 动态库加载、重定位绑定对象、设置对象、对象的初始化。
通过上述打印我们可以通过以下方式来优化App的启动时间:

  • 减少动态库的数量,动态库加载时间会减少,apple推荐的动态库数量不超过6个。

  • 减少Objective-C的类数量,例如合并和删除,这样可以加快动态链接,重定位/绑定耗费的时间会相应的减少。

  • 使用initialize方法替代load方法,或尽量将load方法中的代码延后调用。对象的初始化所耗费的时间会相应减少。

在main之后的app启动时间主要是要优化第一个界面的渲染速度,主要是看进入Viewdidload viewwillAppear这两个方法是否有其他操作。

53.如何检测代码中的循环引用?

目前所了解的有两种方式:

1) 使用Xcode中的Memory Debug Graph。在Xcode中运行代码,在有可能循环引用的地方添加断点,然后点击如图所示的按钮就能查看是否循环引用。

上图中的内容,点击了这个按钮以后左边是类,右边是类图,其中谁引用了谁,这里很清楚的可以看到引用示意图。

2) 使用Instruments里面的leaks选项--这是一个专门检测内存泄漏的工具,在进入首页以后,发现leak Checks中出现内存泄漏,就是出现小红点的时候,可以将导航栏中的选项切换到call tree模式下,如果调试自己写的代码的话,建议勾选Display Settings 中勾选 "Separate by Thread"和"Hide System Libraries"两个选项。这样可以隐藏系统和应用程序本身的调用路径,从而更方便地找出retain cycle位置。

54.怎么解决EXC_BAD_ACCESS

产生EXC_BAD_ACCESS的主要原因是访问了某些已经被释放掉的对象,或者访问了它们已经释放的成员变量或方法。解决方法主要有下面几种:

  • 设置全局断点,快速定位缺陷所在:这种方法效果一般。
  • 重写Object和respondsToSelector方法:这个方法效果一般,并且要在每个class上进行定点排查,所以不推荐使用该方法。
  • 使用Zombie和Address Sanitizer:可以在绝大多数情况下定位问题代码,

55.如何在Playground中执行异步操作

阅读下述代码,打印结果是什么?

import Foundation

let urlstr = URL.init(string: "https://api.apiopen.top/getSingleJoke")
let task = URLSession.shared.dataTask(with: urlstr!) { (data, response, error) in
    do {
        
        let dict:NSDictionary =  try JSONSerialization.jsonObject(with: data!, options: []) as! NSDictionary
        print(dict.object(forKey: "message") as! String)
        print(dict)
    } catch {
        print(error)
    }
} //这个是初始化任务,但是并没有执行,下面必须调用恢复,证明这个任务已经开始只不过暂停,字面意思哈。

task.resume()  //恢复任务

答案是:什么都不会打印出来,原因是playground在执行完所有的操作以后会自动退出,要让playground打印出异步执行的内容,需要具备延时运行的特征,所以需要在Playground中添加下述代码:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

56.在Playground中实现一个10行的列表,每行显示一个0~100的整数。

啥也不说了上代码


import UIKit
import PlaygroundSupport

class ViewController :UITableViewController{
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
}

extension ViewController {
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(Int(arc4random_uniform(100)))"
        return cell
    }
    
}

PlaygroundPage.current.liveView = ViewController()

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

//问题延伸
Weak指针不会增加所引用对象的引用计数,并且在引用对象被回收的时候自动置nil,通常解决循环引用的问题,自动置nil的原理是什么?

Runtime维护了一个Weak的表,用于存储指向某个对象的所有Weak指针。Weak表就是一个hash表(哈希表),Key是所指对象地址,Value是Weak指针的地址(这个地址的值是所指对象的地址)的数组。
在对象被回收的时候,经过层层调用,会最终将这个Weak表中所有的Weak指针全部置nil。

源码在Runtime的源码中有个文件objc-weak.mm 其中就有这段代码。

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

根据对象地址,找到weak指针的数组,遍历所有数组的weak指针置nil,最后将这个entry从weak表中删除 weak_entry_remove(weak_table, entry);

58.介绍一下GCD、NSOperation、NSTread分别是是什么?NSOperation和GCD比有哪些优缺点?

GCD是一个基于C语言的多线程编程的解决方式。NSOperation是更加注重面向对象的多线程编程的解决方式。NSTread是一个轻量级的多线程编程方式。

NSOperation更加抽象化,抽象化的方式可以使多线程管理并发,顺序,依赖关系时更加灵活:

  • 性能上:GCD接近底层,基于C语言的代码执行更加高效,理论上速度比NSOperation快
  • 异步队列操作上,管理顺序、依赖关系,这些面向对象的编程会更加方便去实现,而GCD在这种方式上面会更加麻烦,代码量会更多。
  • 日常使用上,如果是简单的异步操作回调的形式建议是GCD,简单方便,如果对一些异步操作管理过程中有更多的线程顺序、依赖关系那么建议用NSOperation

最后扯个犊子, 这玩意就是看你用的哪个熟悉。只要能够快速准确的实现,用什么都是一样的。

59.怎么用Copy关键字

copy一般用在NSString、NSArray、NSDictionary等属性字段的修饰符。

因为这些属性都有与之对应可变的子类。用copy修饰的上述几个类型,在赋值的时候有坑会赋到可变的类型的指针,如果这个可变子类用strong修饰,那么一旦这个可变对象的值被修改了,那么这个对象也就被修改了,所以copy就是为了复制一份不可变的对象付给copy修饰的对象。

    //mutableString这个参数是类A的一个变量类型是NSMutableString
    NSMutableString* mutableString = [[NSMutableString alloc]initWithString:@"mutablestring"];
    
    UserModel *user = [[UserModel alloc]init];
    
    //首先将字符串赋值
    user.title = @"title";
    user.title = mutableString; //这时候title==mutableString==@"mutablestring"
    
    //修改mutableString的值
    [mutableString appendString:@"111"];
    
    //得到的结果是user.title == mutableString == @"mutablestring111"
    NSLog(@"title=%@--%p mutableString=%@--%p",user.title,user.title,mutableString,mutableString);

最终 title在不知不觉中就被修改了。所以用copy,修饰出来的属性都是不可变的。
在setter方法中:

- (void)setStr:(NSString*)str
    if(_Str != str){
        [_Str release];
        _Str = [str copy];
}

60.什么是MVC、MVP、MVVM?MVVM的每一层关系是什么?

MVC 是Model、View、Controller。
View与Controller通信、Model与Controller通信 View与Model严格意义上是完全解耦合的。

  • View与Controller:

    • action:点击、滑动、跳转、刷新UI、网络请求等,用户与View交互触发、控制器方法
    • 委托delegate:View向Controller询问一些自己无法做到的事情,让Controller去解决,比如说获取数据
    • 数据Model:View跟Controller要需要显示的数据,Controller需要访问Model,从Model中获取数据告诉View让其显示
  • Model与Controller

    • 广播(Notification),Controller注册监听Model的数据变化的通知,当Model变化的时候告诉ControllerModel数据更新了
    • KVO(KEY-Value-Observing):Model作为Controller的属性,Controller监听这个属性变化,当Model属性被修改时,这个Controller会接收到通知
分离了视图层和业务层
耦合性低
开发速度快
可维护性高

MVP 是Model-View(Controller)-Presenter

MVP模式也是一种经典的界面模式。MVP中的M代表Model, V是View, P是Presenter。在MVP里,Presenter完全把Model和View进行了分离,主要的程序逻辑在Presenter里实现。而且,Presenter与具体的View是没有直接关联的,而是通过定义好的接口(协议)进行交互,从而使得在变更View时候可以保持Presenter的不变,重用

优点

低耦合
可重用性
独立开发
可测试

缺点

如果view跟Presenter的交互太过于频繁,那就会跟特定的界面过于紧密,如果视图变更,那么presenter也要跟着变更了。

MVVM 是 Model View ViewModel

Model:业务逻辑处理、数据控制(本地缓存、网络加载)
View(Controller):显示用户可见、用户交互
ViewModel:组织View和Model的逻辑层

View绑定到ViewModel,然后执行一些命令在向它请求一个动作。
ViewModel跟Model通讯,反过来ViewModel在告诉View更新UI

优点

低耦合
可重用性
独立开发
可测试

缺点

因为ViewModel跟Presenter不一样的是所有的数据都是协议、接口回调,而ViewModel则是数据绑定到View上面,如果是数据bug问题,很难定位到数据哪一步出错。
在一个模块中  viewModel中如果Model的数据很大,长期不释放,这是一个不必要的花销。
数据双向绑定不利于代码的重用,开发中常见的重用都是view,一个view中绑定一个model,不同的模块中的model不同,到时候如果只简单的重用view,Model不做任何变化的话不太现实。

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

推荐阅读更多精彩内容

  • 基础 1. 为什么说Objective-C是一门动态的语言? 2. 讲一下MVC和MVVM,MVP? 3. 为...
    波妞和酱豆子阅读 3,307评论 0 46
  • 续写iOS 面试题及答案20道(一) 21.OC中什么是ARC?Objective-C的内存管理机制是什么? AR...
    struggle3g阅读 1,318评论 0 6
  • 丫头,你知道你昨天晚上有多美么,就像明星一样,尤其是低头的时候,头发垂下,看的我惊心动魄。 昨天和你吵架在这里和你...
    海绵宝宝的小盘子阅读 194评论 0 0
  • 在我身边的许多动物都是最最常见的。然而可爱的动物却是成千上万,数也数不清。如果说哪种动物才是最可爱的,在我...
    亂世浮華_8244阅读 154评论 0 0
  • 转眼,一个学期的教育教学工作已经结束了,回顾这一学期以来,学生的点滴进步让我无比欣慰,但还存在着一些问题,今后还需...
    一帘梦幻葛宝梅阅读 211评论 0 1