美文网首页
iOS-面试

iOS-面试

作者: 李永开 | 来源:发表于2019-01-11 18:10 被阅读0次

一.NSObjcet对象在内存中占多少个字节?

  1. 点进去NSObject,发现NSObject内部只有一个Class.
     @interface NSObject <NSObject> {
        Class isa  OBJC_ISA_AVAILABILITY;
    }
  1. Class是一个objc_class结构体指针,指向objc_class这个结构体
    typedef struct objc_class *Class;
  1. objc_class内部实现
    !__OBJC2__代表:不是objc2这个版本才可以使用下面的定义;
    Use Class instead of struct objc_class:让我们使用Class这个结构体.
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

4.从源码中找到Class结构体(从下往上看)

#:类最初始的信息,不包含catagory等的信息---ro的意思:只读
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

#:类的具体信息----rw的意思:可读可写
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;//包含了上面的结构体

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

//因为是继承关系,所以objc_class里面也有一个isa指针
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;

#方法缓存
    cache_t cache;             // formerly cache pointer and vtable

#:bits用来获取具体的类信息,bits&FAST_DATA_MASK就可以拿到struct class_rw_t的信息.
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

答案:
arm64架构的数据总线为64bits,也就是8bytes,所以在arm64架构里面一个指针占8个字节.
如果是arm32,那么就占4个字节.

拓展
demo1:

使用runtime的class_getInstanceSize()方法得到一个 的大小内存对齐过的
使用malloc_size()得到的是实际分配的内存大小

-----#import<malloc/malloc.h>
-----5s,6等arm64架构下输出
-----
----- NSLog(@"%zd",  -----class_getInstanceSize([NSObject class]));//8
-----
----- NSObject *objc = [[NSObject alloc]init];
----- NSLog(@"%zd",malloc_size((__bridge const void *)(objc)));//16
  • 可以看出来,虽然Class大小应该是8个字节,但是系统实际上给分配了16个字节.
    所以NSObjcet对象在内存中占16个字节(arm64架构).
demo2:
@interface Father: NSObject
{
  int age;
  int weight;
  int height;
}
@end
@implementation Father
@end

打印:NSLog(@"%zd", class_getInstanceSize([Father class]));
输出:2019-01-10 10:14:24.498 iOSWorld[2913:385535] 24

Father *f = [[Father alloc]init];
NSLog(@"%zd",malloc_size((__bridge const void *)(f)));
输出32.

注意:因为Father继承自NSObject,所以它内部有NSObject的实现,那么它占的内存为8(NSObject的isa指针)+4x(Father的3个int类型) = 8 + 12 =20个字节
但是:因为结构体计算内存的时候,要考虑内存对齐(和数据总线有关)的问题.
不满8个字节也要算8个字节,也就是必须是8的倍数,所以实际占用24个字节.
但但但又因为:苹果规定了每个对象在内存中至少16个字节,也就是16的倍数.所以f这个实例对象实际上占用了32个字节.(objc4源码的alloc方法里面有苹果的规定)
结论:Father对象其实只需要24个字节的空间,但实际上给分配了32个字节.

demo3:
@interface Father : NSObject
{
    int age;
}
@end
@implementation Father
@end
@interface Son : Father
{
    int weight;
}
@end
@implementation Son
@end
打印:NSLog(@"%zd", class_getInstanceSize([Son class]));
输出:2019-01-10 10:48:00.284 iOSWorld[2940:390486] 16

father占了8(NSObject 指针) + 4(int类型) = 12
son 继承自 father,所以son的c++结构体应该张这样

struct Son_IMPL{
  struct Father_IMPL s; //16个字节
  int weight;//4个字节
}

乍一看16+4= 20, 但是答案是16.
原因还在于内存对齐,毕竟Father_IMPL还有4个字节是空的,刚好容得下weight这个int

注意:

1.结构体内存对齐:结构体的大小必须为最大成员大小的倍数
用空间换时间,合理利用内存的地址.
2.OC中,对象的变量和方法不在同一区域,所以对象在内存中占的大小不包括方法.
解释:NSObject实际上使用了8个字节,但是apple规定分配了16个字节,所以NSObject实际上占用了16个字节(使用和占用不太一样哦)

apple规定:如果一个对象分配的内存<16,那么就等于16.而且对象分配的内存为16的倍数猜想:给8个字节太小了,放个指针就没有了,都没地方放成员变量,所以就以16为单位了吧😅
从源码看规定,下面是方法的依次调用

1.
+ (id)allocWithZone:(struct _NSZone *)zone {
   return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
2.
obj = class_createInstance(cls, 0);
3.
class_createInstance(Class cls, size_t extraBytes)
4.
_class_createInstanceFromZone(cls, extraBytes, nil);
5. 拿到size,分配内存
size_t size = cls->instanceSize(extraBytes);
obj = (id)calloc(1, size);

6. size的实现 ,如果<16,则=16
size_t size = alignedInstanceSize() + extraBytes;//extraBytes为0
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;

7.alignedInstanceSize()的实现,也就是内存对齐的实现.
unalignedInstanceSize是对象的实际大小,不经过内存对齐的.经过word_align后就是内存对齐过的.
word_align(unalignedInstanceSize())

二.类的信息放在哪里?

*OC对象共3种:
1.实例对象:存放isa指针(指向类)和变量
2.类对象:存放isa指针(指向元类)、superClass、属性、对象方法、协议、成员变量
3.元类对象:存放isa指针(指向元类本身)、superClass、类方法
所有元类的isa指针都指向根元类(rootMetaClass)
根元类的superClasss指向RootClass
答案:类的信息放在元类里面

拓展:
@interface Father: NSObject
{
    int age;
}
@end
@implementation Father
@end
Father *father = [[Father alloc]init];

father这个实例对象里面的isa地址:p/x (long)father->isa0x1a1003a7cf9
Father这个类对象的地址:NSLog(@"%p",object_getClass(father));0x1003a7cf8
p/x 0x1a1003a7cf9 & 0xffffffff8 = 0x1003a7cf8
object_getClass获取的是isa指针指向的类,而objc_getClass是通过查找mapPair(字典)的形式获取类
apple官方源码:实例对象的isa指针需要进行&计算才能得到类对象的真正地址

为啥需要&运算呢?(下面为objc源码)

#union:共用体
union isa_t {
   isa_t() { }
   isa_t(uintptr_t value) : bits(value) { }

   Class cls;
   uintptr_t bits;
#if defined(ISA_BITFIELD)
   struct {
       ISA_BITFIELD;  // defined in isa.h
   };
#endif
};

#ISA_BITFIELD的定义
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
     uintptr_t nonpointer        : 1;                                       \
     uintptr_t has_assoc         : 1;                                       \
     uintptr_t has_cxx_dtor      : 1;                                       \
     uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ >\
     uintptr_t magic             : 6;                                       \
     uintptr_t weakly_referenced : 1;                                       \
     uintptr_t deallocating      : 1;                                       \
     uintptr_t has_sidetable_rc  : 1;                                       \
     uintptr_t extra_rc          : 19

因为apple在arm64后,对isa做了优化.
原来的isa直接指向类对象或元类对象地址,现在优化过后将位域的33bits保存类对象或元类对象地址,其他的64-33=31位保存其他信息.

三.extention和category的区别?

  • extension看起来很像一个匿名的category,extension在编译期决议,它就是类的一部分,一般用来隐藏类的私有信息.
  • category则完全不一样,它是在运行期决议的.在运行程序的时候,内核会加载dyld,dyld会加载各种动态库镜像,会加载catagory并将catagroy的类方法添加到已经编译好的类的方法列表里面,然后才会调用main()函数.
    所以说:使用catagory会降低程序的运行速度.
  • 所以extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的.相对的,对象的方法定义都保存在类的可变区域中。

四. + (void)load方法调用时机?

   //1.从objc-os.mm文件的void _objc_init(void)方法进来
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

   //2.进入load_images
   //判断是否有load方法,没有直接返回
   if (!hasLoadMethods((const headerType *)mh)) return;

   //3.发现load方法
   prepare_load_methods((const headerType *)mh);

   //4.添加到loadable_classes表中去,这个表中维护了所有类,及其对应的load方法的IMP
   schedule_class_load(remapClass(classlist[i]));
    //内部实现:先递归添加父类的load方法
    schedule_class_load(cls->superclass);

   //5.获取非懒加载Category的列表,添加到loadable_categories表中去
   add_category_to_loadable_list(cat);
 
   //6.调用load方法
   call_load_methods();
   //内部实现
    void *pool = objc_autoreleasePoolPush();
    do {
        // 1. Repeatedly call class +loads until there aren't any more
        //调用class的load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        //调用Category的load方法
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);
  • + (load)(在main函数开始执行之前,调用顺序和编译顺序无关,并且只调用一次)的顺序是先执行父类中的+load,然后是子类,再然后执行category中的+load如果有多个category,那么就按照编译的顺序来调用+load.
  • 而且分类的load()方法不会覆盖原来类的load()方法,也就是说所有的load方法都会执行.

五.+ (void)initialize方法调用时机?

  • 在类第一次接收到消息的时候调用,比如第一次alloc的时候.
  • 调用子类的initialize()时候,会先调用父类的initialize().毕竟你爸爸都不存在,你还怎么玩继承呢?
  • catagory的initialize()方法会覆盖原来类的initialize()方法(这是和load()方法的区别)
    解释:initialize()方法走的是msg_send(),所以catagory的initialize()方法调用顺序优先级要高于原来类的优先级
源码:
void callInitialize(Class cls)
{
   ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
   asm("");
}

六.kvc设置值会触发kvo吗

答案:会的.

  • 如果有set(),那么kvc会触发set(),set()又会触发kvo
  • 如果没有set(),kvc内部会调用willChangeValueForKey()didChangeValueForKey(),同样也会触发kvo

拓展 : kvc的实现
kvc会依次寻找下面的方法

  1. setName()
  2. _setName()
  3. 判断accessInstanceVariablesDirectly.如果返回YES,那就开始查找成员变量
  4. 给_name赋值
  5. 给_isName赋值
  6. 给name赋值
  7. 给isName赋值
  8. 如果都没有,报错valueForUndefinedKey.

七. 打印结果是什么?

创建了一个son类和father类,他们都有eat()

@interface Father : NSObject
- (void)eat;
@end
@implementation Father
- (void)eat
{
}
@end

@interface Son : Father
- (void)eat;
@end
@implementation Son
- (void)eat
{
    NSLog(@"self的类为%@",[self class]);
    NSLog(@"super的类为%@",[super class]);
}
@end

打印结果:
2019-01-17 16:39:36.548350+0800 demo[23333:6167589] self的类为Son
2019-01-17 16:39:36.548401+0800 demo[23333:6167589] super的类为Son
  • 为什么[super class]的值也是Son呢?
  1. 使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令得到c++代码,搜索_Son_eat做删减后得到源码
static void _I_Son_eat(Son * self, SEL _cmd) {
    NSLog( (objc_msgSend) (self, sel_registerName("class")) );
    NSLog((objc_msgSendSuper)({self, Father.class}, sel_registerName("class")));
}
  1. 第一个NSLog是给self发送class消息.
    因为son没有class()方法,所以会找Father类.Father类也没有所以会找到NSObject的class()方法,所以会返回Son
  2. 第二个NSLog里面调用了objc_msgSendSuper()方法,查看objc_msgSendSuper()的源码
    里面需要传入一个结构体和SEL,和我们简化过的c++代码吻合
#objc_msgSendSuper
#方法说明:给一个实例对象的父类发送一条消息
#参数1:是一个objc_super结构体,包含了接受消息的实例对象和开始搜索方法实现的超类
#参数2:@SEL 发送的消息
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
  1. struct objc_super结构体的源码为
    这里的receiver为刚才我们打印出来的self, super_class为Father.class.
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};
  1. objc_msgSendSuper()方法我们可以得出:
    第二个NSLog的内容其实是给self发送class消息,而且查找class()这个方法的起始地为Father这个类的cache,然后是Father这个类的msthodList,然后是Father的父类NSObject.
    所以:既然还是给self发送消息,那么打印的值仍然是self的类Son

拓展

我们平时写得self = [super init],其实是告诉系统去父类的methodList中查找方法而已,并不是初始化父类....

八.objc_getClass()object_getClass()区别

1.objc_getClass()

  • 根据map表找到Class,并且只能找到类,不能找到元类.
  • 传入字符串
Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    //获取类
    return look_up_class(aClassName, NO, YES);
}

2.object_getClass()

  • 直接去找isa指针.
  • 传入实例对象,返回类.传入类对象,返回元类.
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

九.为什么UIKit不是线程安全的

  • 成本较大,性价比低.

十.property里面的atomic和nonatomic是怎样实现的?

  • 其实是使用了自旋锁(自旋锁适合消耗时间短的操作,而set方法刚好满足,所以使用自旋锁比使用synchronized更合适),看下面的代码
  • atomic保证了同一时刻只有一个set方法能执行,但不保证get方法.(多读单写)
  • atomic并不安全,直接调用 _age=10就跳过了set方法...
if (!atomic) 
{  
  oldValue = *slot;  
  *slot = newValue;  
} 
else
{  
  spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];  
  _spin_lock(slotlock);  
  oldValue = *slot;  
  *slot = newValue;          
   _spin_unlock(slotlock);  
}  

十一.优化

1.CALayer替代UIView2
2.提前计算好布局
3.不适用autolayout
4.把耗时操作放到子线程
5.减少图层
6.减少透明视图的存在.
7.避免离屏渲染
8.减少、合并动态库
9.减少类、分类
10.不要把所有的东西方法didfinishLaunch

十二.信号量和互斥锁的区别

  • 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)
  • 而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的
  • 也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
    互斥量和信号量的区别
  1. 互斥量用于线程的互斥,信号量用于线程的同步。
    这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
    互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
    同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
  2. 互斥量值只能为0/1,信号量值可以为非负整数。
    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
  3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

总结:
1.信号量用于同步,互斥锁用于同步.信号量不会锁定资源,而互斥锁会锁定资源.
2.信号量的值为非负整数,互斥锁只有0和1.
3.信号量可以在一个线程释放,在另一线程得到.而互斥锁只能在同一线程使用.

十三.iOS开发中静态库和动态库区别

静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。

动态库形式:.dylib和.framework
静态库形式:.a和.framework

使用静态库的好处

1,模块化,分工合作
2,避免少量改动经常导致大量的重复编译连接
3,也可以重用,注意不是共享使用

使用动态库的好处

1,使用动态库,可以将最终可执行文件体积缩小
2,使用动态库,多个应用程序共享内存中得同一份库文件,节省资源
3,使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。

  • 库里面都是函数,是一种可执行代码的二进制格式,可以被载入内存中执行
  • 静态库:
  1. 在程序编译时已经加入到可执行文件中
  2. 不能再多个程序间共享
  3. .a或.framework结尾
  • 动态库:
  1. 在程序运行时才去载入该库
  2. 可以多个程序间共享
  3. .tbd或.framework结尾
  • tips:苹果系统提供的framework(UIKit等)全部属于动态库,可在多个程序间共享.
动态库和静态库的使用时机
  • iOS8以后苹果允许上架自写动态库,但是会对动态库签名,所以不可以在线更新动态库
  • 一般都选择制作静态库
  • 动态库可以使用的两个地方 1.App Extention 2. 企业级应用

网络优化

相关文章

  • iOS-面试题-OC基础篇 (3) - (1)

    前言:面试笔试都是必考语法知识点。请认真复习和深入研究OC。 目录:iOS-面试题-OC基础篇 (1) - (84...

  • iOS-面试题-OC基础篇 (2) - (83)

    前言:面试笔试都是必考语法知识点。请认真复习和深入研究OC。 目录:iOS-面试题-OC基础篇 (1) - (84...

  • 网络

    iOS面试备战-网络篇[https://www.jianshu.com/p/0e5b22cd3409]IOS-网络...

  • iOS-面试

    一.NSObjcet对象在内存中占多少个字节? 点进去NSObject,发现NSObject内部只有一个Class...

  • iOS-面试

    面试知识点 block 什么是block是将函数及其上下文封装起来的对象 block的截获变量对不同变量类型分别是...

  • iOS-私有API与runtime

    iOS-私有API与runtime iOS-私有API与runtime

  • iOS-代码混淆加固策略

    iOS-代码混淆加固策略 iOS-代码混淆加固策略

  • iOS-性能优化深入探究

    iOS-性能优化深入探究 iOS-性能优化深入探究

  • iOS-自动打包及分发(三)

    iOS-自动打包及分发(一)iOS-自动打包及分发(二)iOS-自动打包及分发(三) 废话不多说了,上正文: 一、...

  • iOS-自动打包及分发(二)

    iOS-自动打包及分发(一)iOS-自动打包及分发(二)iOS-自动打包及分发(三) 本篇介绍:自动打包及分发xc...

网友评论

      本文标题:iOS-面试

      本文链接:https://www.haomeiwen.com/subject/wwjgrqtx.html