面試成功源於基礎和經驗(iOS)

SLPA發表於2020-12-10

UIView和CALayer是什麼關係

1
2
3
4
5
6
7
8
9
* UIView繼承自UIResponder類,可以響應事件
 
* CALayer直接繼承自NSObject類,不可以響應事件
 
* UIView是CALayer的delegate(CALayerDelegate)
 
* UIView主要處理事件,CALayer負責繪製
 
* 每個UIView內部都有一個CALayer在背後提供內容的繪製和顯示,並且UIView的尺寸樣式都由內部的Layer所提供。兩者都有樹狀層級結構,Layer內部有SubLayers,View內部有SubViews,但是Layer比View多了個AnchorPoint
  1. NSCache和NSMutableDictionary的相同點與區別

    相同點
    NSCache和NSMutableDictionary功能用法基本是相同的
    區別
    NSCache是執行緒安全的,NSMutableDictionary執行緒不安全,Mutable開發的類一般都是執行緒不安全的
    當記憶體不足時NSCache會自動釋放記憶體(所以從快取中取資料的時候總要判斷是否為空)
    NSCache可以指定快取的限額,當快取超出限額自動釋放記憶體
    NSCache的Key只是對物件進行了Strong引用,而非複製,所以不需要實現NSCopying協議

  2. atomic的實現機制;為什麼不能保證絕對的執行緒安全(最好可以結合場景來說)
    • atomic會對屬性的setter/getter方法進行加鎖,這僅僅只能保證在操作setter/getter方法是安全的。不能保證其他執行緒的安全

    • 例如:執行緒1呼叫了某一屬性的setter方法並進行到了一半,執行緒2呼叫其getter方法,那麼會執行完setter操作後,再執行getter操作,執行緒2會獲取到執行緒1setter後的完整的值;當幾個執行緒同時呼叫同一屬性的setter、getter方法時,會獲取到一個完整的值,但獲取到的值不可控

  3. iOS 中內省的幾個方法

    物件在執行時獲取其型別的能力稱為內省。內省可以有多種方法實現
    OC執行時內省的4個方法:

  • 判斷物件型別:
1
2
-(BOOL) isKindOfClass:             // 判斷是否是這個類或者這個類的子類的例項
-(BOOL) isMemberOfClass:       // 判斷是否是這個類的例項
  • 判斷物件/類是否有這個方法
1
2
-(BOOL) respondsToSelector:                       // 判斷例項是否有這樣方法
+(BOOL) instancesRespondToSelector:       // 判斷類是否有這個方法

5. objc在向一個物件傳送訊息時,發生了什麼
根據物件的isa指標找到該物件所屬的類,去objc的對應的類中找方法
1.首先,在相應操作的物件中的快取方法列表中找呼叫的方法,如果找到,轉向相應實現並執行
2.如果沒找到,在相應操作的物件中的方法列表中找呼叫的方法,如果找到,轉向相應實現執行
3.如果沒找到,去父類指標所指向的物件中執行1,2.
4.以此類推,如果一直到根類還沒找到,轉向攔截呼叫,走訊息轉發機制
5.如果沒有重寫攔截呼叫的方法,程式報錯

  1. 你是否接觸過OC中的反射機制?簡單聊一下概念和使用
  • class反射

  • 透過類名的字串形式例項化物件

1
2
Class  class = NSClassFromString(@ "student" );
Student *stu = [[ class alloc] init];
  • 將類名變為字串

    1
    2
    Class  class = [Student  class ];
    NSString *className = NSStringFromClass( class );
  • SEL的反射

  • 透過方法的字串形式例項化方法

1
2
SEL selector = NSSelectorFromString(@ "setName" );
[stu performSelector:selector withObject:@ "Mike" ];
  • 將方法變成字串

NSStringFromSelector(@selector(setName:));

  1. 這個寫***出什麼問題@property (nonatomic, copy) NSMutableArray *arr;

    新增,刪除,修改陣列內元素的時候,程式會因為找不到對應的方法而崩潰。原因:是因為copy就是複製一個不可變NSArray的物件,不能對NSArray物件進行新增/修改

  2. 如何讓自己的類用copy修飾符

    若想令自己所寫的物件具有複製功能,則需實現NSCopying協議。如果自定義的物件分為可變版本與不可變版本,那麼就要同時實現NSCopying與NSMutableCopying協議。
    具體步驟:
    1.需宣告該類遵從NSCopying協議
    2.實現NSCopying協議的方法,具體區別

  • NSCopying協議方法為:
1
2
3
4
5
- (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資料型別,因為基本資料型別是分配在棧上的,由系統分配和釋放,所以不會造成野指標

  2. 請寫出以下程式碼輸出

    1
    2
    3
    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 + 5。a是個常量指標,指向當前陣列的首地址,指標+1就是移動sizeof(int)個位元組
    因此,ptr是指向int  型別的指標,而ptr指向的就是a + 5,那麼ptr + 1也相當於a + 6,所以最後的(ptr + 1)就是一個隨機值了。而*(ptr – 1)就相當於a + 4,對應的值就是5

  3. 一個view已經初始化完畢,view上面新增了n個button(可能使用迴圈建立),除用view的tag之外,還可以採用什麼辦法來找到自己想要的button來修改Button的值

    第一種:如果是點選某個按鈕後,才會重新整理它的值,其它不用修改,那麼不用引用任何按鈕,直接在回撥時,就已經將接收響應的按鈕給傳過來了,直接透過它修改即可
    第二種:點選某個按鈕後,所有與之同型別的按鈕都要修改值,那麼可以透過在建立按鈕時將按鈕存入到陣列中,在需要的時候遍歷查詢

  4. UIViewController的viewDidUnload、viewDidLoad和loadView分別什麼時候呼叫?UIView的drawRect和layoutSubviews分別起什麼作用

    第一個問題:
    在控制器被銷燬前會呼叫 viewDidUnloadMRC下才會呼叫)
    在控制器沒有任何 view時,會呼叫 loadView
    view載入完成時,會呼叫 viewDidLoad
    第二個問題:
    在呼叫 setNeedsDisplay後,會呼叫 drawRect方法,我們透過在此方法中可以獲取到 context(設定上下文),就可以實現繪圖
    在呼叫 setNeedsLayout後,會呼叫 layoutSubviews方法,我們可以透過在此方法去調整UI。當然能引起 layoutSubviews呼叫的方式有很多種的,比如新增子檢視、滾動 scrollview、修改檢視的 frame

  5. 自動釋放池工作原理

    自動釋放池是 NSAutorelease類的一個例項,當向一個物件傳送 autorelease訊息時,該物件會自動入池,待池銷燬時,將會向池中所有物件傳送一條 release訊息,釋放物件
    [pool release]、[pool drain]表示的是池本身不會銷燬,而是池子中的臨時物件都被髮送 release,從而將物件銷燬

  6. 蘋果是如何實現autoreleasepool的

    autoreleasepool是由 AutoreleasePoolPage以雙向連結串列的方式實現的,主要透過下列三個函式完成:

    • objc_autoreleasePoolPush作為自動釋放池作用域的第一個函式
    • 使用 objc_autorelease將物件加入自動釋放池
    • objc_autoreleasePoolPop作為自動釋放池作用域的最後一個函式
  7. autorelease的物件何時被釋放
    RunLoop在每個事件迴圈結束後會去自動釋放池將所有自動釋放物件的引用計數減一,若引用計數變成了0,則會將物件真正銷燬掉,回收記憶體。
    在沒有手動新增Autorelease Pool的情況下,autorelease的物件是在每個事件迴圈結束後,自動釋放池才會對所有自動釋放的物件的引用計數減一,若引用計數變成了0,則釋放物件,回收記憶體。因此,若想要早一點釋放掉autorelease物件,那麼我們可以在物件外加一個自動釋放池。比如,在迴圈處理資料時,臨時變數要快速釋放,就應該採用這種方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 透過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方法釋放該物件和物件本身所擁有的例項

  2. 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

  3. 什麼是離屏渲染?什麼情況下會觸發?離屏渲染消耗效能的原因

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

    • 圓角(同時設定 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

  2. OC中的類方法和例項方法有什麼本質區別和聯絡

    類方法:

    • 類方法是屬於類物件的
    • 類方法只能透過類物件呼叫
    • 類方法中的 self 是類物件
    • 類方法可以呼叫其他的類方法
    • 類方法中不能訪問成員變數
    • 類方法中不能直接呼叫物件方法
      例項方法:
    • 例項方法是屬於例項物件的
    • 例項方法只能透過例項物件呼叫
    • 例項方法中的 self 是例項物件
    • 例項方法中可以訪問成員變數
    • 例項方法中直接呼叫例項方法
    • 例項方法中也可以呼叫類方法(透過類名)
  3. 能否向編譯後得到的類中增加例項變數?能否向執行時建立的類中新增例項變數?為什麼?
    • 不能向編譯後得到的類中增加例項變數
    • 能向執行時建立的類中新增例項變數
    • 因為編譯後的類已經註冊在 runtime中,類結構體中的 objc_ivar_list例項變數的連結串列和 instance_size例項變數的記憶體大小已經確定,同時 runtime會呼叫 class_setIvarLayoutclass_setWeakIvarLayout來處理 strong weak引用,所以不能向存在的類中新增例項變數
      執行時建立的類是可以新增例項變數,呼叫 class_addIvar函式。但是得在呼叫 objc_allocateClassPair之後, objc_registerClassPair之前,原因同上
  4. runtime如何透過selector找到對應的IMP地址(分別考慮例項方法和類方法)Selector、Method 和 IMP的有什麼區別與聯絡

    對於例項方法,每個例項的 isa指標指向著對應類物件,而每一個類物件中都有一個物件方法列表。對於類方法,每個類物件的 isa指標都指向著對應的元類物件,而每一個元類物件中都有一個類方法列表。方法列表中記錄著方法的名稱,方法實現,以及引數型別,其實 selector本質就是方法名稱,透過這個方法名稱就可以在方法列表中找到對應的方法實現
    Selector、Method 和 IMP的關係可以這樣描述:在執行期分發訊息,方法列表中的每一個實體都是一個方法( Method)它的名字叫做選擇器( SEL)對應著一種方法實現( IMP

  5. 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物件裡拿到呼叫的所有引數值
  6. class方法和objc_getClass方法有什麼區別

    object_getClass(obj)返回的是 obj中的 isa指標,即指向類物件的指標;而 [obj class]則分兩種情況:一是當 obj為例項物件時, [obj class]class是例項方法,返回的是 obj物件中的 isa指標;二是當 obj為類物件(包括元類和根類以及根元類)時,呼叫的是類方法,返回的結果為其本身

  7. OC中向一個nil物件傳送訊息將會發生什麼

    OC中向 nil傳送訊息是完全有效的,只是在執行時不會有任何作用;向一個 nil物件傳送訊息,首先在尋找物件的 isa指標時就是 0地址返回了,所以不會出現任何錯誤,也不會崩潰

  8. _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

  9. 什麼時候會報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:")) {

    1
    2
    3
    4
    5
    6
    // 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);
}

1
2
* Fast forwarding(訊息接受者重定向)
如果目標物件實現了-forwardingTargetForSelector:,Runtime這時就會呼叫這個方法,給你把這個訊息轉發給其他物件的機會。只要這個方法返回的不是nil和self,整個訊息傳送的過程就會被重啟,當然傳送的物件會變成你返回的那個物件。否則,就會繼續Normal Fowarding。 這裡叫Fast,只是為了區別下一步的轉發機制。因為這一步不會建立任何新的物件,但下一步轉發會建立一個NSInvocation物件,所以相對更快點

// 訊息接受者重定向

  • (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(run:)) {

    1
    2
    return [[Person alloc] init];
    // 返回 Person 物件,讓 Person 物件接收這個訊息

    }
    return [super forwardingTargetForSelector:aSelector];
    }

  • Normal forwarding(訊息重定向)
    這一步是Runtime最後一次給你挽救的機會。首先它會傳送-methodSignatureForSelector:訊息獲得函式的引數和返回值型別。如果-methodSignatureForSelector:返回nil,Runtime則會發出-doesNotRecognizeSelector:訊息,程式這時也就掛掉了。如果返回了一個函式簽名,Runtime就會建立一個NSInvocation物件併傳送-forwardInvocation:訊息給目標物件
    ```
    // 獲取函式的引數和返回值型別,返回簽名

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) {

    1
    return [NSMethodSignature signatureWithObjCTypes: "v@:@" ];

    }
    return [super methodSignatureForSelector:aSelector];
    }

// 訊息重定向

  • (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 從 anInvocation 中獲取訊息
    SEL sel = anInvocation.selector;
    if (sel == NSSelectorFromString(@"run:")) {

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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{

    1
    [ super forwardInvocation:anInvocation];

    }
    }

  • (void)doesNotRecognizeSelector:(SEL)aSelector {
    [super doesNotRecognizeSelector:aSelector];
    }

    1

既然-forwardingTargetForSelector:和-forwardInvocation:都可以將訊息轉發給其他物件處理,那麼兩者的區別在哪?
區別就在於-forwardingTargetForSelector:只能將訊息轉發給一個物件。而-forwardInvocation:可以把訊息儲存,在你覺得合適的時機轉發出去,或者不處理這個訊息。修改訊息的target,selector,引數等。將訊息轉發給多個物件

  1. iOS layoutSubviews什麼時候會被呼叫
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    > *   `init`方法不會呼叫`layoutSubviews`,但是是用`initWithFrame`進行初始化時,當`rect`的值不為`CGRectZero`時,會觸發
    > *   `addSubview`會觸發`layoutSubviews`方法
    > *   `setFrame`只有當設定的`frame`的引數的`size`與原來的`size`不同,才會觸發其`view`的`layoutSubviews`方法
    > *   滑動`UIScrollView`會呼叫`scrollview`及`scrollview`上的`view`的`layoutSubviews`方法
    > *   旋轉裝置只會呼叫`VC`的`view`的`layoutSubviews`方法
    > *   直接呼叫`[self setNeedsLayout];`(這個在上面蘋果官方文件裡有說明)
    >     `-layoutSubviews`方法:這個方法預設沒有做任何事情,需要子類進行重寫
    >     `-setNeedsLayout`方法:標記為需要重新佈局,非同步呼叫`layoutIfNeeded`重新整理佈局,不立即重新整理,但`layoutSubviews`一定會被呼叫
    >     `-layoutIfNeeded`方法:如果有需要重新整理的標記,立即呼叫`layoutSubviews`進行佈局(如果沒有標記,不會呼叫`layoutSubviews`)
    >     如果要立即重新整理,要先呼叫`[view setNeedsLayout]`,把標記設為需要佈局,然後馬上呼叫`[view layoutIfNeeded]`,實現佈局
    >     在檢視第一次顯示之前,標記總是`需要重新整理`的,可以直接呼叫`[view layoutIfNeeded]`
  2. 下面程式碼會發生什麼問題
    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];
});
}

1
2
會crash。因為在並行佇列DISPATCH_QUEUE_CONCURRENT中非同步dispatch_async對str屬性進行賦值,就會導致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.釋放舊值
    }

    1
    那麼假如併發佇列裡排程的執行緒A執行到步驟 1 ,還沒到步驟 2 時,執行緒B執行到步驟 3 ,那麼當執行緒A再執行步驟 3 時,舊值就會被過度釋放,導致向已釋放記憶體的物件傳送訊息而崩潰
  • 追問:怎麼修改這段程式碼變為不崩潰呢

    1、使用序列佇列
    將set方法改成在序列佇列中執行就行,這樣即使非同步,但所有block操作追加在佇列最後依次執行
    2、使用atomic
    atomic關鍵字相當於在setter方法加鎖,這樣每次執行setter都是執行緒安全的,但這只是單獨針對setter方法而言的狹義的執行緒安全
    3、使用weak關鍵字
    weak的setter沒有保留新值的操作,所以不會引發重複釋放。當然這個時候要看具體情況能否使用weak,可能值並不是所需要的值
    4、使用互斥鎖,保證資料訪問的唯一性@synchronized (self) {self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];}
    5、使用Tagged Pointer
    Tagged Pointer是蘋果在64位系統引入的記憶體技術。簡單來說就是對於NSString(記憶體小於60位的字串)或NSNumber(小於2^31),64位的指標有8個位元組,完全可以直接用這個空間來直接表示值,這樣的話其實會將NSString和NSNumber物件由一個指標轉換成一個值型別,而值型別的setter和getter又是原子的,從而執行緒安全

  • 發散:下面程式碼會crash嗎

    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:@"%d",i];
NSLog(@"%d, %s, %p", i, object_getClassName(self.str), self.str);
});
}

```
不會crash。而且發現str這個字串型別是NSTaggedPointerString
Tagged Pointer是一個能夠提升效能、節省記憶體的有趣的技術
Tagged Pointer專門用來儲存小的物件,例如NSNumber和NSDate(後來可以儲存小字串)
Tagged Pointer指標的值不再是地址了,而是真正的值。所以,實際上它不再是一個物件了,它只是一個披著物件皮的普通變數而已
它的記憶體並不儲存在堆中,也不需要malloc和free,所以擁有極快的讀取和建立速度

推薦

面試合集

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990324/viewspace-2740995/,如需轉載,請註明出處,否則將追究法律責任。

相關文章