iOS 面試題-2019.上

  1. UIViewCALayer是什麼關係
  • UIView繼承自UIResponder類,能夠響應事件
  • CALayer直接繼承自NSObject類,不能夠響應事件
  • UIViewCALayerdelegate(CALayerDelegate)
  • UIView主要處理事件,CALayer負責繪製
  • 每一個UIView內部都有一個CALayer在背後提供內容的繪製和顯示,而且UIView的尺寸樣式都由內部的Layer所提供。二者都有樹狀層級結構,Layer內部有SubLayersView內部有SubViews,可是LayerView多了個AnchorPoint
  1. NSCacheNSMutableDictionary的相同點與區別

相同點: NSCacheNSMutableDictionary功能用法基本是相同的 區別: NSCache是線程安全的,NSMutableDictionary線程不安全,Mutable開發的類通常都是線程不安全的 當內存不足時NSCache會自動釋放內存(因此從緩存中取數據的時候總要判斷是否爲空) NSCache能夠指定緩存的限額,當緩存超出限額自動釋放內存 NSCacheKey只是對對象進行了Strong引用,而非拷貝,因此不須要實現NSCopying協議git

  1. atomic的實現機制;爲何不能保證絕對的線程安全(最好能夠結合場景來講)
  • atomic會對屬性的setter/getter方法進行加鎖,這僅僅只能保證在操做setter/getter方法是安全的。不能保證其餘線程的安全
  • 例如:線程1調用了某一屬性的setter方法並進行到了一半,線程2調用其getter方法,那麼會執行完setter操做後,再執行getter操做,線程2會獲取到線程1setter後的完整的值;當幾個線程同時調用同一屬性的setter、getter方法時,會獲取到一個完整的值,但獲取到的值不可控
  1. iOS 中內省的幾個方法

對象在運行時獲取其類型的能力稱爲內省。內省能夠有多種方法實現 OC運行時內省的4個方法:程序員

  • 判斷對象類型:
-(BOOL) isKindOfClass:            // 判斷是不是這個類或者這個類的子類的實例
-(BOOL) isMemberOfClass:      // 判斷是不是這個類的實例
複製代碼
  • 判斷對象/類是否有這個方法
-(BOOL) respondsToSelector:                      // 判斷實例是否有這樣方法
+(BOOL) instancesRespondToSelector:      // 判斷類是否有這個方法
複製代碼
  1. objc在向一個對象發送消息時,發生了什麼

根據對象的isa指針找到該對象所屬的類,去objc的對應的類中找方法 1.首先,在相應操做的對象中的緩存方法列表中找調用的方法,若是找到,轉向相應實現並執行 2.若是沒找到,在相應操做的對象中的方法列表中找調用的方法,若是找到,轉向相應實現執行 3.若是沒找到,去父類指針所指向的對象中執行1,2. 4.以此類推,若是一直到根類還沒找到,轉向攔截調用,走消息轉發機制 5.若是沒有重寫攔截調用的方法,程序報錯github

  1. 你是否接觸過OC中的反射機制?簡單聊一下概念和使用
  • class反射
  • 經過類名的字符串形式實例化對象
Class class = NSClassFromString(@"student"); 
Student *stu = [[class alloc] init];
複製代碼
  • 將類名變爲字符串
Class class = [Student class];
NSString *className = NSStringFromClass(class);
複製代碼
  • SEL的反射
  • 經過方法的字符串形式實例化方法
SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];
複製代碼
  • 將方法變成字符串 NSStringFromSelector(@selector(setName:));
  1. 這個寫法會出什麼問題@property (nonatomic, copy) NSMutableArray *arr;

添加,刪除,修改數組內元素的時候,程序會由於找不到對應的方法而崩潰。緣由:是由於copy就是複製一個不可變NSArray的對象,不能對NSArray對象進行添加/修改數組

  1. 如何讓本身的類用copy修飾符

若想令本身所寫的對象具備拷貝功能,則需實現NSCopying協議。若是自定義的對象分爲可變版本與不可變版本,那麼就要同時實現NSCopyingNSMutableCopying協議。 具體步驟: 1.需聲明該類聽從NSCopying協議 2.實現NSCopying協議的方法,具體區別戳這裏緩存

  • NSCopying協議方法爲:
- (id)copyWithZone:(NSZone *)zone {
  MyObject *copy = [[[self class] allocWithZone: zone] init];
  copy.username = self.username;
  return copy;
}
複製代碼
  1. 爲何assign不能用於修飾對象

首先咱們須要明確,對象的內存通常被分配到堆上,基本數據類型和oc數據類型的內存通常被分配在棧上 若是用assign修飾對象,當對象被釋放後,指針的地址仍是存在的,也就是說指針並無被置爲nil,從而形成了野指針。由於對象是分配在堆上的,堆上的內存由程序員分配釋放。而由於指針沒有被置爲nil,若是後續的內存分配中,恰好分配到了這塊內存,就會形成崩潰 而assign修飾基本數據類型或oc數據類型,由於基本數據類型是分配在棧上的,由系統分配和釋放,因此不會形成野指針安全

  1. 請寫出如下代碼輸出
int a[5] = {1, 2, 3, 4, 5};
 int *ptr = (int *)(&a + 1);
 printf("%d, %d", *(a + 1), *(ptr + 1));
複製代碼

參考答案:2,隨機值 分析: a表明有5個元素的數組的首地址,a[5]的元素分別是1,2,3,4,5。接下來,a + 1表示數據首地址加1,那麼就是a[1],也就是對應於值爲2,可是,這裏是&a + 1,由於a表明的是整個數組,它的空間大小爲5 * sizeof(int),所以&a + 1就是a + 5a是個常量指針,指向當前數組的首地址,指針+1就是移動sizeof(int)個字節 所以,ptr是指向int *類型的指針,而ptr指向的就是a + 5,那麼ptr + 1也至關於a + 6,因此最後的*(ptr + 1)就是一個隨機值了。而*(ptr – 1)就至關於a + 4,對應的值就是5bash

  1. 一個view已經初始化完畢,view上面添加了n個button(可能使用循環建立),除用viewtag以外,還能夠採用什麼辦法來找到本身想要的button來修改Button的值

第一種:若是是點擊某個按鈕後,纔會刷新它的值,其它不用修改,那麼不用引用任何按鈕,直接在回調時,就已經將接收響應的按鈕給傳過來了,直接經過它修改便可 第二種:點擊某個按鈕後,全部與之同類型的按鈕都要修改值,那麼能夠經過在建立按鈕時將按鈕存入到數組中,在須要的時候遍歷查找併發

  1. UIViewControllerviewDidUnload、viewDidLoadloadView分別何時調用?UIViewdrawRectlayoutSubviews分別起什麼做用

第一個問題: 在控制器被銷燬前會調用viewDidUnloadMRC下才會調用) 在控制器沒有任何view時,會調用loadViewview加載完成時,會調用viewDidLoad 第二個問題: 在調用setNeedsDisplay後,會調用drawRect方法,咱們經過在此方法中能夠獲取到context(設置上下文),就能夠實現繪圖 在調用setNeedsLayout後,會調用layoutSubviews方法,咱們能夠經過在此方法去調整UI。固然能引發layoutSubviews調用的方式有不少種的,好比添加子視圖、滾動scrollview、修改視圖的frame異步

  1. 自動釋放池工做原理

自動釋放池是NSAutorelease類的一個實例,當向一個對象發送autorelease消息時,該對象會自動入池,待池銷燬時,將會向池中全部對象發送一條release消息,釋放對象 [pool release]、[pool drain]表示的是池自己不會銷燬,而是池子中的臨時對象都被髮送release,從而將對象銷燬async

  1. 蘋果是如何實現autoreleasepool

autoreleasepool是由AutoreleasePoolPage以雙向鏈表的方式實現的,主要經過下列三個函數完成:

  • objc_autoreleasePoolPush做爲自動釋放池做用域的第一個函數
  • 使用objc_autorelease將對象加入自動釋放池
  • objc_autoreleasePoolPop做爲自動釋放池做用域的最後一個函數
  1. autorelease的對象什麼時候被釋放

RunLoop在每一個事件循環結束後會去自動釋放池將全部自動釋放對象的引用計數減一,若引用計數變成了0,則會將對象真正銷燬掉,回收內存。 在沒有手動添加Autorelease Pool的狀況下,autorelease的對象是在每一個事件循環結束後,自動釋放池纔會對全部自動釋放的對象的引用計數減一,若引用計數變成了0,則釋放對象,回收內存。所以,若想要早一點釋放掉autorelease對象,那麼咱們能夠在對象外加一個自動釋放池。好比,在循環處理數據時,臨時變量要快速釋放,就應該採用這種方式:

// 經過alloc建立的對象,直接加入@autoreleasepool沒有做用,需在建立對象後面顯式添加autorelease
// 經過類方法建立的對象不須要顯式添加autorelease,緣由是類方法建立的對象系統會自動添加autorelease
for (int i = 0; i < 1000000; i++) {
 @autoreleasepool {
   NSString *str = @"Abc";
   str = [str lowercaseString];
   str = [str stringByAppendingString:@"xyz"];
   NSLog(@"%@", str);
 } // 出了這裏,就會去遍歷該自動釋放池了
}
複製代碼
  1. 簡述內存管理基本原則

OC內存管理遵循誰建立,誰釋放,誰引用,誰管理的機制,當使用alloc、copy(mutableCopy)或者retian一個對象時,你就有義務向它發送一條release或者autorelease消息釋放該對象,其餘方法建立的對象,不須要由你來管理內存,當對象引用計數爲0時,系統將釋放該對象,這是OC的手動管理機制(MRC) 向一個對象發送一條autorelease消息,這個對象並不會當即銷燬,而是將這個對象放入了自動釋放池,待池子釋放時,它會向池中每個對象發送一條release消息,以此來釋放對象 向一個對象發送release消息,並不意味着這個對象被銷燬了,而是當這個對象的引用計數爲0時,系統纔會調用dealloc方法釋放該對象和對象自己所擁有的實例

  1. sizeof關鍵字

sizeof是在編譯階段處理,且不能被編譯爲機器碼。sizeof的結果等於對象或類型所佔的內存字節數。sizeof的返回值類型爲size_t 變量:int a; sizeof(a)爲4; 指針:int *p; sizeof(p)爲4; 數組:int b[10]; sizeof(b)爲數組的大小4*10;int c[0]; sizeof(c)等於0 sizeof(void)等於1 sizeof(void *)等於4

  1. 什麼是離屏渲染?什麼狀況下會觸發?離屏渲染消耗性能的緣由

離屏渲染就是在當前屏幕緩衝區之外,新開闢一個緩衝區進行操做 離屏渲染觸發的場景有如下:

  • 圓角(同時設置layer.masksToBounds = YES、layer.cornerRadius大於0)
  • 圖層蒙版
  • 陰影,layer.shadowXXX,若是設置了layer.shadowPath就不會產生離屏渲染
  • 遮罩,layer.mask
  • 光柵化,layer.shouldRasterize = YES

離屏渲染消耗性能的緣由 須要建立新的緩衝區,離屏渲染的整個過程,須要屢次切換上下文環境,先是從當前屏幕(On-Screen)切換到離屏(Off-Screen)等到離屏渲染結束之後,將離屏緩衝區的渲染結果顯示到屏幕上,又須要將上下文環境從離屏切換到當前屏幕

  1. ARC 下,不顯式指定任何屬性關鍵字時,默認的關鍵字都有哪些

基本數據類型默認關鍵字是:atomic, readwrite, assign 普通Objective-C對象默認關鍵字是:atomic, readwrite, strong

  1. OC中的類方法和實例方法有什麼本質區別和聯繫

類方法:

  • 類方法是屬於類對象的
  • 類方法只能經過類對象調用
  • 類方法中的 self 是類對象
  • 類方法能夠調用其餘的類方法
  • 類方法中不能訪問成員變量
  • 類方法中不能直接調用對象方法

實例方法:

  • 實例方法是屬於實例對象的
  • 實例方法只能經過實例對象調用
  • 實例方法中的 self 是實例對象
  • 實例方法中能夠訪問成員變量
  • 實例方法中直接調用實例方法
  • 實例方法中也能夠調用類方法(經過類名)
  1. 可否向編譯後獲得的類中增長實例變量?可否向運行時建立的類中添加實例變量?爲何?
  • 不能向編譯後獲得的類中增長實例變量
  • 能向運行時建立的類中添加實例變量
  • 由於編譯後的類已經註冊在runtime中,類結構體中的objc_ivar_list實例變量的鏈表和instance_size實例變量的內存大小已經肯定,同時runtime會調用class_setIvarLayoutclass_setWeakIvarLayout來處理strong weak引用,因此不能向存在的類中添加實例變量 運行時建立的類是能夠添加實例變量,調用class_addIvar函數。可是得在調用objc_allocateClassPair以後,objc_registerClassPair以前,緣由同上
  1. runtime如何經過selector找到對應的IMP地址(分別考慮實例方法和類方法)Selector、Method 和 IMP的有什麼區別與聯繫

對於實例方法,每一個實例的isa指針指向着對應類對象,而每個類對象中都有一個對象方法列表。對於類方法,每一個類對象的isa指針都指向着對應的元類對象,而每個元類對象中都有一個類方法列表。方法列表中記錄着方法的名稱,方法實現,以及參數類型,其實selector本質就是方法名稱,經過這個方法名稱就能夠在方法列表中找到對應的方法實現 Selector、Method 和 IMP的關係能夠這樣描述:在運行期分發消息,方法列表中的每個實體都是一個方法(Method)它的名字叫作選擇器(SEL)對應着一種方法實現(IMP

  1. objc_msgSend、_objc_msgForward都是作什麼的?OC 中的消息調用流程是怎樣的
  • objc_msgSend是用來作消息發送的。在OC中,對方法的調用都會被轉換成內部的消息發送執行
  • _objc_msgForwardIMP類型(函數指針)用於消息轉發的:當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward會嘗試作消息轉發
  • 在消息調用的過程當中,objc_msgSend的動做比較清晰:首先在Class中的緩存查找IMP(沒緩存則初始化緩存)若是沒找到,則向父類的Class查找。若是一直查找到根類仍舊沒有實現,則用_objc_msgForward函數指針代替IMP。最後,執行這個IMP。當調用一個NSObject對象不存在的方法時,並不會立刻拋出異常,而是會通過多層轉發,層層調用對象的-resolveInstanceMethod:、-forwardingTargetForSelector:、-methodSignatureForSelector:、-forwardInvocation:等方法。其中最後-forwardInvocation:是會有一個NSInvocation對象,這個NSInvocation對象保存了這個方法調用的全部信息,包括Selector名,參數和返回值類型,能夠從這個NSInvocation對象裏拿到調用的全部參數值
  1. class方法和objc_getClass方法有什麼區別

object_getClass(obj)返回的是obj中的isa指針,即指向類對象的指針;而[obj class]則分兩種狀況:一是當obj爲實例對象時,[obj class]class是實例方法,返回的是obj對象中的isa指針;二是當obj爲類對象(包括元類和根類以及根元類)時,調用的是類方法,返回的結果爲其自己

  1. OC中向一個nil對象發送消息將會發生什麼

OC中向nil發送消息是徹底有效的,只是在運行時不會有任何做用;向一個nil對象發送消息,首先在尋找對象的isa指針時就是0地址返回了,因此不會出現任何錯誤,也不會崩潰

  1. _objc_msgForward函數是作什麼的?直接調用它將會發生什麼

_objc_msgForward是一個函數指針(和IMP的類型同樣)用於消息轉發;當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward會嘗試作消息轉發 objc_msgSend消息傳遞中的做用。在消息傳遞過程當中,objc_msgSend的動做比較清晰:首先在Class中的緩存查找IMP沒有緩存則初始化緩存)若是沒找到,則向父類的Class查找。若是一直查找到根類仍舊沒有實現,則用_objc_msgForward函數指針代替IMP,最後執行這個IMP 一旦調用了_objc_msgForward,將跳過查找IMP的過程,直接觸發消息轉發,若是調用了_objc_msgForward,即便這個對象確實已經實現了這個方法,你也會告訴objc_msgSend,我沒有在這個對象裏找到這個方法的實現,若是用很差會直接致使程序Crash

  1. 何時會報unrecognized selector的異常
  • 當調用該對象上某個方法,而該對象上沒有實現這個方法的時候。能夠經過消息轉發進行解決,流程見下圖
  • OC在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,而後在該類中的方法列表以及其父類方法列表中尋找方法運行,若是在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉並拋出異常unrecognized selector sent to XXX 可是在這以前,OC的運行時會給出三次拯救程序崩潰的機會
  • Method resolution(消息動態解析) OC運行時會調用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機會提供一個函數實現。若是你添加了函數,那運行時系統就會從新啓動一次消息發送的過程,不然,運行時就會移到下一步,消息轉發(Message Forwarding
// 重寫 resolveInstanceMethod: 添加對象方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    // 若是是執行 run 函數,就動態解析,指定新的 IMP
    if (sel == NSSelectorFromString(@"run:")) {
        // class: 給哪一個類添加方法
        // SEL: 添加哪一個方法
        // IMP: 方法實現 => 函數 => 函數入口 => 函數名
        // type: 方法類型:void用v來表示,id參數用@來表示,SEL用:來表示
        class_addMethod(self, sel, (IMP)runMethod, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

//新的 run 函數
void runMethod(id self, SEL _cmd, NSNumber *meter) {
    NSLog(@"跑了%@", meter);
}
複製代碼
  • Fast forwarding(消息接受者重定向) 若是目標對象實現了-forwardingTargetForSelector:Runtime這時就會調用這個方法,給你把這個消息轉發給其餘對象的機會。只要這個方法返回的不是nilself,整個消息發送的過程就會被重啓,固然發送的對象會變成你返回的那個對象。不然,就會繼續Normal Fowarding。 這裏叫Fast,只是爲了區別下一步的轉發機制。由於這一步不會建立任何新的對象,但下一步轉發會建立一個NSInvocation對象,因此相對更快點
// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(run:)) {
        return [[Person alloc] init];
        // 返回 Person 對象,讓 Person 對象接收這個消息
    }
    return [super forwardingTargetForSelector:aSelector];
}
複製代碼
  • Normal forwarding(消息重定向) 這一步是Runtime最後一次給你挽救的機會。首先它會發送-methodSignatureForSelector:消息得到函數的參數和返回值類型。若是-methodSignatureForSelector:返回nilRuntime則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。若是返回了一個函數簽名,Runtime就會建立一個NSInvocation對象併發送-forwardInvocation:消息給目標對象
// 獲取函數的參數和返回值類型,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 從 anInvocation 中獲取消息
    SEL sel = anInvocation.selector;
    if (sel == NSSelectorFromString(@"run:")) {
        // 1. 指定當前類的一個方法做爲IMP
        // anInvocation.selector = @selector(readBook:);
        // [anInvocation invoke];
        
        // 2. 指定其餘類來執行這個IMP
        Person *p = [[Person alloc] init];
        // 判斷 Person 對象方法是否能夠響應 sel
        if([p respondsToSelector:sel]) {
            // 若能夠響應,則將消息轉發給其餘對象處理
            [anInvocation invokeWithTarget:p];
        } else {
            // 若仍然沒法響應,則報錯:找不到響應方法
            [self doesNotRecognizeSelector:sel];
        }
    }else{
        [super forwardInvocation:anInvocation];
    }
}

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    [super doesNotRecognizeSelector:aSelector];
}
複製代碼

既然-forwardingTargetForSelector:-forwardInvocation:均可以將消息轉發給其餘對象處理,那麼二者的區別在哪? 區別就在於-forwardingTargetForSelector:只能將消息轉發給一個對象。而-forwardInvocation:能夠把消息存儲,在你以爲合適的時機轉發出去,或者不處理這個消息。修改消息的target,selector,參數等。將消息轉發給多個對象

  1. iOS layoutSubviews何時會被調用
  • init方法不會調用layoutSubviews,可是是用initWithFrame進行初始化時,當rect的值不爲CGRectZero時,會觸發
  • addSubview會觸發layoutSubviews方法
  • setFrame只有當設置的frame的參數的size與原來的size不一樣,纔會觸發其viewlayoutSubviews方法
  • 滑動UIScrollView會調用scrollviewscrollview上的viewlayoutSubviews方法
  • 旋轉設備只會調用VCviewlayoutSubviews方法
  • 直接調用[self setNeedsLayout];(這個在上面蘋果官方文檔裏有說明) -layoutSubviews方法:這個方法默認沒有作任何事情,須要子類進行重寫 -setNeedsLayout方法:標記爲須要從新佈局,異步調用layoutIfNeeded刷新佈局,不當即刷新,但layoutSubviews必定會被調用 -layoutIfNeeded方法:若是有須要刷新的標記,當即調用layoutSubviews進行佈局(若是沒有標記,不會調用layoutSubviews) 若是要當即刷新,要先調用[view setNeedsLayout],把標記設爲須要佈局,而後立刻調用[view layoutIfNeeded],實現佈局 在視圖第一次顯示以前,標記老是須要刷新的,能夠直接調用[view layoutIfNeeded]
  1. 下面代碼會發生什麼問題
@property (nonatomic, strong) NSString *str;

dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
    dispatch_async(queue, ^{
        self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];
    });
}
複製代碼

crash。由於在並行隊列DISPATCH_QUEUE_CONCURRENT中異步dispatch_asyncstr屬性進行賦值,就會致使str已經被release了,還會執行release。這就是向已釋放內存的對象發送消息而發生crash 詳細解析:對str屬性strong修飾進行賦值,至關與MRC中的

- (void)setStr:(NSString *)str{
    if (str == _str) return;
    id pre = _str;
    [str retain];//1.先保留新值
    _str = str;//2.再進行賦值
    [pre release];//3.釋放舊值
}
複製代碼

那麼假如併發隊列裏調度的線程A執行到步驟1,還沒到步驟2時,線程B執行到步驟3,那麼當線程A再執行步驟3時,舊值就會被過分釋放,致使向已釋放內存的對象發送消息而崩潰

  • 追問:怎麼修改這段代碼變爲不崩潰呢

一、使用串行隊列 將set方法改爲在串行隊列中執行就行,這樣即便異步,但全部block操做追加在隊列最後依次執行 二、使用atomic atomic關鍵字至關於在setter方法加鎖,這樣每次執行setter都是線程安全的,但這只是單獨針對setter方法而言的狹義的線程安全 三、使用weak關鍵字 weaksetter沒有保留新值的操做,因此不會引起重複釋放。固然這個時候要看具體狀況可否使用weak,可能值並非所須要的值 四、使用互斥鎖,保證數據訪問的惟一性@synchronized (self) {self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];} 五、使用Tagged Pointer Tagged Pointer是蘋果在64位系統引入的內存技術。簡單來講就是對於NSString(內存小於60位的字符串)或NSNumber(小於2^31),64位的指針有8個字節,徹底能夠直接用這個空間來直接表示值,這樣的話其實會將NSStringNSNumber對象由一個指針轉換成一個值類型,而值類型的setter和getter又是原子的,從而線程安全

  • 發散:下面代碼會crash
@property (nonatomic, strong) NSString *str;

dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
    dispatch_async(queue, ^{
        // 相比上面,僅字符串變短了
        self.str = [NSString stringWithFormat:@"%d",i];
        NSLog(@"%d, %s, %p", i, object_getClassName(self.str), self.str);
    });
}
複製代碼

不會crash。並且發現str這個字符串類型是NSTaggedPointerString Tagged Pointer是一個可以提高性能、節省內存的有趣的技術 Tagged Pointer專門用來存儲小的對象,例如NSNumberNSDate(後來能夠存儲小字符串) Tagged Pointer指針的值再也不是地址了,而是真正的值。因此,實際上它再也不是一個對象了,它只是一個披着對象皮的普通變量而已 它的內存並不存儲在中,也不須要malloc和free,因此擁有極快的讀取和建立速度

附:個人博客地址

相關文章
相關標籤/搜索