Objective-C (temp)

基本概念

  • 类( class )
  • 实例/对象 ( object )
  • 实例变量/成员变量 ( ivar, instance variable )
    • 对象 和 变量 是两个东西,当一个变量指向一个对象
  • 属性
  • 消息与方法
    • 编译时与运行时
    • SEL -- 可理解为消息名
    • IMP -- 地址,可理解为实现代码
    • Method -- SEL + IMP + 其他
      Selector(typedef struct objc_selector *SEL): 在运行时 Selectors 用来代表一个方法的名字。Selector 是一个在运行时被注册(或映射)的C类型字符串。Selector由编译器产生并且在当类被加载进内存时由运行时自动进行名字和实现的映射。
      Implementation(typedef id (*IMP)(id, SEL,...)):这个数据类型指向一个方法的实现的最开始的地方。该方法为当前CPU架构使用标准的C方法调用来实现。该方法的第一个参数指向调用方法的自身(即内存中类的实例对象,若是调用类方法,该指针则是指向元类对象 metaclass)。第二个参数是这个方法的名字selector,该方法的真正参数紧随其后。
      Method(typedef struct objc_method *Method): 方法是一个不透明的用来代表一个方法的定义的类型。

基本数据类型

iOS 应避免使用基本类型,建议使用 Foundation 数据类型,这是为了基于64-bit 适配考虑,对应关系如下:

基本类型 Foundation 类型
int NSInteger
float CGFloat
unsigned NSUInteger
动画时间 NSTimeInterval

一般文件形式

  • .h 文件
// 引用
// #import 与 C 的 #include 区别是 #import 可确保不会重复导入同一个文件
#import <UIKit/UIKit.h>
// 一般用于引用系统框架
//@import UserNotifications;
// @class 只是对类进行声明
//@class TestItem;

// Objective-C 中定义一个类并不是使用 @class , 而是使用 @interface 。 interface 直译过来就是接口的意思, Objective-C 使用这个单词定义类,意思是说”外部能用的接口都在这里了“
// : NSObject -- 表示继承
@interface TestObject : NSObject
//{
//    //放置实例变量,但一般不会写在.h文件中,因为一般默认实例变量是私有的
//    //实例变量,一般约定实例变量采用 _ 开头命名
//    NSString *_str1;
//}
// 存取方法
//- (void)setStr1:(NSString *)str;
//- (NSString *)str1;

// 属性 = 实例变量 + getter + setter
// 属性的特征:多线程特征、读写特征、内存管理特征
@property (nonatomic, readwrite, strong) NSString *str2;
@property (nonatomic, readwrite, assign) CGFloat floatValue;

// 初始化方法 ( initialization method ),一般约定实例初始化方法格式为 initXXX ,类初始化方法格式为 类名 + XXX
// 返回值为什么是 instancetype ?涉及到继承问题
// 实例方法 ( instance method ),以 - 开头
- (instancetype)initWithParam:(NSString *)param1 param2:(NSInteger)param2;
// 类方法 ( class method ),以 + 开头
+ (instancetype)testObjectWithoutParam;

// 返回值类型 函数名(包含:) 参数类型 参数名
- (void)method1;
- (void)method2:(NSString *)str;
+ (NSString *)sayHello;

@end
  • .m 文件
#import "TestObject.h"

// 类扩展( class extension )
@interface TestObject()
{
    // 放置实例变量

}
// 放置属性

@end

// 实现程序段 ( implementation block )从 @implementation 指令开始, @end 指令结束
@implementation TestObject
{
    // 放置实例变量

}
// 自定义属性的合成。对应也有 @dynamic ,@dynamic 告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成
@synthesize str2 = _str2;
// 如果自定义了属性的存取方法,编译器不会自动创建相应的实例变量,则需要进行属性合成
- (void)setStr2:(NSString *)str2 {
    _str2 = str2;
}
- (NSString *)str2 {
    return _str2;
}
//在初始化方法中尽量避免使用存取方法访问实例变量,而是直接访问实例变量
- (instancetype)initWithParam:(NSString *)param1 param2:(NSInteger)param2 {
    self = [super init];
    if (self) {

    }
    return self;
}
+ (instancetype)testObjectWithoutParam {
    TestObject *testObject = [[TestObject alloc] initWithParam:@"defaultStr" param2:0];
    return testObject;
}

@end

NOTE1:

  • self -- 隐式的局部变量,指向当前调用方法的这个类的实例
  • super -- 本质是一个编译器标示符,和 self是指向的同一个消息接受者
    super 和 self 的区别是: 当向 self 发送消息时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当向 super 发送消息时,其实是向self发送消息,但是要求系统在查找方法时跳过当前对象的类,从父类的方法列表开始查询
  • id -- 指向任意对象的指针

NOTE2: 类方法和实例方法的区别和联系

  • 类方法:
    类方法是属于类对象的
    类方法只能通过类对象调用
    类方法中的self是类对象
    类方法可以调用其他的类方法
    类方法中不能访问成员变量
    类方法中不定直接调用对象方法
  • 实例方法:
    实例方法是属于实例对象的
    实例方法只能通过实例对象调用
    实例方法中的self是实例对象
    实例方法中可以访问成员变量
    实例方法中直接调用实例方法
    实例方法中也可以调用类方法(通过类名)

OC对象的内存布局

  • 所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中,对象的结构如图
    对象的结构
  • 每一个对象内部都有一个 isa 指针,指向他的类对象,类对象中存放着本对象的 对象方法列表成员变量的列表属性列表
  • 类对象也有一个 isa 指针指向元对象 ( meta class ) ,元对象内部存放的是 类方法列表
  • 类对象还有一个 superclass 指针指向他的父类对象

属性

  • 属性 = 实例变量 + getter + setter
  • 属性的主要作用在于封装对象中的数据
  • 完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”,这个过程由编译器在编译期执行
  • 每增加一个属性,系统都会在ivar_list(成员变量列表)中添加一个成员变量的描述,在method_list(方法列表)中增加setter与getter方法的描述,在prop_list(属性列表)中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转
  • 在 protocol 中使用 @property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
  • 在 category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject 和 objc_getAssociatedObject
  • 什么情况下不会 autosynthesis(自动合成):手动合成属性和实例变量;同时重写了 getter 和 setter 方法;重写了只读属性的 getter 方法;使用 @dynamic 时;在 @protocol 中定义所有的属性;在 category 中定义的所有属性;重载的属性,在子类中重载了父类的属性,必须用 @synthesize 来手动合成ivar。

property 在 Runtime 中是 objc_property_t ,定义如下:

typedef struct objc_property *objc_property_t;

而 objc_property 是一个结构体,包括 name 和 attributes ,定义如下:

struct property_t {
    const char *name;
    const char *attributes;
};

而 attributes 本质是 objc_property_attribute_t ,定义了 property 的一些属性,定义如下:

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

而attributes的具体内容包括:类型,原子性,内存语义和对应的实例变量。
例如:我们定义一个 string 的 property @property (nonatomic, copy) NSString *string; ,通过 property_getAttributes(property) 获取到 attributes 并打印出来之后的结果为T@"NSString",C,N,V_string
其中T就代表类型,可参阅Type Encodings,C就代表Copy,N代表nonatomic,V就代表对于的实例变量。

属性的特征

  • 多线性特征
    nonatomic / atomic
  • 读写特征
    readwrite / readonly
  • 内存管理特征
    strong / weak / assign / copy / unsafe_unretained
    • strong 修饰OC对象属性描述符;强引用,引用计数加一
    • weak 可以修饰OC对象;不做强引用,引用计数不加一,当属性指向的对象被回收后属性自动置为nil
    • assign 修饰非OC对象属性描述符;拷贝值,不做引用计数
    • copy 所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。一般用于指向有可修改子类的对象的指针的属性
    • unsafe_unretained 修饰的属性的指针指向的对象被销毁时,指针不会自动置为nil,而是成为空指针(保留原始值)。因此用于不需要做内存管理,即不指向任何对象的属性
  • 存取方法名
    getter= / setter=
  • 其他
    nonnull / nullable / null_resettable

默认修饰符

  • 基本数据类型 -- atomic,readwrite,assign
  • OC对象 -- atomic,readwrite,strong

weak的使用场景

  • 避免引用循环
  • 自身已经对它进行一次强引用,没有必要再强引用一次

copy的使用场景

  • NSString、NSArray、NSDictionary 等有可修改子类
  • block
    block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作

weak 与 assign 区别

//输出为:
//A dealloc
//b.a: 0x0
//B dealloc
// 因为 A 是强引用 B,所以先 dealloc A,然后 B 中属性指向的 A 指针指向 nil,最后 dealloc B

@interface A : NSObject
@property B *b;
@end
@implementation A
- (void)dealloc {
    NSLog(@"A dealloc");
}
@end

@interface B : NSObject
@property (weak)A *a;
@end
@implementation B
- (void)dealloc {
    NSLog(@"B dealloc");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        B *b = [[B alloc] init];
        {
            A *a = [[A alloc] init];
            a.b = b;
            b.a = a;
        }
        NSLog(@"b.a: %p", b.a);
    }
    return 0;
}
//输出为:
//A dealloc
//b.a: 0x1002025a0  //这里是有值的
//B dealloc

@interface A : NSObject
@property B *b;
@end
@implementation A
- (void)dealloc {
    NSLog(@"A dealloc");
}
@end

@interface B : NSObject
@property (assign)A *a;
@end
@implementation B
- (void)dealloc {
    NSLog(@"B dealloc");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        B *b = [[B alloc] init];
        {
            A *a = [[A alloc] init];
            a.b = b;
            b.a = a;
        }
        NSLog(@"b.a: %p", b.a);
    }
    return 0;
}
  • weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 weak 必须用于 OC 对象。
  • assign在属性所指的对象遭到摧毁时,属性值不会清空。assign 可以用非 OC 对象。

Runtime 如何实现 weak 属性

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

属性所指向的对象使用 copy 修饰符的条件

要使属性所指向的对象能使用 copy 修饰符,对象必需实现 <NSCopying> 协议的 - copyWithZone: 方法

- (id)copyWithZone:(NSZone *)zone {
    CYLUser *copy = [[[self class] allocWithZone:zone]
                     initWithName:_name
                     age:_age
                     sex:_sex];
    copy->_friends = [_friends mutableCopy];
    return copy;
}
//在上述例子中,存放朋友对象的 set 是用 “copyWithZone:” 方法来拷贝的,这种浅拷贝方式不会逐个复制 set 中的元素。若需要深拷贝的话,则可像下面这样,编写一个专供深拷贝所用的方法:
- (id)deepCopy {
    CYLUser *copy = [[[self class] allocWithZone:zone]
                     initWithName:_name
                     age:_age
                     sex:_sex];
    copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
                                             copyItems:YES];
    return copy;
}

NSObject 与 <NSObject>

NSObject 是所有类的根类,<NSObject> 是 NSObject 实现的协议( protocol ),集合了对所有OC对象基本的方法

实例的创建、初始化和销毁

// 返回接收类的新的实例,但这个实例并不能使用,因为所有关于这个实例的实例变量的内存地址都指向0
+ alloc
// 一般的初始化方法
- init
+ new
// 当实例被回收前系统发送该通知
- dealloc
// 返回一个对象,使用此方法必须实现 <NSCopying> 协议的- copyWithZone:方法
- copy
// 返回一个对象,使用此方法必须实现 <NSMutableCopying> 协议的- mutableCopyWithZone:方法
- mutableCopy

浅复制与深复制

浅复制:指针复制
深复制:内容复制。但对于集合类来说,内容复制是复制集合对象本身,对于集合对象内的元素仍是指针复制,即单层深复制

  • 非集合类
immutable 对象 mutable 对象
copy 浅复制 深复制
mutableCopy 深复制 深复制
  • 集合类
immutable 对象 mutable 对象
copy 浅复制 单层深复制
mutableCopy 单层深复制 单层深复制

判断对象类、继承、行为和一致性

// 返回类对象
- class

  • NSStringFromClass // 返回类名的字符串
  • NSClassFromString // 根据字符串返回对应的类对象

// 判断实例是否某一类对象或继承与该类的子类的对象
- isKindOfClass:
// 判断实例是否某一类对象
- isMemberOfClass:
// 判断实例是否能够响应选择器 ( selector )
- respondsToSelector:
- instanceRespondsToSelector:
// 判断实例是否实现协议内容
- conformsToProtocol:

判断两个实例是否相等

// 1.先判断hash值:hash == NO,对象不等;hash == YES ==> isEqual:
// 2.isEqual: == NO,对象不等,hash == YES,对象相等

.hash
- isEqual:

发送消息

- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
- performSelector:withObject:afterDelay:

对象的描述

//可重写属性 getter 方法返回对象的描述
.description

MRC 相关

- retain
- release
- autorelease
- retainCount

KVC 相关

- setValue:forKey:
- setValue:forUndefinedKey:
- setValue:forKeyPath:

- valueForKey:
- valueForUndefinedKey:
- valueForKeyPath:

KVO相关

- addObserver:forKeyPath:options:context:
- removeObserver:forKeyPath:
// oberver 的方法回调
- observeValueForKeyPath:ofObject:change:context:

其他

- dictionaryWithValuesForKeys:
- awakeFromNib

内存管理

OC 通过引用计数来管理内存,决定对象是否需要释放。检查对象的引用计数( retainCount ),如果为 0 ,则释放掉

对象的内存销毁步骤

  1. 对象的引用计数变为零
  • 对象正在被销毁,生命周期即将结束
  • 不能再有新的 __weak 弱引用,否则将指向 nil
  • 调用 [self dealloc]
  1. 父类对象调用 - dealloc
  • 继承关系最底层的父类调用 - dealloc
  • 如果是 MRC 代码,则手动释放实例变量们( ivars )
  • 继承关系中的每一层的父类,都在调用 - dealloc
  1. 根类对象 NSObject 调用 - dealloc
  • 调用 OC Runtime 中的 object_dispose() 方法
  1. 调用 object_dispose()
  • 为 C++ 的实例变量们( ivars ) 调用 destructors
  • 为 ARC 状态下的实例变量们( ivars ) 调用 - release
  • 解除所有使用 Runtime Associate 方法关联的对象
  • 解除所有 __wesk 引用
  • 调用 free()

MRC

在OC 1.0采用的是手动引用计数 (MRC) 来管理内存

  • 关闭内存自动管理
    在 Targets 设置里 -- Build Phases -- Compile Sources 里,对想要关闭内存自动管理的文件的 Compiler Flags 输入 -fno-objc-arc

- release
- retain
- retainCount

@interface Test : NSObject
@end
@implementation Test
- (void)dealloc {
    NSLog(@"Test: dealloc");
}
@end

Test *a = [[Test alloc] init];
Test *b = nil;  
b = a;
[a release];
//输出 Test: dealloc
//因为 b 并未拥有 Test 类型的对象,所以并不影响这个对象的回收,此时如果执行 [b release] 会报错提示 overreleased
Test *a = [[Test alloc] init];
Test *b = nil;  
b = a;
[a retain];
[a release];
//输出 Test: dealloc
[a release]; // 或 [b release];

//谁持有谁释放
Test *a = [[Test alloc] init];
Test *b = nil;  
b = a;
[b retain];
[a release];
//输出 Test: dealloc
[b release];
@interface Test : NSObject
@end
@implementation Test
- (void)dealloc {
    NSLog(@"Test: dealloc");
}
//重写 retain 和 release 方法
- (instancetype)retain {
    NSLog(@"Test before retain: %@", @(self.retainCount));
    id result = [super retain];
    NSLog(@"Test after retain: %@", @(self.retainCount));
    return result;
}
- (oneway void)release
{
    NSLog(@"Test before release: %@", @(self.retainCount));
    [super release];
    NSLog(@"Test after release: %@", @(self.retainCount));
}
@end

Test *a = [[Test alloc] init];
//Test before release: 1
//Test: dealloc
//Test after release: 1
[a release];
//Test after release: 1 是因为都已经释放了,也没必要将引用计数置为 0
@interface Test : NSObject
+ (Test *)getInstance;
@end
@implementation Test
- (void)dealloc {
    NSLog(@"Test: dealloc");
}
+ (Test *)getInstance {
    Test *result = [[Test alloc] init];
    return result;
}
@end

Test *a = [Test getInstance];
[a release];
// 这里我们明确知道要调用 [a release],但如果 [Test getInstance] 是个黑箱,又如何知道是否该 release  
// 函数创建对象,但无法release它;函数调用者使用对象,且不应去 release 它  
//即函数调用的时候不是释放对象的好时机  
// autorelease == 延后调用一次release ,关于延后,具体是什么时机 ==> NSAutoreleasePool 相关  
//对象标记 autorelease 相当于 放到 NSAutoreleasePool 对象里,当 NSAutoreleasePool 对象调用 - drain 时,它会对里面所有对象都调用 release  

@interface Test : NSObject
+ (Test *)getInstance;
@end
@implementation Test
- (void)dealloc {
    NSLog(@"Test: dealloc");
}
+ (Test *)getInstance {
    // OC 约定放到最近的一个 NSAutoreleasePool 对象
    Test *result = [[[Test alloc] init] autorelease];
    return result;
}
@end

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Test *a = [Test getInstance];
//输出 Test: dealloc
[pool drain];
  • autoreleasepool 以一个队列数组的形式实现,主要通过三个函数完成 Push / release / Pop 操作
    • objc_autoreleasepoolPush
    • objc_autorelease
    • objc_autoreleasepoolPop

ARC

ARC(自动引用计数)相对于 MRC ,不仅仅在编译时自动添加 retain / release / autorelease ,应该是编译期和运行期两部分帮助开发者管理内存

  • 编译期,ARC 用的是更底层的 C 接口实现的 retain / release / autorelease ,这样做性能更好,也是为什么不能再 ARC 环境下手动 retain / release / autorelease ,同时对同一上下文的同一对象的成对 retain / release 操作进行优化

ARC 下 不能使用 NSAutoreleasePool ,对于 autorelease 需求,有 NSAutoreleasePool ==> @autoreleasepool { … }, ARC 下 autorelease 的释放时机:

  • 指定 @autoreleasepool { … } 下,在当前大括号的作用域结束时释放
  • 不指定 @autoreleasepool { … } ,对象会在当前 Runloop 结束时释放

ARC下的dealloc

//创建B并且当回收时,先调用B的dealloc,再调用A的dealloc  

@interface A : NSObject
@end
@implementation A
- (void)dealloc {
    NSLog(@"A dealloc");
}
@end

@interface B : NSObject
@property A *a;
@end
@implementation B
- (void)dealloc {
    NSLog(@"B dealloc");
}
- (instancetype)init {
    self = [super init];
    if (self) {
        _a = [[A alloc] init];
    }
    return self;
}
@end

BAD_ACCESS

BAD_ACCESS 出现的情况分为两种:

  • 访问了野指针
    • 在 MRC 下对已经释放的对象再次执行 release
    • 如属性用 assign 修饰,当所指向的对象已经释放回收后,属性并不会指向 nil ,这时访问该对象
  • 死循环

CoreFoundation 管理内存

CoreFoundation 没有自动内存管理,需要手动释放内存

CFStringRef string = CFStringCreateWithCString(NULL, "苹果", kCFStringEncodingUTF8);  
CFRelease(string);

因此,当一个对象在 Foundation 或 CoreFoundation 中创建而需要在另一个系统中使用时,需要确定对象的管理权,分三种情况:

  1. 不改变
  2. 在 Foundation 中创建str,在CoreFoundation中手动管理
  3. 在 CoreFoundation 中创建str,在Foundation中自动管理

原则:明确告知由哪个系统来管理内存资源

  1. __bridge —— 跨层级使用,但不更改对象的拥有权
  2. __bridge_retained —— 内存归属修改,需手动release
  3. __bridge_transfer —— 内存归属修改,由ARC管理,不需手动release
NSString *str = @"苹果";
CFStringRef str2 = (__bridge CFStringRef)str;

NSString *str3 = @"苹果";
CFStringRef str4 = (__bridge_retained CFStringRef)str3;
CFRelease(str4);


CFStringRef string = CFStringCreateWithCString(NULL, "苹果", kCFStringEncodingUTF8);
NSString *string2 = (__bridge NSString *)string;
//...
CFRelease(string);

CFStringRef string3 = CFStringCreateWithCString(NULL, "苹果", kCFStringEncodingUTF8);
NSString *string4 = (__bridge_transfer NSString *)string3;

KVC & KVO

KVC

KVC ( Key Value Coding ),它把整个 object 看成 key 和 value 的对应关系
- setValue:forKey:
// 可重写用于处理没有对应key的情况
- setValue:forUndefinedKey:
// 嵌套使用
- setValue:forKeyPath:

- valueForKey:
- valueForUndefinedKey:
- valueForKeyPath:

  • KVC 属性不用实现 getter 和 setter 方法

KVC Collection Operators

@avg.
@sum.
@max.
@min.
@count

NSMutableArray* array = [NSMutableArray array];
NSDictionary* dic = @{@"name" : @"苹果", @"price" : @1.5};
Fruit* fruit = [[Fruit alloc] initWithDict:dict];
[array addObject:fruit];
NSDictionary* dic = @{@"name" : @"苹果", @"price" : @1};
Fruit* fruit = [[Fruit alloc] initWithDict:dict];
[array addObject:fruit];
NSDictionary* dic = @{@"name" : @"苹果", @"price" : @2.5};
Fruit* fruit = [[Fruit alloc] initWithDict:dict];
[array addObject:fruit];

CGFloat avg = [[array valueForKeyPath:@"@avg.price"] floatValue];
CGFloat max = [[array valueForKeyPath:@"@max.price"] floatValue];

KVO

KVO ( Key Value Observing )
- addObserver:forKeyPath:options:context:
- removeObserver:forKeyPath:
//观察的回调
- observeValueForKeyPath:ofObject:change:context:

Runtime

要使用Runtime相关方法,需导入Runtime框架#import <objc/runtime.h>
OC 可以转成 C 的 API,通过这些API可以达到高级的功能,这个C API也称为 Runtime

在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)

OC函数调用本质

@interface Fruit : NSObject
@property CGFloat price;
@end
@implementation Fruit
@end

@interface Ferrari : NSObject
@property CGFloat price;
@end
@implementation Ferrari
@end

@interface Util : NSObject
@end
@implementation Util
+(CGFloat)getPriceForFruit:(Fruit *)f {
    return f.price;
}
@end


Ferrari *f = [[Ferrari alloc] init];
f.price = 5000000;
//输出5000000
NSLog(@"price: %@", @([Util getPriceForFruit:f]));

上面的代码Xcode会警告但编译运行仍会输出结果,对于 Util 的类方法 getPriceForFruit ,它接收的是 Fruit 类型的参数,但在代码中实际我们给的是 Ferrari 类型是实例对象
再看下面这一句代码:

CGFloat result = [cashier checkout];   

这句代码可以有两种说法:

  • 调用cashier的一个名为checkout的函数,该函数没有参数,其返回结果是一个CGFloat,存入result变量中
  • 给cashier发送一个名为checkout的消息,没有附加内容,消息响应结果是一个CGFloat,存入result变量中
    两种说法都没有出现类名,所以,发送消息只跟该对象响应该消息的实现相关

objc_msgSend

首先用终端切换到项目目录下,输入 clang -rewrite-objc xxx.m ,把OC程序重写成C代码
简化代码可知每一个对象的消息发送,都是调用到

// id -- self,指发送消息的对象本身
// SEL -- _cmd,指消息名称
objc_msgSend(id, SEL, …)

即在 OC 动态编译时,方法在运行时会被动态转为消息发送,即: objc_msgSend()

  • OC 中向一个对象发送消息时,Runtime 会根据对象的 isa 指针找到该对象所属的类,然后在该类中的方法列表及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend 方法不会返回值,所谓的返回内容都是具体调用时执行的
    • 如果向一个 nil 对象发送消息, isa 指针指向的内存地址为 0 ,所以不会出现任何错误
  • 在 objc_msgSend() 中第二个参数为 SEL,对于每个类对象都有一个方法列表,方法列表中记录着方法的名称,方法实现以及参数类型,其实 selector 本质就是方法名称,通过这个 selector ,Runtime 可以尝试找到对应的 IMP 地址,即其方法实现。如果找到了,就跳到响应的函数IMP去执行实现代码,如果在最顶层的父类中依然找不到响应的方法实现,则用 objc_msgForward() 函数(IMP类型)指针代替 IMP ,最后执行这个 IMP。 objc_msgForward() 会做以下几件事:
    • Method resolution
      Runtime 发送 + resolveInstanceMethod: 或 + resolveClassMethod: 。在这两个方法里可以提供一个方法将方法与传入的 SEL 绑定( class_addMethod() ),并放回 YES,那么运行时系统就会重新启动一次消息发送的过程
    • Fast forwarding
      如果在 Method resolution 的方法里返回 NO,则 Runtime 发送 - forwardingTargetForSelector: 。在这个方法里可以 return 一个对象,则系统会将消息发送给该对象
    • Normal forwarding
      如果在 Fast forwarding 的方法里返回 nil ,则 Runtime 发送 - methodSignatureForSelector: 。在这个方法里可以 return 一个 NSMethodSignature 类型的对象,它表示一个方法签名,记录了返回值和参数的类型信息,如果方法 return 为 nil ,则 Runtime 会发出 - doesNotRecognizeSelector: 消息,程序报 unrecognized selector 错误。如果 return 一个 NSMethodSignature 类型的对象,则 Runtime 会创建一个 NSInvocation 类型的对象作为 - forwardInvocation: 的实参发送消息给目标对象。NSInvocation 实际上就是对一个消息的描述,包括 selector 以及参数等信息。在 - forwardInvocation:方法里可以给 NSInvocation 类型对象发送 - invokeWithTarget:消息,传进去一个对象,则由该对象来发送该消息
@interface TestModel : NSObject
- (void)hahahaInstance;
+ (void)hahahaClass;
@end
@implementation TestModel
- (void)instanceMethodDealWithHahaha {
    NSLog(@"hahaha");
}
+ (void)classMethodDealWithHahaha {
    NSLog(@"hahaha");
}
// Method resolution
+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod");
    return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod");
    if (sel == @selector(hahahaInstance)) {
        Method method = class_getInstanceMethod([self class], @selector(instanceMethodDealWithHahaha));
        IMP methodImp = method_getImplementation(method);
        const char *methodType = method_getTypeEncoding(method);
        class_addMethod([self class], sel, methodImp, methodType);
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
// Fast forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwardingTargetForSelector");
    if (aSelector == @selector(hahahaInstance)) {
        TestModel2 *testModel2 = [[TestModel2 alloc] init];
        return testModel2;
    }
    return [super forwardingTargetForSelector:aSelector];
}
// Normal forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        Method m = class_getInstanceMethod([TestModel2 class], aSelector);
        const char *type = method_getTypeEncoding(m);
        signature = [NSMethodSignature signatureWithObjCTypes:type];
    }
    return signature;
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"doesNotRecognizeSelector");
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation");
    TestModel2 *aaa = [[TestModel2 alloc] init];
    [anInvocation invokeWithTarget:aaa];
}
@end
@interface TestModel2 : NSObject
- (void)hahahaInstance;
@end
@implementation TestModel2
- (void)hahahaInstance {
    NSLog(@"TestModel2's hahaha");
}
@end

OC <=> C

要使用Runtime相关方法,需包含头文件 #include <objc/runtime.h>
Runtime C API 可做到:

  • 读取语言信息
  • 可以做一些OC无法做的事情
  • 修改语言信息

获取一个类对象的所有属性名

unsigned int count = 0;
//objc_property_t 是一个C的数据结构,为属性的所有描述
objc_property_t *properties = class_copyPropertyList([XXX class], &count);
for (int i = 0; i < count; i++) {
    objc_property_t peoperty = properties[i];
    const char *propertyName = property_getName(properties[i]);
    NSString *name = [NSString stringWithUTF8String:propertyName];
    NSLog(@"%@", name);
}
free(properties);

Category添加属性(关联对象)

  • objc_setAssociatedObject
    object -- 关联的源对象
    key -- 关联用到的 key,key 值必须保证是一个对象级别的唯一常量
    value -- 对象的key所关联的变量的值,当为 nil 时可清除一个已存在的关联
    policy -- 关联策略,Associative Object Behaviors
  • objc_getAssociatedObject
    //一般不用
  • objc_removeAssociatedObjects
//.h 文件
#import <Foundation/Foundation.h>

@interface NSObject (ExtentNSObject)

@property (nonatomic, copy)NSString *name;

@end

//.m 文件
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
//取key值一般有三种推荐的方式
//static 的地址是唯一的
static char kAssociatedObjectNameKey1;
//
static void *kAssociatedObjectNameKey2 = &kAssociatedObjectNameKey2;

@implementation NSObject (ExtentNSObject)

- (NSString *)name {
//    return objc_getAssociatedObject(self, &kAssociatedObjectNameKey1);
//    return objc_getAssociatedObject(self, kAssociatedObjectNameKey2);
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setName:(NSString *)name {
//    objc_setAssociatedObject(self, &kAssociatedObjectNameKey1, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
//    objc_setAssociatedObject(self, kAssociatedObjectNameKey2, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

添加方法

在 runtime 中为类在运行时添加一个方法
class_addMethod()会为类添加一个其父类的实现方法的重写,但不会替换掉原来的方法的实现。也就是说,如果类没有重写父类的实现方法, class_addMethod 会用指定的实现方法重写父类的实现方法(相当于重写),但如果子类重写了父类的方法,则消息发送的还是重写的方法

// SEL -- 方法名
// IMP -- 方法必须带至少两个参数:self 和 _cmd  
// typeEncoding -- 用来描述消息实现体的参数和返回值类型的顺序:返回值 + 参数  
class_addMethod(class,SEL, IMP, typeEncoding)
//TestModel 没有重写 NSObject 的 description 方法
TestModel *testModel = [[TestModel alloc] init];
//输出为 <TestModel: 0x100202fa0>
NSLog(@"%@", [testModel description]);
NSObject *obj = [[NSObject alloc] init];
//输出为 <NSObject: 0x100202500>
NSLog(@"%@", [obj description]);

class_addMethod([TestModel class], @selector(description), imp_implementationWithBlock(^NSString*(TestModel* self){
    return @"hahaha";
}), "@@:");

//输出为 hahaha
NSLog(@"%@", [testModel description]);
//输出为 <NSObject: 0x100202500>
NSLog(@"%@", [obj description]);
typeEncoding

- (NSString *)description; ==> id id SEL
id OC对象在运行时都是OC对象
id 所有的事件都有一个隐含的参数self
SEL 所有的事件都有一个隐含的参数_cmd

  • typeEncoding 的对应关系表
    |type| id| SEL| void|
    |:---:|:---:|:---:|:---:|
    |Encoding| @| :| v|

  • 可用 method_getTypeEncoding() 获取一个方法的 TypeEncoding

方法替代

如果类中有对应的方法实现(不是其父类),则可以用 class_replaceMethod()

#import <Foundation/Foundation.h>
@interface NSObject (ExtentNSObject)
@end
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
@implementation NSObject (ExtentNSObject)
- (NSString *)myDescription {
    return @"hahaha";
}
@end

TestModel *testModel = [[TestModel alloc] init];
//输出为 <TestModel: 0x100400800>
NSLog(@"%@", [testModel description]);

Method method = class_getInstanceMethod([NSObject class], @selector(myDescription));
IMP imp = method_getImplementation(method);
const char *typeEncoding = method_getTypeEncoding(method);  

class_replaceMethod([NSObject class], @selector(description), imp, typeEncoding);
//输出为 hahaha
NSLog(@"%@", [testModel description]);

这里用 category 扩展的方法实现 myDescription 代替了 NSObject 原本的 description
但是原本的 description 就被丢弃了

方法交换

method_exchangeImplementations() 交换了两个方法的实现

#import <Foundation/Foundation.h>
@interface NSObject (ExtentNSObject)
@end
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
@implementation NSObject (ExtentNSObject)
- (NSString *)myDescription {
    //输出为 selector: description
    NSLog(@"selector: %@", NSStringFromSelector(_cmd));
    //调用了 myDescription 的实现
    return [NSString stringWithFormat:@"hahaha, %@", [self myDescription]];
}
@end

TestModel *testModel = [[TestModel alloc] init];
//输出为 <TestModel: 0x100400800>
NSLog(@"%@", [testModel description]);

Method method = class_getInstanceMethod([NSObject class], @selector(myDescription));
Method m = class_getInstanceMethod([NSObject class], @selector(description));
method_exchangeImplementations(method, m);
//输出为 hahaha, <TestModel: 0x100300d70>
NSLog(@"%@", [testModel description]);
Method Swizzle

Method Swizzle 要尽早的发生,在方法调用之前

#import <Foundation/Foundation.h>
@interface NSObject (ExtentNSObject)
@end
#import "NSObject+ExtentNSObject.h"
#include <objc/runtime.h>
@implementation NSObject (ExtentNSObject)
+ (void)load {
    Method method = class_getInstanceMethod([NSObject class], @selector(myDescription));
    Method m = class_getInstanceMethod([NSObject class], @selector(description));
    method_exchangeImplementations(method, m);
}
- (NSString *)myDescription {
    //输出为 selector: description
    NSLog(@"selector: %@", NSStringFromSelector(_cmd));
    //调用了 myDescription 的实现
    return [NSString stringWithFormat:@"hahaha, %@", [self myDescription]];
}
@end

TestModel *testModel = [[TestModel alloc] init];
//输出为 hahaha, <TestModel: 0x100400180>
NSLog(@"%@", [testModel description]);

Method Swizzle 最佳实践

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method newMethod = class_getInstanceMethod([NSObject class], @selector(myDescription));
        IMP newImp = method_getImplementation(newMethod);
        const char *newType = method_getTypeEncoding(newMethod);
        Method oldMethod = class_getInstanceMethod([NSObject class], @selector(description));
        IMP oldImp = method_getImplementation(oldMethod);
        const char *oldType = method_getTypeEncoding(oldMethod);
        BOOL addMethod = class_addMethod([self class], @selector(description), newImp, newType);
        if (addMethod) {
            NSLog(@"method add");
            class_replaceMethod([self class], @selector(myDescription), oldImp, oldType);
        } else {
            NSLog(@"method did not add");
            method_exchangeImplementations(newMethod, oldMethod);
        }
    });
}
- (NSString *)myDescription {
//    NSLog(@"selector: %@", NSStringFromSelector(_cmd));
    return [NSString stringWithFormat:@"hahaha, %@", [self myDescription]];
//    return @"hahaha";
}
  • swizzling应该只在+load中完成
    在 Objective-C 的运行时中,每个类有两个方法都会自动调用。+load 是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。
  • swizzling 应该只在 dispatch_once 中完成
    由于 swizzling 改变了全局的状态,所以我们需要确保每个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。

NSString

初始化方法

//语法糖
NSString *str = @“xxoo”;  
NSString *str = [[NSString alloc] initWithUTF8String:"xxoo"];  
NSString *str = [NSString stringWithFormat:@"哈哈哈 %@", @"xxoo"] ;  

字符串比较

- isEqualToString:
// 返回值为NSComparisonResult类型的枚举值
- compare:
- compare:options:
- compare:options:range:

BOOL result = [str1 compare:str2 options:NSCaseInsensitiveSearch] == 0;//大小写无关相等

获取长度

.length

查找和替换

// 返回值NSRange不是一个OC对象,它是一个结构体,包含两个属性.location.length,当查找结果不存在时,.location的值为NSNotFound
- rangeOfString:

NSRange range = [str1 rangeOfString:@"xxoo"];

可修改子类 NSMutableString

//str不能修改原文,- stringByReplacingOccurrencesOfString: 是重新新建一个NSString对象并赋予实例变量
str1 = [str1 stringByReplacingOccurrencesOfString:@"xx" withString:@"ss"];
//NSMutableString可修改原文
\- replaceOccurrencesOfString:withString:options:range:

格式化字符串

//在OC输出只要带*的都可以用%@输出OC变量
%@
%d
%f
%p
_cmd
__FUNCTION__
__FILE__ //文件名
__LINE__ // 行数
__PRETTY_FUNCTION__ // 类名与方法名

其它

NSStringFromClass
NSStringFromSelector

NSArray

创建

NSArray *d = @[@“a”, @“b”];
// 等同于
NSString *raw[] = {@“a”, @“b”};
NSArray *d = [NSArray arrayWithObjects:raw count:2];

NSArray *d = [[NSArray alloc] initWithObjects:@1, @2, nil];

//指明ObjectType
NSArray<NSString *> d = @[@“a”, @“b”];

放置非OC对象

NSArray只接受OC对象,因此非OC对象需要包装

基本类型变量的包装

  • NSNumber
    NSNumber 继承与NAValue,用于包装基本的数据类型:BOOl / char / double / float / int(NSInteger) / long / long long / short / unsigned char / unsigned int(NSUInteger) / unsigned long / unsigned long long / unsigned short
//语法糖
@1
//等同于
[NSNumber numberWithInteger:1]
//解包
[number integerValue];
  • NSValue
    NSValue 用于包装一个C或OC的数据项,比较常用的有
    + valueWithCGPoint:
    + valueWithCGSize:
    + valueWithCGRect:
    + valueWithRange:
  • 其它
const char *str = “haha”;
NSString *oc_str = @(str);

放置空对象

//Log打印输出为1, 2, 3
NSArray *d = [[NSArray alloc] initWithObjects:@1, @2, @3, nil, @4, nil];
//NSNull也是一个类
//Log打印输出为1, 2, 3, "<null>", 4
NSArray *d = [[NSArray alloc] initWithObjects:@1, @2, @3, [NSNull null], @4, nil];

取出元素

//语法糖
NSNumber *v = d[0];
// 等同于
NSNumber *v = [d objectAtIndex:0];

NOTE1: 如果取出的元素不存在(超出范围),会导致崩溃
但使用 .firstObject.lastObject 访问则不会,为空则返回null

迭代

for-in
- enumerateObjectsUsingBlock:

查找

//如果不存在,返回NSNotFound, NSNotFound为最大值
- indexOfObject:

可修改子类 NSMutableArray

- addObject:
- addObjectsFromArray:
- insertObject:atIndex:
- insertObjects:atIndexes:

- removeObject:
- removeObjectAtIndex:
- removeObject:inRange:
- removeLastObject
- removeAllObjects

- replaceObjectAtIndex:withObject:

- sortUsingDescriptors:
- sortUsingComparator:

NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
    if ([obj1 integerValue] > [obj2 integerValue]) {
        return (NSComparisonResult)NSOrderedDescending;
    }
    if ([obj1 integerValue] < [obj2 integerValue]) {
        return (NSComparisonResult)NSOrderedAscending;
    }
    return (NSComparisonResult)NSOrderedSame;
}];

NSIndexSet & NSMutableIndexSet

NSIndexSet 表示一个索引的集合(NSRange & NSInteger),一组index

NSMutableArray *d = [[NSMutableArray alloc] initWithObjects:@1, @2, @3, @4, @5, nil];
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSetWithIndex:1];
[indexSet addIndexesInRange:NSMakeRange(3, 1)];
//等同于
//NSRange range;
//range.location = 3;
//range.length = 1;
//[indexSet addIndexesInRange:range];
//结果为:1, 3, 5
[d removeObjectsAtIndexes:indexSet];

NSDictionary

NSDictionary的 key 需实现<NSCoping>协议的 - copyWithZone: 方法,value必须为oc对象

创建

// 语法糖
NSDictionary *fruitToPrice = @{@"苹果" : @1.5, @"草莓" : @1.2};
// 等同于
NSNumber *nums[] = {@1.5, @1.2};
NSString *strs[] = {@"苹果", @"梨子"};
NSDictionary *dic = [NSDictionary dictionaryWithObjects:(id *)nums forKeys:(id *)str count:2];

NSArray *nums = @[@1.5, @1.2];
NSArray *strs = @[@"苹果", @"梨子"];
NSDictionary *dic = [NSDictionary dictionaryWithObjects:nums forKeys:strs];

NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@1.5, @"苹果", @1.2, @"梨子", nil];

// copyItems 为 NO 时为浅复制,为 YES 时为深复制,集合里的每个对象都会收到 copyWithZone: 消息。如果集合里的对象遵循 NSCopying 协议,那么对象就会被深复制到新的集合。如果对象没有遵循 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。
- initWithDictionary:copyItems:

迭代

// NSDictionary 是无序的
for-in

for (id key in dic.allKeys) {
    id value = [dic objectForKey:key];
}

- enumerateKeysAndObjectsUsingBlock:

可修改子类 NSMutableDictionary

- setObject:forKey:

NSMutableDictionary *fruitToPrice = [NSMutableDictionary dictionaryWithObjectsAndKeys:@1.5, @"苹果", nil];

[fruitToPrice setObject:@1.2 forKey:@"草莓"];
// 等同于
fruitToPrice[@"草莓"] = @1.2;

NSSet

NSSet 无序且各个元素互不相等

//.count == 4
NSSet *set = [NSSet setWithOnject:@1, @2, @3, @4, @1, nil];

NSString *str = @“a”;
NSString *str2 = @“a”;
// set.count == 1
// isEqual:和 hash 是NSSet用来判断两个对象是不是同一个对象
NSSet *set = [NSSet setWithObjects:str, str2, nil];

// 深复制 copyItems每一项都会执行 - copyWithZone:
- initWithSet:copyItems:

可修改子类 NSMutableSet

NSString / NSNumber / NSArray / NSSet / NSDictionary 的相互转换

NSString <=> NSNumber

NSString *str = @"18";
NSNumber *num = @([str integerValue]);

NSString *str = @"18abc”;
//18
NSNumber *num = @([str integerValue]);

NSString *str = @"abc”;
//0
NSNumber *num = @([str integerValue]);

NSString *str = nil;
//0
NSNumber *num = @([str integerValue]);

NSNumber *num2 = @18;
NSString *str2 = [num2 stringValue];

NSString <=> NSArray

NSString *str = @"a, b, c";
NSArray *array = [str componentsSeparatedByString:@","];

NSArray *array2 = @[@"a", @"b", @"c"];
NSString *str2 = [array2 componentsJoinedByString:@", "];

NSArray <=> NSSet

//去重
NSArray *array = @[@"a", @"b", @"c", @"a"];
NSSet *set = [[NSSet alloc] initWithArray:array];

NSSet *set2 = [[NSSet alloc] initWithObjects:@"a", @"b", @"c", nil];
NSMutableArray *array2 = [NSMutableArray array];
for (id value in set2) {
     [array2 addObject:value];
}

NSArray <=> NSDicytionary

NSArray *array = @[@"苹果", @"草莓"];
NSArray *array2 = @[@1.5, @1.2];
NSDictionary *dic = [NSDictionary dictionaryWithObjects:array2 forKeys:array];

NSArray *keys = dic.allKeys;
NSArray *values = dic.allValues;

Block

Block 的声明和使用

Block 的声明
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};

//形参
- (CGFloat)calcPriceWithDiscountHandler:(CGFloat(^)(Fruit *fruit))handler;  // CGFloat(^)(Fruit *fruit) 指block的类型
//实参
[fruit calcPriceWithDiscountHandler:^CGFloat(Fruit *fruit) {
    return 1.0;
}];

// typedef Block
typedef int(^myBlock)(NSString *name);
@property (nonatomic, copy) myBlock mBlock;
  • 使用copy关键字,因为Block对象是在栈中创建的
  • 可以使用其封闭作用域的所有变量
  • 对捕获的变量具有强引用循环,如果捕获变量也对Block对象具有强引用,就会导致强引用循环

__wesk & __block

NSData

NSData 表示一块内存区域

NSString *str = @"abc";
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSString *str2 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

NSError

NSError 包含

  • code 错误代码,一般为 enum 内容
  • domain 错误分类
  • userinfo 为 NSDictionary 类型,为 NSError 带入更多信息

/+ errorWithDomain:code:userInfo:

NS_ENUM & NS_OPTIONS

typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
};

参考

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

推荐阅读更多精彩内容