iOS经典面试题

iOS 经典全部面试题(上) 前端面试题汇总前端面试题是我面试过程中遇到的面试题,每一次面试后我都会复盘总结。 阅读详情

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130595548,不管你是大牛还是小白都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

1. Runtime 是什么?

Runtime是一套有C、C++和汇编混合编写的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中,一旦编译完成,就无法修改。
  • 运行时创建的类还没注册到内存,可以添加实例变量。在动态创建的类添加属性的时候,系统不会生成setter和getter,要手动添加。
  • 编译后的得到的类,我们也要通过类拓展添加实例变量关联对象,类拓展在编译的时候做为类的一部分直接编译到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;
}

通过referent从 weak_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底层维系一张散列表SideTable,SideTable中维系一张弱引用表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方法底层都是发送retain和release消息。

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

对objectAtIndex和lg_objectAtIndex进行交换,当调用系统objectAtIndex方法时,调用lg_objectAtIndex方法,方便我们在数组越界时的一些处理(比如防崩溃)

那么假如我们在添加了NSArray的分类之后,在某个调用的地方,不小心调用了,NSArray的load方法,比如下面的代码,会发生什么呢?

    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中实现了personInstanceMethod和personClassMethod两个方法,

在子类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,也会因为读取不到内存,而报错。

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130595548,不管你是大牛还是小白都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

62道经典 iOS面试题 前言: 每年三四月份都是招聘高峰期,也就是我们说的金三银四,很多想换工作的职场人士都会选择在此时换一份工作,毕竟一年之计在于春,对于公司和个人而言都是一个新的开始。 今年受疫情的影响,各行各业都举步维艰,然而做为iOS面试官的我也在精心准备的为公司招进优秀的iOS开发,期间收到了很多简历,也笔试了不少开发者,上一周我们已经发了3个offer,所以先下手为强,我们已经招满了人的职位,依旧陆陆续续也还有很多的简历投向我们。 前面一段时间给公司面试了10个iOS开发,自己总结了62道面试题,在此,我分享给大家, 阅读详情

相关推荐

整理一篇iOS经典面试题大全

###1.INTERVIEW 共勉 ###2.iOS developers 方向 ###3.INTERVIEW QUESTION 1.深copy和浅copy 浅拷贝: 1.对内存地址的复制,让目标对象指针和源对象指向同一片内存空间. 2.内存销毁的时候,指向这片空间的指针需要重新定义才可以使用,要不然会成为野指针 3.拷贝指向原来对象的指针,使原对象的引用计数加+1 4.相当于创建了一个指向原对象的新指针,并没有创建一个新的对象. 深拷贝: 1.拷贝对象的具体内容,而内存地址是自主分配的 2

IOSNF的博客 619

iOS 面试题总结(可能是最全的!!!)

不设置监听,就可以关闭了。实现思想:当设置监听一个对象时,动态创建一个新的继承被监听对象原本类的类,并重写setter方法,最后把这个对象的isa指针(即这个对象的类的内存地址)更改为这个新创建的类,这时,这个对象就变成了这个新类的实例重写addobserver方法:(1)检查该对象的类有没有对应的setter方法,如果没有就抛出异常(2)检查对象的类是不是一个kvo类,如果不是,就新建一个继承这个类的新类,并把对象的isa指向这个新类。

sgliquangang的专栏 1万+

iOS 经典面试题汇总

(iOS面试资料大全) 1. 简单介绍下NSURLConnection类及+ sendSynchronousRequest:returningResponse:error:与– initWithRequest:delegate:两个方法的区别? 答: NSURLConnection主要用于网络访问,其中+ sendSynchronousRequest:returningResponse:error:是同步访问数据,即当前线程会阻塞,并等待request的返回的response,而– initWithReq

IOSNF的博客 426

(2026年)iOS Swift面试题,Flutter面试,OC面试题及答案。

第一大类OC面试题 一.设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。 1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。 2). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。 3). 单例模式:通过static...

田风有的博客 6万+

Android HWC硬件合成器实战:从SurfaceFlinger到显示驱动的完整流程解析

本文深入解析Android HWC硬件合成器从SurfaceFlinger到显示驱动的完整流程,揭示HWC如何优化图形性能并降低功耗。通过实战案例和调试技巧,帮助开发者掌握硬件加速的图层混合技术,提升移动设备的显示效率。

gaochao的博客 494

Ubuntu安装有线网卡驱动

问题:插入网线后,无法识别有线连接。 原因:主板有线网卡的型号和系统中网卡的驱动不匹配 解决 前提条件: 需要连wifi通网,因为需要安装dkms,和下载驱动。 删除原驱动 1、查看网卡型号 lspci | grep net 我的网卡型号是8125 2、查看网卡驱动 lspci -k 网卡驱动是r8169 3、卸载网卡驱动 找出位置 modinfo r8169 | grep filename 然后删除 rm path/r8169.ko 卸载驱动 rmmod r8169 查看是否卸载成功 lsm

weixin_43932656的博客 1万+

iOS 面试题--转自唐巧

iOS 面试题(一)寻找最近公共 View 题目:找出两个 UIView 的最近的公共 View,如果不存在,则输出 nil 。 分析:这其实是数据结构里面的找最近公共祖先的问题。 一个UIViewController中的所有view之间的关系其实可以看成一颗树,UIViewController的view变量是这颗树的根节点,其它的view都是根节点的直接或间接子节点。

david21984的专栏 1万+

Hi3516CV610硬件设计避坑指南:从电源模块到DDR布线实战解析

本文深入解析了Hi3516CV610硬件设计中的关键难点与实战技巧。重点剖析了电源架构的选型陷阱与PCB布局要点,详细阐述了DDR3/L高速布线的等长匹配、信号完整性及端接策略,并提供了MIPI接口与时钟电路的隔离设计指南。旨在帮助工程师规避常见设计错误,提升系统稳定性与可靠性。

ooo22的博客 255

2024最新!!!iOS高级面试题,全!(一)

Objective-C的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;在核心动画中,动画的实现原理是通过不断地修改CALayer的属性值来实现的。消息发送:当调用某个对象的方法的时候,会通过isa指针找到该类的cache缓存列表中查找,如果没有找到就去方法列表中查找,如果还没有找到就去他的父类中查找,直到最后查找到后为nil。

u010609022的专栏 1万+

MD01和MD01N比较

摘要:MD01(传统MRP)与MD01N(MRP Live)是SAP系统不同版本的物料需求计划事务码。核心差异包括:MD01N取消MRP列表、简化计划规则、合并MPS/MRP操作、采用BAdI技术等,体现S/4HANA平台简化、标准化和智能化的演进方向。MD01N通过优化参数配置、增强筛选功能、利用HANA实时计算能力,实现了更高效的物料计划流程,代表了新一代计划引擎的技术革新。

SAP_LES_COM的博客 891

时间序列学习(4):平稳性检验(单位根检验、ADF检验)

时间序列学习(4):平稳性检验(单位根检验、ADF检验)1、单位根检验2、ADF检验3、指数走势的检验4、对数收益率序列检验 相关图可以大致判断序列是否平稳。但是,这毕竟不是严格的。 这篇笔记来就谈一谈平稳性的检验。 到目前为止,我们有了以下的时间序列模型: 白噪声; 随机游走; AR模型; MA模型; ARMA模型。 我们知道白噪声、MA模型一定是平稳的(这里的平稳都是弱平稳);随机游走一定是不平稳的;ARMA模型取决于其AR部分。 所以唯一需要做平稳性检验的就是AR模型。 1、单位根检验 先来看一阶

lucialucia的博客 7万+

2022年iOS最新面试(底层基础)问题答案

fafd

super_man_ww的博客 7134

手机相机Tuning专项调优(一):基础流程与图像主观评价标准建立实战解析

本篇为手机相机系统“Tuning专项调优”系列的开篇,深入剖析了当前手机相机Tuning的基本流程与图像主观评价标准的构建方法。结合实际工程经验与最新业界案例,详细介绍ISP调优和3A控制中涉及的主观评价标准,如锐度、色彩饱和度、噪声感知等,并提出一套可工程化落地的标准制定与评测实践体系,帮助工程师有效提升图像品质。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 1933

iOS经典面试题大全

1.INTERVIEW 共勉 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:638302184,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 与2800+iOS开发者一起交流学习成长! 2.INTERVIEW 准备 3.iOS developers 方向 4.INTERVIEW QUESTION 4.1深copy和浅co...

原来是泽镜的博客 1491

新手福利!大龙虾OpenClaw可视化管理工具来了!告别命令行,这个可视化工具让OpenClaw管理变得超简单

还在用命令行管理OpenClaw?🚀 这个跨平台可视化工具支持14+AI模型,一键切换,还能对接微信飞书钉钉等8大消息渠道,让AI助手管理变得简单直观 ✅ 暗色主题护眼舒适,Rust后端高性能低占用,新手也能轻松上手 🎯

XiaoqiangClub的博客 760

《囚徒健身》 六艺,十式

文章目录《囚徒健身》 六艺,十式1、六艺,十式2、优缺点3、手臂和上半身 《囚徒健身》 六艺,十式 1、六艺,十式 1、俯卧撑: 2、深蹲: 3、举腿: 4、引体向上: 5、桥: 6、倒立撑: 十式就是每个动作分拆为十个不同的动作等级,进行训练 每一式里面,又根据做的组数和个数,分成三层,初级,中级,高级 依次循序渐进,每完成上一个动作的高级,则向下一个动作前进 2、优缺点 优点 : 六个动作都是很常见的,日常的动作,不太需要依赖什么器械,随时随地都可以练习,很方便 这些动作可以覆盖到全身大部分

简简单单Onlinezuozuo 4564

李洪强iOS经典面试题

李洪强iOS经典面试题 1. struct和class的区别 swift中,class是引用类型,struct是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。 class有这几个功能struct没有的: class可以继承,这样子类可以使用父类的特性和方法 类型...

aaa1231722的博客 150

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

李洪强iOS经典面试题35-按层遍历二叉树的节点

李洪强iOS经典面试题35-按层遍历二叉树的节点 问题 给你一棵二叉树,请按层输出其的节点值,即:按从上到下,从左到右的顺序。 例如,如果给你如下一棵二叉树: 3 / \ 9 20 / \ 15 7 输出结果应该是: [ [3], [9,20], [15,7] ] 代码模版 本题的 Swift...

a359798678的博客 254

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

iOS经典面试题(附答案)

面试题+答案。

ybcy0612的博客 666

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

上一篇: iOS面试的一些小问题
下一篇: iOS(OC)面试小整理
iOS大鑫
博客等级 码龄6年 69粉丝 87原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值