iOS -- 經典面試題

1. Runtime 是什麼?

Runtime是一套有CC++和彙編混合編寫的API,爲OC加入了面向對象以及運行時的功能。

運行時是指將數據類型的確定有編譯時,推遲到了運行時。

比如:在編譯時,只讀取macho中的數據到ro,而真正方法的讀取是在rw中體現的,編譯好的ro是無法修改的,可以通過運行時API給編譯好的類可以通過運行時添加方法屬性

可以通過Runtime API,可以動態的添加屬性,交換方法,調用底層發送消息

2. 方法的本質是什麼?SEL是什麼?IMP是什麼?兩者之間的關係是什麼?

方法的本質是發送消息,發送消息會有一下幾個流程

  1. 快速查找(objc_msgSend),從cache_t中查找是否有緩存的IMP
  2. 慢速查找,遞歸自己然後父類,lookUpImpOrForward
  3. 查找不到消息開始動態方法解析,resolveInstanceMethod
  4. 消息快速轉發流程,forwardingTargetForSelector
  5. 消息慢速轉發流程,methodSignatureForSelector & forwardInvocation

SEL是方法編號,在read_iamges期間,就被編譯進了內存中的相關表中
IMP就是我們函數實現的指針,找IMP,就是找函數實現的過程。

就比如:sel相當於書本的目錄的標題,IMP就相當於書本的頁碼,我們首先知道我們想看什麼,即SEL,然後根據目錄找到對應的頁碼IMP,然後翻到具體內容的一個過程。

3. 能否向編譯後的得到的類中增加實例變量?能否向運行時創建的類添加實例變量?

  • 不能向編譯後得到的類中添加實例變量,因爲我們編譯好的實例變量存儲在ro中,一旦編譯完成,就無法修改。
  • 運行時創建的類還沒註冊到內存,可以添加實例變量。在動態創建的類添加屬性的時候,系統不會生成settergetter,要手動添加。
  • 編譯後的得到的類,我們也要通過類拓展添加實例變量關聯對象,類拓展在編譯的時候做爲類的一部分直接編譯到ro中的,
  • 也通過分類添加實例變量,需要用關聯對象,本質是實例變量的值在關聯哈希表中的存儲和讀取。

4. isKindOfClass 和 isMemberOfClass 的區別

下面代碼怎麼打印:

        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
        
        打印結果:1 0 0 0     1 1 1 1

經典isa走位圖:

isKindOfClass有一個繼承遞歸父類的過程,有更多的容錯

isMemberOfClass 直接對比元類

5. [self class] 和 [super class] 的區別

[self class]本質就是發送消息objc_msgSend,消息接受者是 self, 方法編號:class

[super class]本質是objc_msgSendSuper,消息的接收者還是self
方法編號:class 。只是objc_msgSendSuper 會更快 直接跳過 self 的查找

6. weak 原理,weak 如何實現,爲什麼可以自動置爲 nil

通過彙編查看,當用__weak去修飾一個對象的時候,底層會調用下面的方法:

id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

查看storeWeak方法

static id 
storeWeak(id *location, objc_object *newObj)
{
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // ✅Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // ✅Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

在這個方法中,看到底層維護了一張散列表SideTable,從SideTable中找到維繫的一張weak_table,然後判斷新值舊值

  • 判斷有舊值,直接進入weak_unregister_no_lock,最終在weak_resize方法中
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    size_t old_size = TABLE_SIZE(weak_table);

    weak_entry_t *old_entries = weak_table->weak_entries;
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));

    weak_table->mask = new_size - 1;
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    weak_table->num_entries = 0;  // restored by weak_entry_insert below
    
    if (old_entries) {
        weak_entry_t *entry;
        weak_entry_t *end = old_entries + old_size;
        for (entry = old_entries; entry < end; entry++) {
            if (entry->referent) {
                //✅ entry加入到我們的weak_table
                weak_entry_insert(weak_table, entry);
            }
        }
        free(old_entries);
    }
}

通過weak_entry_insert(weak_table, entry)將修飾的對象,插入到eak_table中。

  • 判斷有新值,進入weak_register_no_lock
/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    weak_entry_t *entry;
    // ✅根據referent 找到 entyry
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        // ✅創建了這個數組 - 插入weak_table
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

通過referentweak_table中,找到entry,判斷entry
是否存在(entry = weak_entry_for_referent(weak_table, referent))),存在append_referrer,將其添加到entry中,不存在,則創建一個weak_entry_t,然後四分之三擴容,然後插入到new_entry中(weak_entry_insert(weak_table, &new_entry))。

總結:

__weak底層維繫一張散列表SideTableSideTable中維繫一張弱引用表weak_table,在這張弱引用表中,有很多弱引用對象的實體weak_entry_t *entry

__weak就是根據傳進來的弱引用對象,去weak_table中找到對應的實體weak_entry_t *entry,然後檢查是否需要擴容(3/4擴容),然後拼接進行內部持有(new_referrers[i] = entry->inline_referrers[i])的過程,如果這個entry不存在,則創建一下新的實體entry,然後擴容,再插入weak_entry_insert

那麼,__weak爲什麼可以自動置爲 nil

查看dealloc源碼,最終在objc_destructInstance方法中,如下源碼:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        // ✅關聯對象表
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        // ✅移除關聯對象表
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

進入obj->clearDeallocating()方法

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

clearDeallocating經過判斷,最終都會進入到weak_clear_no_lock方法中,

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

weak_clear_no_lock中直接將關聯對象的指針referrer置爲nil,所以當其釋放時,會自動設置爲空。

7. __strong 分析

__weak一樣,先通過彙編分析,找到底層調用的objc_storeStrong方法,如下:

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

objc_retain(obj),在objc_release(prev),而objc_retain方法和objc_release方法底層都是發送retainrelease消息。

8. 對 數組 越界與什麼好的處理方法?(Method Swizzling 的使用)

在處理數組越界的時候,我們很容易想到的就是通過Runtime進行方法交換。

我們通常會創建一個分類,如下:

#import "NSArray+LG.h"
#import "LGRuntimeTool.h"
#import <objc/runtime.h>

@implementation NSArray (LG)

+ (void)load{
    
    [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(lg_objectAtIndex:)];
    [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(lg_objectAtIndexedSubscript:)];

}

// 交換的方法

- (id)lg_objectAtIndex:(NSUInteger)index{
    if (index > self.count-1) {
        NSLog(@"數組越界 -- ");
        NSLog(@"取值越界了,記錄:%lu > %lu", (unsigned long)index, self.count - 1);
        return nil;
    }
    return [self lg_objectAtIndex:index];
}


- (id)lg_objectAtIndexedSubscript:(NSUInteger)index {
    if (index > self.count-1) {
        NSLog(@"數組越界 -- ");
        NSLog(@"取值越界了,記錄:%lu > %lu", (unsigned long)index, self.count - 1);
        return nil;
    }
    return [self lg_objectAtIndexedSubscript:index];
}


#import "LGRuntimeTool.h"
#import <objc/runtime.h>

@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"傳入的交換類不能爲空");

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    method_exchangeImplementations(oriMethod, swiMethod);
}

@end

objectAtIndexlg_objectAtIndex進行交換,當調用系統objectAtIndex方法時,調用lg_objectAtIndex方法,方便我們在數組越界時的一些處理(比如防崩潰)

那麼假如我們在添加了NSArray的分類之後,在某個調用的地方,不小心調用了,NSArrayload方法,比如下面的代碼,會發生什麼呢?

    self.dataArray = @[@"Hank",@"Cooci",@"Kody",@"CC"];
    [NSArray load];
    NSLog(@"%@",[self.dataArray objectAtIndex:4]);

通過驗證,上面的代碼,會崩潰,當再次調用load方法時,會再次交換方法,調用系統的objectAtIndex方法,而導致崩潰。

所以,我們要在添加分類的load方法中,使用單例,防止方法多次重複交換

+ (void)load{
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(lg_objectAtIndex:)];
        [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(lg_objectAtIndexedSubscript:)];
    });

}

那麼在日常的開發中,經常出現繼承關係,而在這個繼承關係中,經常出現子類繼承父類的方法,那麼在子類中交換繼承自父類的方法會出現什麼情況呢?如下代碼:

// 父類
@interface LGPerson : NSObject
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

#import "LGPerson.h"

@implementation LGPerson
- (void)personInstanceMethod{
    NSLog(@"person對象方法:%s",__func__);
}
+ (void)personClassMethod{
    NSLog(@"person類方法:%s",__func__);
}
@end

// 子類
@interface LGStudent : LGPerson

@end

#import "LGStudent.h"

@implementation LGStudent

@end

上面代碼中LGStudent繼承自LGPerson,而LGPerson中實現了personInstanceMethodpersonClassMethod兩個方法,

在子類LGStudent中,對personInstanceMethod方法進行方法交換,如下:

@implementation LGStudent (LG)

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}

- (void)lg_studentInstanceMethod{
    [self lg_studentInstanceMethod];
    NSLog(@"LGStudent分類添加的lg對象方法:%s",__func__);
}

@end

+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"傳入的交換類不能爲空");

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    method_exchangeImplementations(oriMethod, swiMethod);
}

這樣的方法交換,在我們調用LGStudent的時候,完美的實現了方法互換,那麼在我們調用父類LGPerson時,會有什麼問題呢?調用如下:

    LGStudent *s = [[LGStudent alloc] init];
    [s personInstanceMethod];
    
    LGPerson *p = [[LGPerson alloc] init];
    [p personInstanceMethod];

結果如下:

在運行結果中看到,在父類調用personInstanceMethod時,出現了父類調用lg_studentInstanceMethod,而父類本身沒有這個方法,所以就崩潰了。

那麼,子類在交換繼承自父類而自己本身沒有重寫的方法時,應該怎麼做呢 ?

其實,我們可以在方法交換的時候做一個判斷,先嚐試添加一個方法,

+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"傳入的交換類不能爲空");
    // oriSEL       personInstanceMethod
    // swizzledSEL  lg_studentInstanceMethod
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
   
    // 嘗試添加
    // ✅ 首先第一步:會先嚐試給自己添加要交換的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
    BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));

    /**
     personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
     lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
     */

    //oriSEL:personInstanceMethod
    if (success) {// 自己沒有 - 交換 - 沒有父類進行處理 (重寫一個)
        // ✅ 然後再將父類的IMP給swizzle  personInstanceMethod(imp) -> swizzledSEL
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{ // 自己有
        method_exchangeImplementations(oriMethod, swiMethod);
    }

}

這樣就可以完美的對子類的方法進行交換了,調用結果如下,

那麼,還有一個問題,假如某人交換了只有聲明並沒有實現的方法,上面的方式就會出現死循環,因爲根本沒有父類方法的IMP,所以,還要對其進行改造。

代碼如下:

+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"傳入的交換類不能爲空");
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!oriMethod) {
        // 在oriMethod爲nil時,替換後將swizzledSEL複製一個不做任何事的空實現,代碼如下:
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }
    
    // 一般交換方法: 交換自己有的方法 -- 走下面 因爲自己有意味添加方法失敗
    // 交換自己沒有實現的方法:
    //   首先第一步:會先嚐試給自己添加要交換的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
    //   然後再將父類的IMP給swizzle  personInstanceMethod(imp) -> swizzledSEL
    //oriSEL:personInstanceMethod

    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    
}

在方法交換的時候,先對被交換方法進行判斷,判斷這個方法是否實現,當方法未實現時,我們手動添加一個空的方法實現,在這個空的方法實現中,我們可以做一些上傳等操作,來記錄收集crash

9. 內存偏移問題

  • 如下代碼,能否正常運行?
@interface LGPerson : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *subject;
@property (nonatomic)int age;

- (void)saySomething;
@end

@implementation LGPerson
- (void)saySomething{
    NSLog(@"NB %s ",__func__);
}

@end

#import "ViewController.h"
#import <objc/runtime.h>
#import "LGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // NSString *tem = @"KC";
    id pcls = [LGPerson class];
    void *pp= &pcls;
    [(__bridge id)pp saySomething];
    
    // p -> LGPerson 實例對象
    LGPerson *p = [LGPerson alloc];
    [p saySomething];
}


運行打印結果如下,那麼爲什麼會這樣呢?

首先,指針p指向的是LGPerson的實例對象的存儲空間,而指針pcls指向的是LGPerson類對象的存儲空間。pp指向的是指針pcls的空間,而實例對象中的isa也指向指針pcls,所以pp能夠正常調用saySomething方法

  • saySomething方法進行修改,如下,並打印self.name
- (void)saySomething{
    NSLog(@"NB %s - %@",__func__,self.name);
}

通過調試,打印結果如下:

那麼爲什麼會打印<ViewController: 0x105105e10>呢,接下來我們把上面的// NSString *tem = @"KC"註釋打開,再來看一下,這一次打印的是KC

那麼爲什麼會這樣呢?

因爲棧的內存是連續的,而我們的屬性的讀取是通過指針偏移來讀取的,而壓棧的示意如下:

上圖中pp指向的是isa,在通過指針偏移讀取self.name時,剛好讀取到了tem,所以打印了KC,所以當沒有打開註釋時,打印的是ViewController

接着在saySomething中打印subject,打印出的是ViewController

那麼我們接着再添加屬性

@property (nonatomic)NSString *age;

打印的話,就會出現野指針報錯

或者將age的類型改成int,也會因爲讀取不到內存,而報錯。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章