類簇映穗,從NSArray說起

在iOS開發(fā)中,廣泛運(yùn)用了類蔟(Class clusters)的設(shè)計(jì)模式幕随。如NSNumber蚁滋、NSString、NSArray等赘淮。類簇其實(shí)是對(duì)現(xiàn)實(shí)的一種抽象和封裝辕录,基于抽象工廠模式(Abstract Factory Pattern)。最近在讀書過程中聯(lián)想到一些東西梢卸,于是嘗試更加深入地去了解它走诞。

問題

所謂抽象工廠模式就是將各種同一主題的工廠類封裝起來,提供一個(gè)通用的抽象工廠類而不用知道具體的工廠類低剔。對(duì)于類蔟的論述已經(jīng)多如牛毛了速梗,在此更加推薦閱讀蘋果的官方文檔肮塞。本篇文章將以NSArray為例,著重講下從alloc到init過程中發(fā)生的事姻锁。在重溫《Effective Objective-C 2.0》的過程中我注意到這么一段話:

In the case of NSArray, when an instance is allocated, it’s an instance of another class that’s allocated (during a call to alloc), known as a placeholder array. This placeholder array is then converted to an instance of another class, which is a concrete subclass of NSArray. This is a pretty little dance but beyond the scope of this book to explan fully.

可以用一個(gè)具體的例子來說明枕赵,比如:

NSArray *placeholder = [NSArray alloc];
NSArray *arr1 = [placeholder init];
NSArray *arr2 = [placeholder initWithObjects:@0, nil];
NSArray *arr3 = [placeholder initWithObjects:@0, @1, nil];
NSArray *arr4 = [placeholder initWithObjects:@0, @1, @2, nil];

NSLog(@"placeholder: %s", object_getClassName(placeholder));    // placeholder: __NSPlaceholderArray
NSLog(@"arr1: %s", object_getClassName(arr1));                  // arr1: __NSArray0
NSLog(@"arr2: %s", object_getClassName(arr2));                  // arr2: __NSSingleObjectArrayI
NSLog(@"arr3: %s", object_getClassName(arr3));                  // arr3: __NSArrayI
NSLog(@"arr4: %s", object_getClassName(arr4));                  // arr4: __NSArrayI

可以看到,alloc后所得到的類為__NSPlaceholderArray位隶。而當(dāng)init為一個(gè)空數(shù)組后拷窜,變成了__NSArray0。如果有且僅有一個(gè)元素涧黄,那么為__NSSingleObjectArrayI篮昧。如果數(shù)組大于一個(gè)元素,那么為__NSArrayI笋妥。這兒暫且不去討論為什么arr1-4有所區(qū)別——先來關(guān)心一下為什么alloc和init前后轉(zhuǎn)化為了不同的類懊昨。

從名字上很容易知道__NSPlaceholderArray作用為占位,我們可以嘗試打印幾個(gè)地址:

NSArray *placeholder = [NSArray alloc];
NSArray *placeholder2 = [NSArray alloc];
NSArray *arr1 = [placeholder init];
NSArray *arr2 = [placeholder initWithObjects:@0, nil];

NSLog(@"placeholder: %p", placeholder);     // placeholder: 0x618000013b10
NSLog(@"placeholder2: %p", placeholder2);   // placeholder2: 0x618000013b10
NSLog(@"arr1: %p", arr1);                   // arr1: 0x618000013b30
NSLog(@"arr2: %p", arr2);                   // arr2: 0x608000014050

可以看到[NSArray alloc]產(chǎn)生的實(shí)例為一個(gè)單例春宣,而在init或者其他初始化方法后酵颁,地址發(fā)生了變化,也就是說月帝,placeholder目前看來只是一個(gè)占位用的單例躏惋,在init后即被新的實(shí)例給替換掉了。那么嚷辅,這個(gè)placeholder真的只用做占位嗎簿姨?

__NSPlaceholderArray

我們可以參考另一個(gè)開源實(shí)現(xiàn)GNUstep一瞥究竟。根據(jù)GNUstep的代碼簸搞,可知NSObject的alloc是直接返回的[self allocWithZone: NSDefaultMallocZone()]扁位,也就是說調(diào)用了對(duì)應(yīng)類實(shí)現(xiàn)的此方法。我們來看看GNUstep中NSArray的allocWithZone:是如何實(shí)現(xiàn)的:

+ (id) allocWithZone: (NSZone*)z
{
  if (self == NSArrayClass)
  {
    /*
    * For a constant array, we return a placeholder object that can
    * be converted to a real object when its initialisation method
    * is called.
    */
    if (z == NSDefaultMallocZone() || z == 0)
    {
      /*
      * As a special case, we can return a placeholder for an array
      * in the default malloc zone extremely efficiently.
      */
      return defaultPlaceholderArray;
    }
    else
    {
      // 此處省略
    }
  }
  else
  {
    return NSAllocateObject(self, 0, z);
  }
}

可以看到NSArray此時(shí)會(huì)返回defaultPlaceholderArray攘乒。在GNUstep的實(shí)現(xiàn)中贤牛,defaultPlaceholderArray實(shí)例所對(duì)應(yīng)的類為GSPlaceholderArray。所以alloc完成后的init消息是發(fā)送給GSPlaceholderArray實(shí)例的则酝。而init恰恰調(diào)用的是initWithObjects:count:——這個(gè)方法其實(shí)就是NSArray的指定初始化方法殉簸。我們繼續(xù)看看GNUstep實(shí)現(xiàn):

// GSPlaceholderArray
- (id) initWithObjects: (const id[])objects count: (NSUInteger)count
{
  self = (id)NSAllocateObject(GSInlineArrayClass, sizeof(id)*count, [self zone]);
  return [self initWithObjects: objects count: count];
}

// GSInlineArray
- (id) initWithObjects: (const id[])objects count: (NSUInteger)count
{
  _contents_array = (id*)(((void*)self) + class_getInstanceSize([self class]));

  if (count > 0)
  {
    NSUInteger  i;

    for (i = 0; i < count; i++)
    {
      if ((_contents_array[i] = RETAIN(objects[i])) == nil)
      {
        _count = i;
        DESTROY(self);
        [NSException raise: NSInvalidArgumentException format: @"Tried to init array with nil object"];
      }
    }
    _count = count;
  }
  return self;
}

可以看到在GSPlaceholderArray的initWithObjects:count:方法中,通過NSAllocateObject給GSInlineArray實(shí)例分配空間沽讹,包括所包含元素的空間般卑。并且在GSInlineArray的initWithObjects:count:方法中,對(duì)分配的元素的空間進(jìn)行初始化爽雄。自此就返回了一個(gè)類型為GSInlineArray的實(shí)例蝠检。

CoreFoundation中NSArray的相關(guān)實(shí)現(xiàn)會(huì)比GNUstep中的實(shí)現(xiàn)復(fù)雜些,但通過匯編代碼來看可以知道基本邏輯是類似的挚瘟,在此不再贅述叹谁。有幾點(diǎn)可以提下:1饲梭、當(dāng)元素為空時(shí),返回的是__NSArray0的單例焰檩;2憔涉、當(dāng)元素僅有一個(gè)時(shí),返回的是__NSSingleObjectArrayI的實(shí)例析苫;3兜叨、當(dāng)元素大于一個(gè)的時(shí)候,返回的是__NSArrayI的實(shí)例衩侥。根據(jù)網(wǎng)上的資料国旷,大多未提及__NSSingleObjectArrayI,可能是后面新增的茫死,理由大概還是為了效率跪但,在此不深究。

同樣的峦萎,對(duì)于NSMutableArray特漩、NSNumber、NSString等也是有相同的NSPlaceholderNumber機(jī)制的骨杂。

可變類的Placeholder

提到NSMutableArray,那么問題來了——NSMutableArray是否也有NSMutablePlaceholderArray呢雄卷?

答案是:并沒有搓蚪。一開始我也是先入為主地認(rèn)為一定對(duì)應(yīng)著一個(gè)可變類型的placeholderArray。但在好奇心驅(qū)使下打印了各個(gè)實(shí)例的父類后丁鹉,我吃驚的發(fā)現(xiàn)其實(shí)并沒有——它依然是__NSPlaceholderArray妒潭。

NSArray *placeholder = [NSArray alloc];
NSArray *arr1 = [placeholder init];
NSArray *arr2 = [placeholder initWithObjects:@0, nil];
NSArray *arr3 = [placeholder initWithObjects:@0, @1, nil];

NSLog(@"superclass of placeholder: %s", class_getName(placeholder.superclass)); // superclass of placeholder: NSMutableArray
NSLog(@"superclass of arr1: %s", class_getName(arr1.superclass));               // superclass of arr1: NSArray
NSLog(@"superclass of arr2: %s", class_getName(arr2.superclass));               // superclass of arr2: NSArray
NSLog(@"superclass of arr3: %s", class_getName(arr3.superclass));               // superclass of arr3: NSArray

NSMutableArray *mPlaceholder = [NSMutableArray alloc];
NSMutableArray *mArr1 = [mPlaceholder init];
NSMutableArray *mArr2 = [mPlaceholder initWithObjects:@0, nil];
NSMutableArray *mArr3 = [mPlaceholder initWithObjects:@0, @1, nil];

NSLog(@"mPlaceholder: %s", object_getClassName(mPlaceholder));    // mPlaceholder: __NSPlaceholderArray
NSLog(@"mArr1: %s", object_getClassName(mArr1));                  // mArr1: __NSArrayM
NSLog(@"mArr2: %s", object_getClassName(mArr2));                  // mArr2: __NSArrayM
NSLog(@"mArr3: %s", object_getClassName(mArr3));                  // mArr3: __NSArrayM

NSLog(@"superclass of mPlaceholder: %s", class_getName(mPlaceholder.superclass));   // superclass of mPlaceholder: NSMutableArray
NSLog(@"superclass of mArr1: %s", class_getName(mArr1.superclass));                 // superclass of mArr1: NSMutableArray
NSLog(@"superclass of mArr2: %s", class_getName(mArr2.superclass));                 // superclass of mArr2: NSMutableArray
NSLog(@"superclass of mArr3: %s", class_getName(mArr3.superclass));                 // superclass of mArr3: NSMutableArray

當(dāng)時(shí)我的心里大概出現(xiàn)了這么個(gè)文件名:大吃一驚.jpg。但轉(zhuǎn)念一想也是可以接受的揣钦,畢竟NSMutableArray是NSArray的子類雳灾,從這個(gè)角度來看,共用一個(gè)NSPlaceholderArray也是情有可原的冯凹。那么現(xiàn)在的問題是:它是個(gè)單例谎亩,又該怎么區(qū)分可變和不可變數(shù)組的呢?畢竟兩個(gè)初始化方法selector是相同的宇姚。GNUstep似乎并不能找到答案匈庭,那么就再次祭出大殺器匯編吧。

CoreFoundation`-[__NSPlaceholderArray initWithObjects:count:]:
; 前略
    0x10edf9698 <+40>:  je     0x10edf96b3               ; <+67>
    0x10edf969a <+42>:  nopw   (%rax,%rax)
    0x10edf96a0 <+48>:  cmpq   $0x0, (%rdx,%r8,8)
    0x10edf96a5 <+53>:  je     0x10edf972c               ; <+188>
    0x10edf96ab <+59>:  incq   %r8
    0x10edf96ae <+62>:  cmpq   %r9, %r8
    0x10edf96b1 <+65>:  jb     0x10edf96a0               ; <+48>
->  0x10edf96b3 <+67>:  cmpq   %rdi, 0x3b514e(%rip)      ; __immutablePlaceholderArray
    0x10edf96ba <+74>:  je     0x10edf96d2               ; <+98>
->  0x10edf96bc <+76>:  cmpq   %rdi, 0x3b514d(%rip)      ; __mutablePlaceholderArray
    0x10edf96c3 <+83>:  jne    0x10edf97b7               ; <+327>
    0x10edf96c9 <+89>:  movq   0x3aa260(%rip), %rdi      ; (void *)0x000000010f1a5db0: __NSArrayM
    0x10edf96d0 <+96>:  jmp    0x10edf9717               ; <+167>
    0x10edf96d2 <+98>:  cmpq   $0x1, %r9
    0x10edf96d6 <+102>: je     0x10edf96f5               ; <+133>
    0x10edf96d8 <+104>: testq  %r9, %r9
    0x10edf96db <+107>: jne    0x10edf9710               ; <+160>
    0x10edf96dd <+109>: leaq   0x3b7c9c(%rip), %rax      ; __NSArray0__
    0x10edf96e4 <+116>: movq   (%rax), %rdi
    0x10edf96e7 <+119>: movq   0x3a862a(%rip), %rsi      ; "retain"
    0x10edf96ee <+126>: popq   %rbp
    0x10edf96ef <+127>: jmpq   *0x371b2b(%rip)           ; (void *)0x000000010e961ac0: objc_msgSend
    0x10edf96f5 <+133>: movq   0x3aa224(%rip), %rdi      ; (void *)0x000000010f1a5d60: __NSSingleObjectArrayI
    0x10edf96fc <+140>: movq   (%rdx), %rdx
    0x10edf96ff <+143>: movq   0x3a92c2(%rip), %rsi      ; "__new::"
    0x10edf9706 <+150>: xorl   %ecx, %ecx
    0x10edf9708 <+152>: callq  *0x371b12(%rip)           ; (void *)0x000000010e961ac0: objc_msgSend
    0x10edf970e <+158>: popq   %rbp
    0x10edf970f <+159>: retq   
    0x10edf9710 <+160>: movq   0x3aa211(%rip), %rdi      ; (void *)0x000000010f1a5d88: __NSArrayI
    0x10edf9717 <+167>: movq   0x3a92b2(%rip), %rsi      ; "__new:::"
    0x10edf971e <+174>: xorl   %r8d, %r8d
    0x10edf9721 <+177>: movq   %r9, %rcx
    0x10edf9724 <+180>: callq  *0x371af6(%rip)           ; (void *)0x000000010e961ac0: objc_msgSend
    0x10edf972a <+186>: popq   %rbp
; 后也略

讓我們重點(diǎn)關(guān)注兩個(gè)箭頭所指向的cmpq指令吧浑劳≮宄郑可以很清楚地知道,其實(shí)就是判斷self == __immutablePlaceholderArray和self == __mutablePlaceholderArray魔熏。也就是說衷咽,CoreFoundation在某個(gè)時(shí)機(jī)初始化了兩個(gè)NSPlaceholderArray鸽扁,分別存起來。在調(diào)用__NSPlaceholderArray的initWithObjects:count:方法時(shí)镶骗,直接通過判斷存起來的這兩個(gè)單例來判斷是否是不可變還是可變數(shù)組桶现。真相就是這么赤裸裸的簡(jiǎn)單粗暴。

我們?cè)賮砜纯?code>+[NSArray allocWithZone:]

CoreFoundation`+[NSArray allocWithZone:]:
    0x10b5004a0 <+0>:   pushq  %rbp
    0x10b5004a1 <+1>:   movq   %rsp, %rbp
    0x10b5004a4 <+4>:   pushq  %r15
    0x10b5004a6 <+6>:   pushq  %r14
    0x10b5004a8 <+8>:   pushq  %rbx
    0x10b5004a9 <+9>:   subq   $0x18, %rsp
    0x10b5004ad <+13>:  movq   %rdx, %r14
    0x10b5004b0 <+16>:  movq   %rdi, %rbx
    0x10b5004b3 <+19>:  movq   0x3aa47e(%rip), %rdi      ; (void *)0x000000010b8acdd8: NSArray
    0x10b5004ba <+26>:  movq   0x3a9647(%rip), %r15      ; "self"
    0x10b5004c1 <+33>:  movq   %r15, %rsi
    0x10b5004c4 <+36>:  callq  *0x371d56(%rip)           ; (void *)0x000000010b068ac0: objc_msgSend
->  0x10b5004ca <+42>:  cmpq   %rbx, %rax
    0x10b5004cd <+45>:  je     0x10b500511               ; <+113>
    0x10b5004cf <+47>:  movq   0x3aa392(%rip), %rdi      ; (void *)0x000000010b8ace50: NSMutableArray
    0x10b5004d6 <+54>:  movq   %r15, %rsi
    0x10b5004d9 <+57>:  callq  *0x371d41(%rip)           ; (void *)0x000000010b068ac0: objc_msgSend
->  0x10b5004df <+63>:  cmpq   %rbx, %rax
    0x10b5004e2 <+66>:  je     0x10b500521               ; <+129>
    0x10b5004e4 <+68>:  movq   %rbx, -0x28(%rbp)
    0x10b5004e8 <+72>:  movq   0x3aa7e9(%rip), %rax      ; (void *)0x000000010b8acea0: NSArray
    0x10b5004ef <+79>:  movq   %rax, -0x20(%rbp)
    0x10b5004f3 <+83>:  movq   0x3a88b6(%rip), %rsi      ; "allocWithZone:"
    0x10b5004fa <+90>:  leaq   -0x28(%rbp), %rdi
    0x10b5004fe <+94>:  movq   %r14, %rdx
    0x10b500501 <+97>:  callq  0x10b6acb50               ; symbol stub for: objc_msgSendSuper2
    0x10b500506 <+102>: addq   $0x18, %rsp
    0x10b50050a <+106>: popq   %rbx
    0x10b50050b <+107>: popq   %r14
    0x10b50050d <+109>: popq   %r15
    0x10b50050f <+111>: popq   %rbp
    0x10b500510 <+112>: retq   
    0x10b500511 <+113>: movq   0x3aa428(%rip), %rdi      ; (void *)0x000000010b8ace78: __NSPlaceholderArray
    0x10b500518 <+120>: movq   0x3a94d9(%rip), %rsi      ; "immutablePlaceholder"
    0x10b50051f <+127>: jmp    0x10b50052f               ; <+143>
    0x10b500521 <+129>: movq   0x3aa418(%rip), %rdi      ; (void *)0x000000010b8ace78: __NSPlaceholderArray
    0x10b500528 <+136>: movq   0x3a94f1(%rip), %rsi      ; "mutablePlaceholder"
    0x10b50052f <+143>: addq   $0x18, %rsp
    0x10b500533 <+147>: popq   %rbx
    0x10b500534 <+148>: popq   %r14
    0x10b500536 <+150>: popq   %r15
    0x10b500538 <+152>: popq   %rbp
    0x10b500539 <+153>: jmpq   *0x371ce1(%rip)           ; (void *)0x000000010b068ac0: objc_msgSend
    0x10b50053f <+159>: nop    

依舊看兩個(gè)箭頭卖词,可以看到當(dāng)self為NSArray和NSMutableArray時(shí)候分別返回immutablePlaceholder和mutablePlaceholder巩那,它們都是__NSPlaceholderArray類型的。這樣就驗(yàn)證了上面的想法此蜈。

Primitive methods

上面多處提到了initWithObjects:count:即横。為什么它這么重要?我們可以看看NSArray的interface是如何定義的:

<figure class="highlight" style="background: rgb(255, 255, 255);">

@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>

@property (readonly) NSUInteger count;
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects count:(NSUInteger)cnt NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

@end

不同于普通的繼承裆赵,在創(chuàng)建某個(gè)類蔟的具體的子類時(shí)东囚,通常不需要實(shí)現(xiàn)所有的功能。也不同于普通的抽象類战授,在公共的抽象基類中页藻,一般提供了輔助的方法的實(shí)現(xiàn),子類只需要提供幾個(gè)核心方法的實(shí)現(xiàn)即可植兰。

在CoreFoundation的類蔟的抽象工廠基類(如NSArray份帐、NSString、NSNumber等)中楣导,Primitive methods指的就是這些核心的方法废境,也就是那些在創(chuàng)建子類時(shí)必須要重寫的方法,通常在類的interface中聲明筒繁,在文檔中一般也會(huì)說明噩凹。其他可選實(shí)現(xiàn)的方法在Category中聲明。同時(shí)還需要注意其整個(gè)繼承樹的祖先的Primitive methods也都需要實(shí)現(xiàn)毡咏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驮宴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子呕缭,更是在濱河造成了極大的恐慌堵泽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恢总,死亡現(xiàn)場(chǎng)離奇詭異落恼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)离熏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門佳谦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滋戳,你說我怎么就攤上這事钻蔑∩犊蹋” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵咪笑,是天一觀的道長(zhǎng)可帽。 經(jīng)常有香客問我,道長(zhǎng)窗怒,這世上最難降的妖魔是什么映跟? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮扬虚,結(jié)果婚禮上努隙,老公的妹妹穿的比我還像新娘。我一直安慰自己辜昵,他們只是感情好荸镊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堪置,像睡著了一般躬存。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舀锨,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天岭洲,我揣著相機(jī)與錄音,去河邊找鬼坎匿。 笑死钦椭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碑诉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼侥锦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼进栽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恭垦,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤快毛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后番挺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唠帝,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年玄柏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了襟衰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粪摘,死狀恐怖瀑晒,靈堂內(nèi)的尸體忽然破棺而出绍坝,到底是詐尸還是另有隱情,我是刑警寧澤苔悦,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布轩褐,位于F島的核電站,受9級(jí)特大地震影響玖详,放射性物質(zhì)發(fā)生泄漏把介。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一蟋座、第九天 我趴在偏房一處隱蔽的房頂上張望拗踢。 院中可真熱鬧,春花似錦蜈七、人聲如沸秒拔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砂缩。三九已至,卻和暖如春三娩,著一層夾襖步出監(jiān)牢的瞬間庵芭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工雀监, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留双吆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓会前,卻偏偏與公主長(zhǎng)得像好乐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓦宜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,385評(píng)論 8 265
  • 在iOS開發(fā)中蔚万,我們?cè)诜浅7浅6嗟牡胤接玫搅藬?shù)組。而關(guān)于數(shù)組临庇,有很多需要注意和優(yōu)化的細(xì)節(jié)反璃,需要我們潛入到下面,去了...
    伯陽閱讀 6,075評(píng)論 3 30
  • 整理出的一些簡(jiǎn)單實(shí)用的OC筆試題,如有錯(cuò)誤之處希望大家及時(shí)提出,以便修改,不誤人子弟.1假夺、不會(huì)立刻使引用計(jì)數(shù)器改變...
    李xiao屁的憂傷閱讀 2,901評(píng)論 1 20
  • 本文轉(zhuǎn)自:咖門 公眾號(hào)「一位“老炮兒”的研發(fā)心得:做一杯好茶淮蜈,就像導(dǎo)演一部大片」 近兩年來,新茶飲逐漸流行并頗有百...
    吳建伸閱讀 696評(píng)論 0 4