Swift 类与结构体

Swift本身是一门高效、安全的语言,怎么理解?通过对Swift类与结构体的比较,深刻理解这两种类型,运用好类与结构体,写出高效安全的代码。

对于开发过程中类与结构体的选择,Apple有给出建议, 参考 Choosing Between Structures and Classes
  • 默认使用结构体
  • 需要Objective-C互交互时使用类
  • 需要共享实例时使用类【如单例、通过对象引用标识修改属性后被共享】
  • 使用结构体遵循协议来共享协议实现的方法

1. Swift 类与结构体的区别

class struct 备注
支持继承 YES NO
支持OC调用 YES (需继承 NSObject) NO
创建速度 struct创建比class快1/3左右参考Demo
内存管理 引用类型(ARC) 值类型(赋值拷贝)
内存分配位置 堆(Heap) 栈(Stack) 由于栈的访问速度大于堆,所以值类型的创建和访问速度大于引用类型
多线程安全 NO 堆的访问不是多线程安全 YES 栈访问是多线程安全 String, Array, Dictionary虽然是struct,数据存储在struct的一个class属性中,支持copy-on-write ,所以并不是线程读写安全
函数派发方式 表派发/静态派发/消息派发 静态派发
内存占用 class默认会继承SwiftObject, 实例中包含metadata, refCounts等信息, 而struct只是单纯的数据结构

2. Swift 类与结构体的相同点

  • 编译器 LLVM swiftc 【OC 的编译器是 LLVM clang】
  • 支持定义初始化器【struct编译器默认生成一个初始化器,class需要手动写】
  • 支持定义属性和方法
  • 支持extension扩展方法
  • 支持遵循协议

3. 类的初始化流程

为什么类创建比结构体慢,而且占用空间比结构体多,这里看看类的初始化流程。

3.1 继承NSObject的类

class Student: NSObject {
    let id: Int
    let name: String
    init(id: Int, name: String) {
        self.id = id
        self.name = name
        super.init()
    }
}
let student = Student(id: 20, name: "Lili")

断点到let student = Student(id: 20, name: "Lili")这行代码,然后查看汇编 Xcode —> Debug —> Debug WorkFlow —> Always Show Disassembly

__allocating_init

objc_allocWithZone

objc_msgSendSuper2

从上面的断点跟踪中可以断定出继承NSObject的类初始化流程为 Student.__allocating_init --> objc_allocWithZone -->objc_msgSendSuper2, objc_allocWithZone就比较熟悉了,这时进入OC的对象初始化流程。

3.1 纯Swift类

class Student {
    let id: Int
    let name: String
    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
}
let student = Student(id: 20, name: "Lili")

断点到let student = Student(id: 20, name: "Lili")这行代码,然后查看汇编 Xcode —> Debug —> Debug WorkFlow —> Always Show Disassembly

SwiftTest.Student.__allocating_init

swift_allocObject

swift_slowAlloc

malloc_zone_malloc

从上面的断点跟踪中可以断定出纯Swift类初始化流程为 Student.__allocating_init --> swift_allocObject --> swift_slowAlloc --> malloc_zone_malloc

3.2 纯Swift类源码导读

  • swift_allocObject 定义在HeapObject.cpp文件
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);
  return object;
}
  • swift_allocObject 定义在HeapObject.cpp文件
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);
#endif
  } else {
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}

源码导读的结果与汇编Debug得出的结果一致

4. 类的实例结构分析

4.1 HeapObject

上面一章节查看Swift源码,初始化流程中,返回了一个heapObject类型,源码分析,swift类实例的内存结构是个HeapObject,HeapObject有两个属性,一个是metadata,一个是refCounts。见HeapObject.h文件

struct HeapObject {
  HeapMetadata const * metadata;
  InlineRefCounts refCounts   // 占8字节
} 
  • InlineRefCounts 定义在RefCount.h文件,
typedef struct {
  __swift_uintptr_t refCounts; // 占8字节,  typedef uintptr_t __swift_uintptr_t;
} InlineRefCountsPlaceholder;
typedef InlineRefCountsPlaceholder InlineRefCounts;
  • HeapMetadata 定义在Metadata.h文件中, HeapMetadata 继承于TargetMetadata<Runtime>
using HeapMetadata = TargetHeapMetadata<InProcess>;   // 表示HeapMetadata是TargetHeapMetadata<InProcess>的别名
struct TargetHeapMetadata : TargetMetadata<Runtime>  // 表示TargetHeapMetadata继承于TargetMetadata<Runtime>
  • TargetHeapMetadata定义在Metadata.h文件中,通过MetadataKind进行初始化
/// The common structure of all metadata for heap-allocated types.  A
/// pointer to one of these can be retrieved by loading the 'isa'
/// field of any heap object, whether it was managed by Swift or by
/// Objective-C.  However, when loading from an Objective-C object,
/// this metadata may not have the heap-metadata header, and it may
/// not be the Swift type metadata for the object's dynamic type.
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}  // Swift通过`MetadataKind `的初始化函数
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa) 
    : TargetMetadata<Runtime>(isa) {} // 继承NSObject的对象通过isa指针进行初始化的函数
#endif
};
  • MetadataKind 的定义在MetadataValues.h文件,是一个enum class类型, 会导入MetadataKind.def文件
enum class MetadataKind : uint32_t {
#define METADATAKIND(name, value) name = value,
#define ABSTRACTMETADATAKIND(name, start, end)                                 \
  name##_Start = start, name##_End = end,
#include "MetadataKind.def"
  
  /// The largest possible non-isa-pointer metadata kind value.
  ///
  /// This is included in the enumeration to prevent against attempts to
  /// exhaustively match metadata kinds. Future Swift runtimes or compilers
  /// may introduce new metadata kinds, so for forward compatibility, the
  /// runtime must tolerate metadata with unknown kinds.
  /// This specific value is not mapped to a valid metadata kind at this time,
  /// however.
  LastEnumerated = 0x7FF,
};
  • 整理MetadataKind定义,得到这样一个表, 可以推断出,调用TargetHeapMetadata(MetadataKind kind)函数时,如果kind == MetadataKind::Class, 表述初始化一个class
name value
name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF
  • TargetHeapMetadata继承TargetMetadata<Runtime>, 在TargetMetadata找到这个函数,当kind == MetadataKind::Class时,得到TargetClassMetadata类型
  ConstTargetMetadataPointer<Runtime, TargetTypeContextDescriptor>
  getTypeContextDescriptor() const {
    switch (getKind()) {
    case MetadataKind::Class: {
      const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);
      if (!cls->isTypeMetadata())
        return nullptr;
      if (cls->isArtificialSubclass())
        return nullptr;
      return cls->getDescription();
    }
    case MetadataKind::Struct:
    case MetadataKind::Enum:
    case MetadataKind::Optional:
      return static_cast<const TargetValueMetadata<Runtime> *>(this)
          ->Description;
    case MetadataKind::ForeignClass:
      return static_cast<const TargetForeignClassMetadata<Runtime> *>(this)
          ->Description;
    default:
      return nullptr;
    }
  }
  • TargetClassMetadata是所有类基类,TargetClassMetadata 继承TargetAnyClassMetadata
/// The structure of all class metadata.  This structure is embedded
/// directly within the class's heap metadata structure and therefore
/// cannot be extended without an ABI break.
///
/// Note that the layout of this type is compatible with the layout of
/// an Objective-C class.
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;

  TargetClassMetadata() = default;
  constexpr TargetClassMetadata(const TargetAnyClassMetadata<Runtime> &base,
             ClassFlags flags,
             ClassIVarDestroyer *ivarDestroyer,
             StoredPointer size, StoredPointer addressPoint,
             StoredPointer alignMask,
             StoredPointer classSize, StoredPointer classAddressPoint)
    : TargetAnyClassMetadata<Runtime>(base),
      Flags(flags), InstanceAddressPoint(addressPoint),
      InstanceSize(size), InstanceAlignMask(alignMask),
      Reserved(0), ClassSize(classSize), ClassAddressPoint(classAddressPoint),
      Description(nullptr), IVarDestroyer(ivarDestroyer) {}

  // The remaining fields are valid only when isTypeMetadata().
  // The Objective-C runtime knows the offsets to some of these fields.
  // Be careful when accessing them.

  /// Swift-specific class flags.
  ClassFlags Flags;

  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;

  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;

  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;

  /// Reserved for runtime use.
  uint16_t Reserved;

  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;

  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;

  // Description is by far the most likely field for a client to try
  // to access directly, so we force access to go through accessors.
private:
  /// An out-of-line Swift-specific description of the type, or null
  /// if this is an artificial subclass.  We currently provide no
  /// supported mechanism for making a non-artificial subclass
  /// dynamically.
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;

public:
  /// A function for destroying instance variables, used to clean up after an
  /// early return from a constructor. If null, no clean up will be performed
  /// and all ivars must be trivial.
  TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;

  // After this come the class members, laid out as follows:
  //   - class members for the superclass (recursively)
  //   - metadata reference for the parent, if applicable
  //   - generic parameters for this class
  //   - class variables (if we choose to support these)
  //   - "tabulated" virtual methods

  using TargetAnyClassMetadata<Runtime>::isTypeMetadata;

  ConstTargetMetadataPointer<Runtime, TargetClassDescriptor>
  getDescription() const {
    assert(isTypeMetadata());
    return Description;
  }

  typename Runtime::StoredSignedPointer
  getDescriptionAsSignedPointer() const {
    assert(isTypeMetadata());
    return Description;
  }

  void setDescription(const TargetClassDescriptor<Runtime> *description) {
    Description = description;
  }

  // [NOTE: Dynamic-subclass-KVO]
  //
  // Using Objective-C runtime, KVO can modify object behavior without needing
  // to modify the object's code. This is done by dynamically creating an
  // artificial subclass of the the object's type.
  //
  // The isa pointer of the observed object is swapped out to point to
  // the artificial subclass, which has the following properties:
  // - Setters for observed keys are overridden to additionally post
  // notifications.
  // - The `-class` method is overridden to return the original class type
  // instead of the artificial subclass type.
  //
  // For more details, see:
  // https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html

  /// Is this class an artificial subclass, such as one dynamically
  /// created for various dynamic purposes like KVO?
  /// See [NOTE: Dynamic-subclass-KVO]
  bool isArtificialSubclass() const {
    assert(isTypeMetadata());
    return Description == nullptr;
  }
  void setArtificialSubclass() {
    assert(isTypeMetadata());
    Description = nullptr;
  }

  ClassFlags getFlags() const {
    assert(isTypeMetadata());
    return Flags;
  }
  void setFlags(ClassFlags flags) {
    assert(isTypeMetadata());
    Flags = flags;
  }

  StoredSize getInstanceSize() const {
    assert(isTypeMetadata());
    return InstanceSize;
  }
  void setInstanceSize(StoredSize size) {
    assert(isTypeMetadata());
    InstanceSize = size;
  }

  StoredPointer getInstanceAddressPoint() const {
    assert(isTypeMetadata());
    return InstanceAddressPoint;
  }
  void setInstanceAddressPoint(StoredSize size) {
    assert(isTypeMetadata());
    InstanceAddressPoint = size;
  }

  StoredPointer getInstanceAlignMask() const {
    assert(isTypeMetadata());
    return InstanceAlignMask;
  }
  void setInstanceAlignMask(StoredSize mask) {
    assert(isTypeMetadata());
    InstanceAlignMask = mask;
  }

  StoredPointer getClassSize() const {
    assert(isTypeMetadata());
    return ClassSize;
  }
  void setClassSize(StoredSize size) {
    assert(isTypeMetadata());
    ClassSize = size;
  }

  StoredPointer getClassAddressPoint() const {
    assert(isTypeMetadata());
    return ClassAddressPoint;
  }
  void setClassAddressPoint(StoredSize offset) {
    assert(isTypeMetadata());
    ClassAddressPoint = offset;
  }

  uint16_t getRuntimeReservedData() const {
    assert(isTypeMetadata());
    return Reserved;
  }
  void setRuntimeReservedData(uint16_t data) {
    assert(isTypeMetadata());
    Reserved = data;
  }

  /// Get a pointer to the field offset vector, if present, or null.
  const StoredPointer *getFieldOffsets() const {
    assert(isTypeMetadata());
    auto offset = getDescription()->getFieldOffsetVectorOffset();
    if (offset == 0)
      return nullptr;
    auto asWords = reinterpret_cast<const void * const*>(this);
    return reinterpret_cast<const StoredPointer *>(asWords + offset);
  }

  uint32_t getSizeInWords() const {
    assert(isTypeMetadata());
    uint32_t size = getClassSize() - getClassAddressPoint();
    assert(size % sizeof(StoredPointer) == 0);
    return size / sizeof(StoredPointer);
  }

  /// Given that this class is serving as the superclass of a Swift class,
  /// return its bounds as metadata.
  ///
  /// Note that the ImmediateMembersOffset member will not be meaningful.
  TargetClassMetadataBounds<Runtime>
  getClassBoundsAsSwiftSuperclass() const {
    using Bounds = TargetClassMetadataBounds<Runtime>;

    auto rootBounds = Bounds::forSwiftRootClass();

    // If the class is not type metadata, just use the root-class bounds.
    if (!isTypeMetadata())
      return rootBounds;

    // Otherwise, pull out the bounds from the metadata.
    auto bounds = Bounds::forAddressPointAndSize(getClassAddressPoint(),
                                                 getClassSize());

    // Round the bounds up to the required dimensions.
    if (bounds.NegativeSizeInWords < rootBounds.NegativeSizeInWords)
      bounds.NegativeSizeInWords = rootBounds.NegativeSizeInWords;
    if (bounds.PositiveSizeInWords < rootBounds.PositiveSizeInWords)
      bounds.PositiveSizeInWords = rootBounds.PositiveSizeInWords;

    return bounds;
  }

#if SWIFT_OBJC_INTEROP
  /// Given a statically-emitted metadata template, this sets the correct
  /// "is Swift" bit for the current runtime. Depending on the deployment
  /// target a binary was compiled for, statically emitted metadata templates
  /// may have a different bit set from the one that this runtime canonically
  /// considers the "is Swift" bit.
  void setAsTypeMetadata() {
    // If the wrong "is Swift" bit is set, set the correct one.
    //
    // Note that the only time we should see the "new" bit set while
    // expecting the "old" one is when running a binary built for a
    // new OS on an old OS, which is not supported, however we do
    // have tests that exercise this scenario.
    auto otherSwiftBit = (3ULL - SWIFT_CLASS_IS_SWIFT_MASK);
    assert(otherSwiftBit == 1ULL || otherSwiftBit == 2ULL);

    if ((this->Data & 3) == otherSwiftBit) {
      this->Data ^= 3;
    }

    // Otherwise there should be nothing to do, since only the old "is
    // Swift" bit is used for backward-deployed runtimes.
    
    assert(isTypeMetadata());
  }
#endif

  bool isStaticallySpecializedGenericMetadata() const {
    auto *description = getDescription();
    if (!description->isGeneric())
      return false;

    return this->Flags & ClassFlags::IsStaticSpecialization;
  }

  bool isCanonicalStaticallySpecializedGenericMetadata() const {
    auto *description = getDescription();
    if (!description->isGeneric())
      return false;

    return this->Flags & ClassFlags::IsCanonicalStaticSpecialization;
  }

  static bool classof(const TargetMetadata<Runtime> *metadata) {
    return metadata->getKind() == MetadataKind::Class;
  }
}
  • 通过整理TargetClassMetadata 的所有属性,得到以下这样一个数据结构
struct Metadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer
}

4.2 通过Swift代码还原Metadata的数据结构

  • 通过4.1小节的分析和总结,将HeapObject通过Swift数据结构进行描述,然后bindMemory进行内存数据进行映射,来验证自己的假设
struct HeapObject{
    var metadata: UnsafeRawPointer
    var refCounts: UInt64
}
struct Metadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer
}

// 定义一个class
class Student {
    let id = 10
    let name = "Lili"
}

let student = Student()
// 获取`student`实实例的原始指针
let pRawHeapObject = Unmanaged.passUnretained(student).toOpaque()

// 将`pRawHeapObject`指向的内容映射到`HeapObject`数据结构
let heapObject = pRawHeapObject.bindMemory(to: HeapObject.self, capacity: 1).pointee
print(heapObject)

// 将`heapObject.metadata`映射到`Metadata`数据结构
let metadata = heapObject.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(metadata)
print("Student instance size is:", class_getInstanceSize(Student.self))

在调试窗口查看运行结果: “superClass: _TtCs12_SwiftObject “, “instanceSize”: 40 符合预期

HeapObject(metadata: 0x00000001000081a8, refCounts: 3)
Metadata(kind: 4295000432, superClass: _TtCs12_SwiftObject, cacheData: (7402896512, 140943646785536), data: 4302401618, classFlags: 2, instanceAddressPoint: 0, instanceSize: 40, instanceAlignmentMask: 7, reserved: 0, classSize: 120, classAddressPoint: 16, typeDescriptor: 0x0000000100003cf0, iVarDestroyer: 0x0000000000000000)
Student instance size is: 40

5 总结

  • Swift 类最终通过malloc_zone_malloc分配在堆空间, 堆空间的分配效率比栈低、堆空间的读写非线程安全。
  • 继承NSObject的类,通过调用TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa) 函数,将isa指针作为参数,初始化一个TargetHeapMetadata结构体,与纯Swift类的数据结构基本保持一致,所以能被OC调用。
  • HeapObject和Metadata的描述占用大量内存空间, 所以类占用的空间比结构体大。
  • HeapObjec.refCounts 虽然加锁保证线程安全,但是频繁读写也会消耗资源,也是类比结构体慢的原因之一。

参考文档:
copy-on-write: https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#advice-use-copy-on-write-semantics-for-large-values
Swift写时复制(copy-on-write): https://www.jianshu.com/p/e8b1336d9d5d
Choosing Between Structures and Classes: https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • Swift创建类和结构体时,不需要将声明文件和实现文件分开 一个类的实例通常被称为对象。 结构体和类对比 共同点都...
    Sunday_David阅读 294评论 0 0
  • 我的博客[https://dengfeng520.github.io/] 1、值类型和引用类型 在iOS中虚拟内存...
    小時間光阅读 749评论 0 2
  • 类和结构体 结构体和类作为一种通用而又灵活的结构,成为了人们构建代码的基础。你可以使用定义常量、变量和函数的语法,...
    xiaofu666阅读 227评论 0 0
  • 在面向过程的语言中,要想实现类似类的功能只能借助结构体,其实从OC源码也能看出来,类的组成本就是复杂的结构体实现的...
    如风如花不如你阅读 8,203评论 2 6
  • Swift进阶-类与结构体[https://www.jianshu.com/p/347bafbb3cf8]Swif...
    顶级蜗牛阅读 1,699评论 0 11