iOS 題目詳解 部分一

主要記錄一些題目所關(guān)聯(lián)的知識點, 詳解

iOS 題目詳解 部分一
iOS 題目詳解 部分二
iOS 題目詳解 部分三

iOS 題目簡述 部分一

題目1 關(guān)于打印self 和 super

三個類的繼承關(guān)系如下NSObject->Dog->Doggy;下列代碼打印結(jié)果是什么?

#import "Doggy.h"
@implementation Doggy
- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", [self class]);
        NSLog(@"%@", [super class]);
    }
    return  self;
}
@end

打印結(jié)果是:

2020-07-06 11:43:28.341267+0800 Topic[8721:101914] Doggy
2020-07-06 11:43:28.341389+0800 Topic[8721:101914] Doggy

對于 [self class]沒什么疑問就是當前類, 所以打印Doggy, 但是為什么[super class]也是Doggy而不是Dog呢, 下面從源碼角度驗證此問題;
我們都知道 iOS中調(diào)用方法最后都會轉(zhuǎn)變?yōu)?code>objc_msgSend(receiver, sel)的方式, 而且從objc4-781的 runtime版本 中可以看到 NSObject.mm的源碼有如下, 有此可以得知, class方法是得到當前類的類對象;

+ (Class)class {
    ///類方法直接返回本身
    return self;
}
- (Class)class {
    ///通過實例對象獲取類對象
    return object_getClass(self);
}

因此可以得知不論是[self class]還是[super class] 最終返回的是調(diào)用者 revceiver 的類對象;
通過指令將文件轉(zhuǎn)化為C++文件;得到的代碼如下:

static instancetype _I_Doggy_init(Doggy * self, SEL _cmd) {
    self = ((Doggy *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Doggy"))}, sel_registerName("init"));
    if (self) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8m_1nrsxxrn5ln9zflyd82pvypc0000gn_T_Doggy_d4a898_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8m_1nrsxxrn5ln9zflyd82pvypc0000gn_T_Doggy_d4a898_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Doggy"))}, sel_registerName("class")));
    }
    return self;
}

將源碼簡化下

static instancetype _I_Doggy_init(Doggy * self, SEL _cmd) {
    self = objc_msgSendSuper)({(id)self, [[Doggy class] superClass]}, sel);
    if (self) {
        NSLog(objc_msgSend)(self, sel));
        NSLog(objc_msgSendSuper)({self,  [[Doggy class] superClass]}, sel);
    }
    return self;
}

因此我們只需要弄清楚objc_msgSendSuper()的內(nèi)部邏輯即可, 我們從源碼objc-msg-arm.s中可以得知此方法的定義如下, 通過匯編實現(xiàn), 但是我們不需要知道具體邏輯,通過注釋即可找到我們所需要的;objc_msgSendSuper()的入?yún)⒁彩莾蓚€參數(shù), 第一個參數(shù)是一個結(jié)構(gòu)體objc_super; 結(jié)構(gòu)體中兩個變量第一個是消息接收者, 第二個是接收者的父類類對象; 至此我們可以得知為什么調(diào)用[super class]也會得到當前類的的類對象,因為它的消息接收者仍然是當前類的實例對象;

/********************************************************************
 * id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
 *
 * struct objc_super {
 *      消息接收者
 *     id receiver;
*      從那個類開始搜索方法
 *     Class cls;   // the class to search
 * }
 ********************************************************************/
    ENTRY _objc_msgSendSuper
    ldr r9, [r0, #CLASS]    // r9 = struct super->class
    CacheLookup NORMAL, _objc_msgSendSuper
    // cache hit, IMP in r12, eq already set for nonstret forwarding
    ldr r0, [r0, #RECEIVER] // load real receiver
    bx  r12         // call imp

    CacheLookup2 NORMAL, _objc_msgSendSuper
    // cache miss
    ldr r9, [r0, #CLASS]    // r9 = struct super->class
    ldr r0, [r0, #RECEIVER] // load real receiver
    b   __objc_msgSend_uncached
    END_ENTRY _objc_msgSendSuper

通過 clang語句將 OC文件轉(zhuǎn)化為C++文件, 并不是100%實際底層執(zhí)行邏輯, 只是大概的邏輯, 就如這里, 其實真正調(diào)用的方法是objc_msgSendSuper2()這個方法, 不過不論哪個方法對我們的分析結(jié)構(gòu)影響不大;

1.1 題目1衍生問題, 分析調(diào)用分類方法為什么先調(diào)用[super sel];

分析如下子類調(diào)用父類方法為什么先寫[super sel];

///父類方法的實現(xiàn)
- (void)eatSomething {
    NSLog(@"Dog Like Bones");
}
///子類的方法實現(xiàn)
- (void)eatSomething {
    [super eatSomething];
    NSLog(@"Doggy like Bones Too");
}

子類調(diào)用父類方法先調(diào)用[super sel]通過上面的底層分析我們可以得知這樣寫的調(diào)用流程為, 通過結(jié)構(gòu)體objc_super我們可以得知實際的調(diào)用者是當前類, 但是查找方法的過程是從父類開始查找的, 因從當前類開始查找會造成死循環(huán);方法調(diào)用順序, 具體可以通過objc_msgSend()流程得知

 struct objc_super {
     id receiver;
     Class cls; // the class to search
 }

題目2 :isKindOfClass 和 isMemberOfClass 的區(qū)別;

如下代碼的打印結(jié)果

Doggy *doggy = [[Doggy alloc] init];
Dog *dog = [[Dog alloc] init];
BOOL case1 = [doggy isKindOfClass:[Dog class]];
BOOL case2 = [doggy isMemberOfClass:[Dog class]];
BOOL case3 = [dog isKindOfClass:[NSObject class]];
BOOL case4 = [dog isMemberOfClass:[NSObject class]];
BOOL case5 = [[Doggy class] isMemberOfClass:object_getClass([Dog class])];
BOOL case6 = [[Doggy class] isKindOfClass:object_getClass([Dog class])];

打印結(jié)果為

case1 = 1, case2 = 0, case3 = 1, case4 = 0, case5 = 0, case6 = 1

下面通過源碼分析為什么是這個結(jié)果, 通過NSObject.mm中我們可以得知isMemberOfClassisKindOfClass的方法實現(xiàn)為


+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

首先得知這倆方法都有實例方法和類方法;分別用來判斷類對象元類對象相等或者包含邏輯;

  • 實例方法: 是判斷類對象是否相等或是其子類;
    - (BOOL)isMemberOfClass:(Class)cls :判斷[當前實例對象的類對象]是否等于[類對象 cls]; (判斷相等)
    - (BOOL)isKindOfClass:(Class)cls : 判斷[當前實例對象的類對象]是否屬于 [類對象cls] 的子類或者等于 [類型cls];(判斷相等或包含)
  • 類方法: 是判斷元類對象是否相等或是其子類;
    + (BOOL)isMemberOfClass:(Class)cls: 判斷[當前類對象指向的元類對象]是否等于 [元類對象cls]; (判斷相等)
    + (BOOL)isKindOfClass:(Class)cls : 判斷[當前類對象指向元類對象] 是否屬于 [元類對象 cls子類]或者等于[元類對象cls], 注意這里有個隱藏點, 基類的元類對象的 superclass指向基類;(判斷相等或包含)

得知方法的底層實現(xiàn)后即可得知為什么打印結(jié)果是那樣;


題目3 以下代碼能否執(zhí)行, 如果能執(zhí)行, 結(jié)果是什么;

如下代碼

///自定義的Model.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Model : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
- (void)printName;
- (void)printAge;
+ (void)printSuperClass;
@end
NS_ASSUME_NONNULL_END

///Model.m文件
#import "Model.h"
@implementation Model
- (void)printName {
    NSLog(@"name is : %@", self.name);
}
- (void)printAge {
    NSLog(@"age is : %@", self.age);
}
+ (void)printSuperClass {
    NSLog(@"superclass is : %@", [self superclass]);
}
@end

以下代碼能不能執(zhí)行, 如果能執(zhí)行, 結(jié)果是什么?

    NSString *testAge = @"TheAge";
    NSString *testName = @"TheName";
    id cls =  [Model class];
    void *clsPoint = &cls;
    [(__bridge id)clsPoint printAge];

首先說下答案, 是可以編譯, 可以執(zhí)行, 而且不會報錯; 打印結(jié)果是

2020-07-07 22:37:37.526402+0800 Topic[7827:1339842] age is : TheAge

此問題牽扯知識點比較多, 一點一點梳理;

  • 3.1 首先是一個結(jié)構(gòu)體的的首個變量的地址就是結(jié)構(gòu)的地址, 并且通過內(nèi)存地址的加減偏移獲得其他的變量地址, 關(guān)于驗證過程可以看結(jié)尾的補充1部分;
  • 3.2 實例對象和類對象的底層結(jié)構(gòu)都為結(jié)構(gòu)體, 并且首個元素是isa指針, 因此實例對象的地址就是isa的地址, 因為在64位架構(gòu)之前isa直接就是指向的類對象, 64位架構(gòu)開始isa&ISAac_MASK可以得到類對象, 因此我們可以簡單的只要拿到isa就可以拿到類對象的地址, 進而可以查找到整個類的實例方法, 因為實例方法存儲在類對象中,可以看這篇文章;
  • 3.3 方法的調(diào)用最后都會轉(zhuǎn)化為objc_msgSend(receiver, sel)的方式, 以實例對象為例, 則實例對象receiver的首地址就是isa, 通過首地址可以找到對應(yīng)的類對象, 進而查找調(diào)用方法;因此我們可以推測只要是這種指向結(jié)構(gòu)的均可以查找并調(diào)用方法;
  • 3.4 iOS 中采用小端模式, 驗證過程可以看結(jié)尾的補充2部分

通過這個圖我們可以看出當實例方法中調(diào)用_age的變量時, 是實例對象地址(結(jié)構(gòu)體, 向下尋找)加上16個字節(jié)就是_name的地址, 所以通過clsPoint查找到方法并調(diào)用時內(nèi)存地址(向上尋找)加16個字節(jié)得到的是 testAge變量的值; 至此, 方法調(diào)用結(jié)束, 注意如果地址(向上尋找)加上16個字節(jié)找不到合適的變量, 則會崩潰;


題目4 日常開發(fā)中哪些地方用到 runtime?

OC 是動態(tài) 語言, 它的動態(tài)性就是由runtime來支撐的;平時編寫的 OC代碼基本上底層都是轉(zhuǎn)化為了runtime來調(diào)用的;
具體運用:

  • 利用關(guān)聯(lián)對象技術(shù)來跟分類添加屬性;
  • 遍歷類的所有成員變量(例如之前可以直接修改 UITextField占位文字顏色背景色之類的操作);
  • 方法交換,替換系統(tǒng)方法的實現(xiàn);
  • 利用消息轉(zhuǎn)發(fā)處理找不到方法的異常(動態(tài)添加方法);

題目5 如何通過runtime替換系統(tǒng)的 UIButton 的點擊事件;

例如我們需要打印出來每個 UIButton的點擊事件時的信息, 可以為其添加一個分類將點擊后調(diào)用的方法替換掉;
直接為 UIControl添加一個 Category將原本方法替換掉, 一旦執(zhí)行了方法替換, 則方法查找, 緩存會被清空;

@implementation UIControl (Extention)
+ (void)load {
    Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method method2 = class_getInstanceMethod(self, @selector(x_sendAction:to:forEvent:));
    method_exchangeImplementations(method1, method2);
}
- (void)x_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    ///如果是 UIButton則處理下
    if ([self isKindOfClass:[UIButton class]]) {
        NSLog(@"UIButton 點擊著的信息action: %@__target: %@__event: %@", NSStringFromSelector(action), target, event);
        /*
        如果原本的方法中仍然想讓執(zhí)行, 則此處要這樣調(diào)用一下, 為什么這樣不會造成死循環(huán)
        因為方法的實現(xiàn)(IMP)已經(jīng)被替換了;源碼中可以得知這一點; 
        */
        [self x_sendAction:action to:target forEvent:event];
    }
}
@end
===>
void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;
    mutex_locker_t lock(runtimeLock);
    ///將方法的 IMP 替換
    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;
    ///將方法的緩存清空
    flushCaches(nil);
    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}



題目6 關(guān)于 Tagged Pointer

有如下兩段代碼, 請問分別能不能執(zhí)行, 執(zhí)行結(jié)果如何
情況1:

for (int i = 0; i < 1000; i ++) {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0 );
    dispatch_async(queue, ^{
        NSString *name = [NSString stringWithFormat:@"abcdefghhijklmnopqrst"];
//            NSLog(@"name = %p", name);    
    self.name = name;
    });
}

情況2

for (int i = 0; i < 10000; i ++) {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0 );
    dispatch_async(queue, ^{
        NSString *name = [NSString stringWithFormat:@"abc"];
//            NSLog(@"name = %p", name);
        self.name = name;
    });
}

運行結(jié)果是, 情況1會崩潰,情況2能正常運行;
首先我們知道self.name = name;的本質(zhì)是調(diào)用其setter方法, 而ARC下也是有內(nèi)存管理這個概念, 只不過系統(tǒng)幫忙做了這個工作, 所以不論是ARC還是MRC最后的setter方法都會執(zhí)行如下

- (void)setName:(NSString *)name {
  if (_name != name)
    [_name release];
    _name = [name retain];
}

情況1為什么會崩潰, 因為NSString *name = [NSString stringWithFormat:@"abcdefghhijklmnopqrst"];執(zhí)行多少次就在堆區(qū)創(chuàng)建了多少個新對象, 通過打印即可確定; 所以我們可以知道if (_name != name)會一直成立, 這樣的話[_name release];_name = [name retain];也會每次都執(zhí)行, 由于是多線程, 同時修改_name崩潰風(fēng)險;
解決方案1, 使用原子操作:
@property (nonatomic, strong) NSString *name;
改為@property (natomic, strong) NSString *name;
解決方案2: 為 setter的賦值過程加鎖;

為什么情況2可以正常運行, 那是因為在64架構(gòu)以后使用了Tagged Pointer技術(shù), 針對小的NSNumber, NSString做了優(yōu)化, 將值直接存在指針內(nèi), 所以不論多少次NSString *name = [NSString stringWithFormat:@"abc"];name中存儲的始終是一個地址(abc值則直接封裝在這個地址中), 通過打印即可驗證;
因為始終是同一個地址, 所以判斷條件if (_name != name)只會在第一次成立, 后續(xù)就不再成立, 即使再多線程也沒風(fēng)險;


題目7 OC的內(nèi)存管理
  • iOS中使用引用計數(shù)的概念來管理內(nèi)存;
  • 新創(chuàng)建的對象引用計數(shù)為1, 引用計數(shù)為0時對象會被銷毀, 釋放其所占內(nèi)存;
  • 調(diào)用retain會使引用計數(shù)加1, 調(diào)用release會使引用計數(shù)減1;
  • 調(diào)用alloc, new, copy, mutablecopy創(chuàng)建新對象, 引用計數(shù)變?yōu)?; 調(diào)用release, autorelease釋放對象;
7.1 引用計數(shù)存儲在哪里?

答案: 引用計數(shù)存放在 isa指針中, 如果不夠存放則存放在一個散列表SideTable中;
通過在runtime部分學(xué)習(xí)isa指針的結(jié)構(gòu)可以得知這個結(jié)論;
下面通過源碼來驗證, 使用runtime的版本為objc4-781版本;
找到NSObject.mm的實現(xiàn), 找到retainCount, 可以找到SideTable的結(jié)構(gòu)為

struct SideTable {
    spinlock_t slock;
    ///存放的是引用計數(shù)reference counts;
    RefcountMap refcnts;
    ///弱指針引用表, weak 修飾的對象, 釋放時使用這個表;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
...
}

我們知道查看引用計數(shù)使用的是retainCount, 所以可以得到

///引用計數(shù)入口
- (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}

===>
_objc_rootRetainCount(id obj) {
    ASSERT(obj);
    return obj->rootRetainCount();
}

===>
/*
這里需要注意, 你會找到兩個rootRetainCount()方法, 需要注意的是一個是
區(qū)別一個支持NONPOINTER_ISA, 一個是不支持NONPOINTER_ISA;
現(xiàn)在的64位架構(gòu)都是使用SUPPORT_NONPOINTER_ISA, 具體驗證過程可以點擊#if SUPPORT_NONPOINTER_ISA宏去查看, 
不再貼出驗證過程;
*/
#if SUPPORT_NONPOINTER_ISA
...

inline uintptr_t 
objc_object::rootRetainCount()
{
    ///如果是TaggedPointer 直接返回, 因為這是一個指針, 不是對象, 不需要考慮引用計數(shù);
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    ///獲取 isa 指針的內(nèi)容
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    ///如果使用了nonpointer, 64架構(gòu)值是1
    if (bits.nonpointer) {
        /*
        rc 就是1加上存在 isa 中的extra_rc;
          跟之前的 runtime 中講解extra_rc的對應(yīng)上. extra_rc存的值是 rc 的值減1;
        */
        uintptr_t rc = 1 + bits.extra_rc;
        /// 跟之前的 runtime 中講解has_sidetable_rc對應(yīng)上, 如果是1,  則使用散列表存儲引用計數(shù);
        if (bits.has_sidetable_rc) {
             ///rc 的值為1加上散列表中 rc 的值
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}
// SUPPORT_NONPOINTER_ISA
#else
// not SUPPORT_NONPOINTER_ISA

....
#endif

===>
size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    ASSERT(isa.nonpointer);
     ///散列表SideTable, 第二個變量存儲的是引用計數(shù)
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

至此可以驗證引用計數(shù)的存儲位置;

7.2 weak 指針的原理是什么?

跟引用計數(shù)一樣, weak指針會被存放在散列表中; 在對象執(zhí)行dealloc的時候?qū)⑦@個散列表遍歷置為nil;
通過源碼查看流程, 入口為NSObjectdealloc函數(shù);

- (void)dealloc {
    _objc_rootDealloc(self);
}

===>
void _objc_rootDealloc(id obj){
    ASSERT(obj);
    obj->rootDealloc();
}

===>
inline void
objc_object::rootDealloc()
{
    ///判斷是否是 TaggedPointer 如果是則不考慮內(nèi)存管理
    if (isTaggedPointer()) return;  // fixme necessary?
    /*
    判斷是否有弱引用, 關(guān)聯(lián)對象, C++析構(gòu)函數(shù), 是否散列表存儲
    如果沒有這些則會釋放的更快
    */
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        ///有弱引用, 關(guān)聯(lián)對象, C++析構(gòu)函數(shù), 散列表存儲之類
        object_dispose((id)this);
    }
}

===>
id 
object_dispose(id obj) {
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

===>
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);
        ///處理需要 dealloc 的對象
        obj->clearDeallocating();
    }
    return obj;
}

===>
inline void 
objc_object::clearDeallocating()
{
    sidetable_clearDeallocating();
}

===>
void 
objc_object::sidetable_clearDeallocating()
{
    ///通過當前為 key 取出散列表
    SideTable& table = SideTables()[this];
    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        ///通過&SIDE_TABLE_WEAKLY_REFERENCED 獲取弱引用散列表 然后清理
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            ///清理弱引用指針,  通過弱引用散列表
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

至此可以確定weak的原理;




補充和驗證部分

補充1

結(jié)構(gòu)體的首個變量的地址就是結(jié)構(gòu)體的地址, 并且可以通過通過內(nèi)存地址的加減獲得其他變量的地址;
例如有如下結(jié)構(gòu)體:

struct Person {
    int age;
    int height;
};
struct Man {
    struct Person person;
    int weight;
};

則下述的操作進和判斷都是合理的

//**驗證代碼如下**//
    struct Man man = {
        {30, 180},
        100
    };
    void *ageAddress = &(man.person.age);
    ///第一個元素的的地址就是結(jié)構(gòu)體的地址   地址: 0x00007ffee44e4b80
    void *manAddress = &(man);
    ///地址:  0x00007ffee44e4b80
    void *firstParaAddress = &(man.person);
    ///拿到 man 結(jié)構(gòu)體中第一個元素的地址+8就是 man.weight 的地址; firstParaAddress + 8 = 0x00007ffee44e4b88;
    void *weightAddress = firstParaAddress + 8;
    NSLog(@"結(jié)構(gòu)體中第一個元素地址: %p",ageAddress );
    NSLog(@"結(jié)構(gòu)體中第一個元素地址: %p",manAddress );
    NSLog(@"結(jié)構(gòu)體中地址: %p",manAddress );
    NSLog(@"首地址+8 就是weight的地址: %p == %p", &(man.weight), weightAddress);
//**打印結(jié)果如下**//
2020-07-07 22:37:37.526033+0800 Topic[7827:1339842] 結(jié)構(gòu)體中第一個元素地址: 0x16f73f840
2020-07-07 22:37:37.526191+0800 Topic[7827:1339842] 結(jié)構(gòu)體中第一個元素地址: 0x16f73f840
2020-07-07 22:37:37.526240+0800 Topic[7827:1339842] 結(jié)構(gòu)體中地址: 0x16f73f840
2020-07-07 22:37:37.526290+0800 Topic[7827:1339842] 首地址+8 就是weight的地址: 0x16f73f848 == 0x16f73f848
補充2

iOS 采用小段模式,存儲在棧區(qū)的變量的內(nèi)存地址是遞減的;

    ///&str1 = 0x00007ffee71a6ed8
    NSString *str1 = @"abc";
    ///&str2 = 0x00007ffee71a6ed0
    NSString *str2 = @"111";
    ///&str3 = 0x00007ffee71a6ec8
    NSString *str3 = @"123asdf";
    ///&num1 = 0x00007ffee71a6ec4
    int num1 = 1;
    ///&num3 = 0x00007ffee71a6ec0
    int num2 = 2;
    ///num3Address = 0x00007ffee71a6ebc;
    int num3 = 3;
    void *num3Address = &num3;

通過上面我們可以看到在這個方法中開辟的變量在棧中的地址是遞減存在的; 每次是遞減8個字節(jié)或者4個字節(jié);


文中測試代碼


參考文章和下載鏈接
Apple 一些源碼的下載地址
大小端模式
iOS 判斷大小端字節(jié)序
方法的查找順序
LP64 結(jié)構(gòu)數(shù)據(jù)占據(jù)多少位
LP64什么意思
isa 的結(jié)構(gòu)
iOS Tagged Pointer技術(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容