iOS面试题收纳-基础之OC

235 阅读33分钟

Cocoa 和 Cocoa Touch是什么

  • Cocoa包含Foundation和AppKit框架,用于开发Mac OS X系统的应用程序

  • Cocoa Touch包含Foundation和UIKit框架,用于开发iOS系统的应用程序

  • Cocoa Touch底层技术架构主要分为4层

    • 可触摸层Cocoa Touch:UI组件,触摸事件和事件驱动,系统接口
    • 媒体层 Media:音视频播放,动画,2D和3D图形
    • Core Service:核心服务层,底层特性,文件,网络,位置服务区等
    • Core OS:内存管理,底层网络,硬盘管理

为什么说OC是一门动态语言

OC作为一门面向对象的语言,具有封装、继承、多态的语言特性,可以在运行时动态改变类的结构:添加新函数、删除旧函数等,所以说它是一门动态语言

动态类型

  1. 一个变量所指向的类型推迟到运行时才具体知道是什么类型,最直接的就是id类型。
  2. 静态类型是强类型,而动态类型属于弱类型,运行时决定接受者

动态绑定

  1. 将调用方法的确定也推迟到运行时。
  2. 在编译时,方法的调用并不和代码绑定在一起,只有在消息发送出来之后,才确定被调用的代码,需要传什么参数进去

动态加载

在运行期间加载需要的资源或可执行代码

对于语句NSString * obj = [[NSData alloc] init]obj在编译时和运行时分别是什么类型?

编译时是NSString的类型;运行时是NSData类型的对象

对于OC来讲,它最大优缺点是什么?

优点

  1. OC是C语言的超集,在C语言基础上增加了面向对象特性,,开发使用起来方便高效
  2. 分类可以快速扩展类的方法,扩展模块之间相互不影响
  3. 运行时特性,动态特性提高了编程的灵活性
  4. OC可以与C / C++进行混编

缺点

  1. 不支持多继承,多继承可以使用分类,协议,消息转发来弥补
  2. 不支持运算符重载
  3. 使用动态运行时类型,所有的方法都是函数调用,所以很多编译时优化方法都用不到,如内联函数等,性能低劣
  4. 执行效率比C低,语法怪异

什么是懒加载

  • 懒加载 也叫做 延迟加载,它的核心思想是把对象的实例化尽量延迟,在需要使用的时候才会初始化。这样做的好处是可以减轻大量对象实例化对资源的消耗
  • 另外懒加载把对象的实例化代码抽取、独立出来,提高了代码的可读性,以便更好的组织代码

什么是类工厂方法?

类工厂方法就是用来快速创建对象的类方法, 它可以直接返回一个初始化好的对象

  1. 一定是类方法
  2. 返回值需要是 id/instancetype 类型

为什么iOS 9 以后通知不再需要手动移除

  1. 在 MRC 时代,通知中心持有的是注册者的 unsafe_unretained 指针,在注册者被回收时若不对通知进行手动移除,则指针指向被回收的内存区域,变为野指针。此时发送通知会造成 crash 。
  2. 在 iOS 9 以后,通知中心持有的是注册者的 weak 指针,这时即使不对通知进行手动移除,指针也会在注册者被回收后自动置空

id、nil、Nil、NULL、NSNull的区别

  • id 声明的对象具有运行时的特性,即可以指向任意类型的OC对象
  • nil 是一个实例对象值,常用于清空一个对象和作为arrayWithObjects的结束符
  • Nil 是一个类对象值,常用于清空一个类对象
  • NULL 指向基本数据类型的空指针(C语言的变量的指针为空)
  • NSNull 是一个对象,常用于需要占位的场合

self. 跟 self-> 区别?

  • self. 是调用getter或者setter方法,self是一个指向当前对象的指针
  • self-> 是直接访问成员变量

#include、#import、@class、#import<>、 #import“”的区别

  • 在C语言中,使用 #include 来引入头文件,防止重复导入需要用#ifndef...#define...#endif宏分割
  • 在OC语言中,使用#import来引入头文件,不会引起交叉编译,可以防止重复引入和避免头文件递归引入
  • @class用来告诉编译器有这样一个类,编译代码时不报错,也不会拷贝头文件,可以解决头文件的相互引用。仅在需要使用该类时使用 #import引入。
  • #import<> 用于引用系统头文件,会在系统头文件内寻找
  • #import“” 用于引用本工程的头文件,它会先在工程中找,找不到再去引用系统同名头文件

id、instancetype的区别

  • id 可以作为方法的返回值以及参数类型,也可以用来定义变量。编译器不检查id的类型
  • instancetype 只能作为方法的返回值
  • instancetype对比id的好处就是:能精确的限制返回值的具体类型

NSObject和id的区别

  • NSObject 和 id都可以指向任何对象
  • NSObject 对象会在编译时进行检查,需要强制类型转换
  • id类型不会在编译时检查,不需要强制类型转换

isEqual、isEqualToString、==、hash区别

  • == 比较两个对象的内存地址,若一样就返回TRUE,若不一样就返回FALSE
  • isEqual:首先检查指针的等同性,然后是类的等同性,最后对对象的属性和变量检查,比较成功返回true
  • isEqualToString:字符串比较,只比较字符串本身的内容是否一致,不比较内存地址
  • hash 是一个类方法,任何类都可以调用这个方法,返回的结果是一个NSInteger值(如果两个对象相等,那么他们的hash值一定相等,但是,如果两个对象的哈希值相等是不能一定推出来这两个对象是相等的)

isMemberOfClass 和 isKindOfClass 联系与区别

  • 联系:两者都能检测一个对象是否是某个类的成员
  • 区别:isKindOfClass 不仅用来确定一个对象是否是一个类的成员,也可以用来确定一个对象是否是派生自该类的成员 ,而isMemberOfClass 只能做到第一点

方法和选择器有何不同

  1. selector是一个方法的名字,method是一个组合体,包含了名字和实现。
  2. 通过一个selector可以找到方法地址,进而调用一个方法

new关键字作用是什么

  • 向计算机(堆区)申请内存空间

  • 初始化实例变量

  • 返回所申请空间的首地址

alloc init和new的区别

  • alloc的作用是分配内存给对象,使对象不被释放,将地址返回给指针

  • init 就是为分配好的内存进行初始化

  • new和alloc/init在功能上几乎是一致的,分配内存并完成初始化。

    差别在于,采用new的方式只能采用默认的init方法完成初始化,采用alloc的方式可以用其他定制的初始化方法

OC中的NSInteger 和int 有什么区别

  • NSInteger实际上一个typedef

    #if __LP64__ || 0 || NS_BUILD_32_LIKE_64
    typedef long NSInteger;
    typedef unsigned long NSUInteger;
    #else
    typedef int NSInteger;
    typedef unsigned int NSUInteger;
    #endif
    
  • 在32位操作系统上,NSInteger 等价于 int,即32位

  • 在64位操作系统上,NSInteger 等价于 long,即64位

OC的数据类型和C的基本数据类型有什么区别?

  • OC的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,
  • C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值

类方法和实例方法有什么区别

  • 类方法:

    • 类方法是属于类对象的
    • 类方法只能通过类对象调用
    • 类方法中的self是类对象
    • 类方法可以调用其他的类方法
    • 类方法中不能访问成员变量
    • 类方法中不能直接调用对象方法
  • 实例方法:

    • 实例方法是属于实例对象的
    • 实例方法只能通过实例对象调用
    • 实例方法中的self是实例对象
    • 实例方法中可以访问成员变量
    • 实例方法中直接调用实例方法
    • 实例方法中也可以调用类方法(通过类名)

category 和 extension 的区别

  • 分类有名字,类扩展没有分类名字,是一种特殊的分类
  • 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法

Foundation对象与CoreFoundation对象有什么区别

  • Foundation 对象是OC的,在MRC下需要手动管理内存,ARC下不需要手动管理

  • Core Foundation对象是C对象,MRC和ARC都需要手动管理内存

  • Foundation对象 和 Core Foundation对象间的转换:俗称桥接

    ARC环境桥接关键字:

    // 可用于Foundation对象 和 Core Foundation对象间的转换
    __bridge
    
    // 用于Foundation对象 转成 Core Foundation对象
    __bridge_retained
    
    // Core Foundation对象 转成 Foundation对象
    __bridge_transfer
    
    • Foundation对象 转成 Core Foundation对象

      • 使用__bridge桥接

        • 如果使用__bridge桥接,它仅仅是将strOC的地址给了strC, 并没有转移对象的所有权,也就是说, 如果使用__bridge桥接, 那么如果strOC释放了,strC也不能用了
        • 注意:在ARC条件下,如果是使用__bridge桥接,那么strC可以不用主动释放, 因为ARC会自动管理strOC和strC
        NSString *strOC1 = [NSString stringWithFormat:@"abcdefg"];
        CFStringRef strC1 = (__bridge CFStringRef)strOC1;
        NSLog(@"%@ %@", strOC1, strC1);
        
      • 使用__bridge_retained桥接

        • 如果使用__bridge_retained桥接,它会将对象的所有权转移给strC, 也就是说, 即便strOC被释放了, strC也可以使用
        • 注意:在ARC条件下,如果是使用__bridge_retained桥接,那么strC必须自己手动释放,因为桥接的时候已经将对象的所有权转移给了strC,而C语言的东西不是不归ARC管理的
        NSString *strOC2 = [NSString stringWithFormat:@"abcdefg"];
        //    CFStringRef strC2 = (__bridge_retained CFStringRef)strOC2;
        CFStringRef strC2 = CFBridgingRetain(strOC2);// 这一句, 就等同于上一句
        CFRelease(strC2);
        
    • Core Foundation对象 转成 Foundation对象

      • 使用__bridge桥接

        • 如果使用__bridge桥接,它仅仅是将strC的地址给了strOC, 并没有转移对象的所有权
        • 也就是说如果使用__bridge桥接,那么如果strC释放了,strOC也不能用了
        CFStringRef strC3 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);
        NSString *strOC3 = (__bridge NSString *)strC3;
        CFRelease(strC3);
        
      • 使用__bridge_transfer桥接

        • 如果使用__bridge_transfer桥接,它会将对象的所有权转移给strOC, 也就是说, 即便strC被释放了, strOC也可以使用
        • 如果使用__bridge_transfer桥接, 他会自动释放strC, 也就是以后我们不用手动释放strC
        CFStringRef strC4 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);
        //     NSString *strOC = (__bridge_transfer NSString *)strC;
        NSString *strOC4 = CFBridgingRelease(strC4); // 这一句, 就等同于上一句
        

NSNotification、Block、Delegate、KVO的区别

  • Block和Delegate属于代理模式,是一对一的关系,Notification和KVO是观察者模式,是一对多的关系
  • 代理模式相对观察者模式效率比较高
  • notification通过维护一个array,实现一对多消息的转发
  • Delegate需要定义协议方法,代理对象实现协议方法,需要两者之间必须建立联系,不然没法调用代理的方法;
  • notification不需要两者之间有联系
  • Block更加简洁,不需要定义繁琐的协议方法,但通信事件比较多的话,建议使用Delegate

__block__weak修饰符的区别?

  • __block不管是ARC还是MRC模式下可以使用,可以修饰对象,也可以修饰基本数据类型
  • __weak 只能在ARC模式下使用,只能修饰对象(NSString),不能修饰基本数据类型
  • __block修饰的对象可以在block中被重新赋值,__weak修饰的对象不可以

postNotification是同步调用还是异步调用?

  1. 同步调用。
  2. 当调用addObserver方法监听通知,然后调用postNotification抛通知,postNotification会在当前线程遍历所有的观察者,然后依次调用观察者的监听方法
  3. 调用完成后才会去执行postNotification后面的代码。

如何实现异步监听通知?

通过addObserverForName:object:queue:usingBlock来实现异步通知。

是否可以把比较耗时的操作放在 NSNotification中

  • 首先必须明确通知在哪个线程中发出,那么处理接受到通知的方法也在这个线程中调用
  • 如果在异步线程发的通知,那么可以执行比较耗时的操作;
  • 如果在主线程发的通知,那么就不可以执行比较耗时的操作

类别的作用?

category 可以在不获悉,不改变原来代码的情况下往里面添加新的方法,只能添加,不能删除修改,并且如果类别和原来类中的方法产生名称冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级。类别不能添加实例变量

  • 将类的实现分散到多个不同文件或多个不同框架中

  • 创建对私有方法的前向引用

  • 向对象添加非正式协议

C和 OC 如何混编

  • .m文件 可以编写 OC语言 和 C 语言代码
  • .cpp文件 只能识别C++ 或者C语言(C++兼容C)
  • .mm文件 主要用于混编 C++和OC代码,可以同时识别OC,C,C++代码

Swift 和OC 如何调用

  • Swift 调用 OC代码 需要创建一个 <Module>-Bridging-Header.h 的桥接文件,在文件内引入需要调用的OC代码头文件
  • OC 调用 Swift代码 直接引入 <Module>-Swift.h文件。Swift如果需要被OC调用,使用@objc 对方法或者属性进行修饰

OC中定义一个枚举的几种方法

  • 因为 OC 是兼容 C 的,所以可以使用 C 语言风格的 enum 进行定义
  • 使用NS_ENUM宏进行定义
  • 使用NS_OPTIONS宏进行定义

NS_ENUM为定义通用性枚举,只能单选。NS_OPTIONS为定义位移枚举,可多选

怎么理解协议以及协议的默认修饰符

  • 协议规定了一系列的行为(方法)和数据(属性),主要用在代理模式中
  • OC中协议默认是@required必须实现的,使用@optional修饰后变为可选择实现

OC中集合的注意事项和相互之间的区别

NSArray/NSMuatbleArray/NSPointerArray

  • NSArray 初始化后,元素就不可再增删

  • NSMuatbleArray 初始化后,可以随时添加或删除元素对象

  • NSPointerArrayNSMuatbleArray 相似,区别是可以指定对元素的强/弱引用

indexOfObjectindexOfObjectIdenticalTo 的区别

  • 都是来判断某一对象是否是 Array 集合内的元素,如果是则返回该对象在集合内的索引
  • 区别就在于两个 API 判定的依据
    • indexOfObject 会使用 isEqualTo 方法将 Object 与集合元素进行比较
    • indexOfObjectIdenticalTo 则会比较 Object 与集合元素的指针是否相同

NSDictionary/NSMutableDictionary/NSMapTable

  • NSDictionary 初始化后就不可再增删键值对
  • NSMutableDictionary 初始化后可以随时添加或删除键值对
  • NSMapTable 类似 NSMutableDictionary 可以指定对 value 的强/弱引用
  • 当某个键值对添加到 Dictionary 时,Dictionary 会对 key 进行深拷贝,而对 value 进行浅拷贝,已经添加到集合内部的键值对,key 值将不可更改
  • 作为 key 的对象都需要满足哪些条件呢
    • key 必须遵循 NSCopying 协议,因为当元素渐入字典后,会对 key 进行深拷贝
    • key 必须实现 hashisEqual 方法

NSSet/NSMuatbleSet/NSCountedSet/NSHashTable

  • NSSet 初始化后就不可再增删元素
  • NSMutableSet 初始化后可随时增删元素
  • NSCountedSet 每个元素都带有一个计数,添加元素,计数为一。重复添加某个元素则计数加一;移除元素计数减一,计数为零则移除
  • NSHashTableNSMutableSet 类似,区别是可指定对元素的强/弱引用

NSMutableDictionary 中使用setValueForKey 和 setObjectForKey有什么区别

  • 一般情况下,给NSMutableDictionary 发送setValue 仍然是调用了 setObject方法,如果参数 value 为 nil,则会调用removeObject 移除这个键值对
  • setObjectForKey 是 NSMutableDictionary特有的,value 不能为nil,否则会崩溃
  • setValueForKey 是KVC的方法,key 必须是字符串类型,setObject 的 key 可以是任意类型

NSCache 和NSDictionary 区别

  • NSDictionary的key必须符合nscopying协议,在设置值时会copy key
  • NSCache可以提供自动删减缓存功能,而且保证线程安全,与字典不同,不会拷贝键
  • NSPurgeableData搭配NSCache使用,可以自动清除数据

NSArray 和 NSSet区别

  • NSSet和NSArray功能性质一样,用于存储对象,属于集合。
  • NSSet,NSArray都是类,只能添加对象,如果需要加入基本数据类型(int,float,BOOL,double等),需要将数据封装成NSNumber类型
  • NSSet属于 “无序集合”,采用hash算法计算存储位置,查询速度快,保证了元素的唯一性,在内存中存储方式是不连续的
  • NSArray是 “有序集合” 它内存中存储位置是连续的

OC类可以多继承么?可以实现多个接口么?

  • OC的类不可以有多继承,OC里面都是单继承

  • 多继承可以用protocol委托代理来模拟实现,可以通过实现多个接口完成OC的多重继承

为什么不要在Category中重写一个类原有的方法

  1. Category没有办法去代替子类,它不能像子类一样通过super去调用父类的方法实现。

    如果Category中重写覆盖了当前类中的某个方法,那么这个当前类中的原始方法实现,将永远不会被执行,这在某些方法里是致命的

  2. 一个Category也不能可靠的覆盖另一个Category中相同的类的相同的方法。

    例如UIViewController+A与UIViewController+B,都重写了viewDidLoad,我们就无法控制谁覆盖了谁

  3. 要重写方法,我们首推通过子类重写父类的方法,在一些不方便重写的情况下,我们也可以在category中用runtime进行method swizzling(方法的偷梁换柱)来实现。

OC成员变量的修饰符及作用范围

  • @private,本类可以访问,子类和其他类不可以访问
  • @protected(默认修饰符),本类和子类可以访问,其他类不可以访问
  • @package,对于framework内部相当于@protected,对于framework外部相当于@private
  • @public ,本类、子类、其他类都可以访问

@proprety的本质和作用

  • @property = ivar + getter + setter
    • 在.h文件自动生成getter/setter方法声明
    • 在.m文件生成成员变量和getter/setter方法的实现
  • 可以手动实现getter/setter方法。如果同时实现了getter和setter,那么还需要指定成员变量
  • 也可以使用@synthesize myName = myString自己指定成员变量

@proprety修饰符说明

  • 原子性:atomic/nonatomic
    • 默认为 atomic,系统会自动加上同步锁(自旋锁),影响性能

    • 建议使用 nonatomic,可以提高访问的性能

  • 读写权限readonly,readwrite
  • 指定读写方法getter,setter
  • 内存管理语义strong,assign,weak,copy,unsafe_unretained
  • 是否可以为空nullable,nonnull,null_resettable,null_unspecified
  • 类属性class

ARC下,不显式指定属性关键字时,默认的关键字都有哪些

  • 基本数据类型: atomic, readwrite, assign
  • 普通的OC 对象: atomic, readwrite, strong

atomic、nonatomic区别以及作用

  • 主要区别就是系统自动生成的getter/setter方法不一样

    • atomic 系统自动生成的getter/setter方法会进行加锁操作
    • nonatomic 系统自动生成的getter/setter方法不会进行加锁操作
  • atomic不是线程安全的

    • 系统生成的getter/setter方法会进行加锁操作,**注意:**这个锁仅仅保证了getter/setter存取方法的线程安全
    • atomic 可以保证多线程访问时,对象是未被其他线程销毁的(比如:如果当一个线程正在get或set时,又有另一个线程同时在进行release操作,可能会直接crash)

讲一下atomic的实现机制

  1. atomic 表示是原子性的,用来声明属性时,编译器自动生成getter/setter方法,最终会调用objc_getPropertyobjc_setProperty方法来进行存取属性
  2. 这两个方法内部使用了os_unfair_lock进行加锁,来保证读写的原子性。
  3. 锁都在PropertyLocks中保存着(在iOS平台会初始化8个,mac平台64个)。在用之前,会把锁都初始化好
  4. 在需要用到时,用对象的地址加上成员变量的偏移量为key,去PropertyLocks中去取。因此存取时用的是同一个锁,所以atomic能保证属性的存取时是线程安全的。
  5. 注:由于锁是有限的,不同对象,不同属性的读取用的也可能是同一个锁

atomic为什么不能保证绝对的线程安全?

  1. atomic在getter/setter方法中加锁,仅保证了存取时的线程安全
  2. 假设我们的属性是@property(atomic)NSMutableArray *array; 可变的容器时,无法保证对容器的修改是线程安全的

什么情况使用 weak 关键字,相比 assign 有什么不同

什么情况使用 weak 关键字

  • 在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性,代理属性也可使用assign
  • 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong,但是建议使用weak

weak 和 assign 的不同点

  • weak :在属性所指的对象遭到销毁时,系统会将 weak 修饰的属性指向 nil,在 OCnil 发消息是不会有什么问题的
  • assign :在属性所指的对象遭到销毁时,assign属性还指向原来的对象,由于对象已经被销毁,这时候就产生了野指针,如果这时候再给此对象发送消息,很容造成程序崩溃
  • assign 可以用于修饰非 OC 对象,而 weak 必须用于 OC 对象

代理使用 weak 还是 assign

  • 建议使用 weak,它指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制
  • 可以使用 assign,也有weak的效果,对于使用 assign修饰的delegate,在对象释放前需要将 delegate 指针设置为 nil,不然会产生野指针

用@property声明的NSString经常使用copy关键字,为什么

  • NSString、NSArray、NSDictionary 等经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary
  • 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本
  • 他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性

这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr

  • 问题:添加、删除、修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃
  • 原因:是因为 copy 就是复制一个不可变 NSArray 的对象,不能对 NSArray 对象进行添加、修改

如何让自定义类可以用 copy 修饰符

  1. 若想令自己所写的对象具有拷贝功能,需实现 NSCopying 协议
  2. 如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopyiog 与 NSMutableCopying 协议
// 实现不可变版本拷贝
- (id)copyWithZone:(NSZone *)zone; 
// 实现可变版本拷贝
- (id)mutableCopyWithZone:(NSZone *)zone;
// 重写带 copy 关键字的 setter
- (void)setName:(NSString *)name {
    _name = [name copy];
}

在某个方法中 self.name = _name,name = _name 它 们有区别吗,为什么?

  • 前者是存在内存管理的setter方法赋值,它会对_name对象进行保留或者拷贝操作
  • 后者是普通的变量赋值

@synthesize 和 @dynamic 分别有什么作用

  • @property 有两个对应的词,一个是@synthesize,一个是@dynamic
  • 如果 @synthesize 和@dynamic都没写,那么默认的就是@syntheszie var = _var
  • @synthesize 如果你没有手动实现 getter/setter 方法,那么编译器会自动为你加上这两个方法
  • @dynamic 告诉编译器属性的 getter/setter方法由用户自己实现,不自动生成(当然对于 readonly 的属性只需提供 getter 即可)

@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么

  • 如果指定了成员变量的名称,会生成一个指定的名称的成员变量
  • 如果这个成员已经存在了就不再生成了
  • 如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:如果没有指定成员变量的名称会自动生成一个属性同名的成员变量

在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景

  • 同时重写了 setter 和 getter 时
  • 重写了只读属性的 getter 时
  • 使用了 @dynamic 时
  • 在 @protocol 中定义的所有属性
  • 在 category 中定义的所有属性
  • 重载的属性,当你在子类中重载了父类中的属性,必须 使用@synthesize来手动合成ivar

什么是沙盒机制

每个iOS程序都有一个独立的文件系统(存储空间),而且只能在对应的文件系统中进行操作,此区域被称为沙盒。应用必须待在自己的沙盒里,其他应用不能访问该沙盒

沙盒目录结构是怎样的?

  • Application:存放程序源文件,上架前经过数字签名,上架后不可修改
  • Documents:常用目录,iCloud备份目录,存放数据
  • Library
    • Caches:存放体积大又不需要备份的数据
    • Preference:设置目录,iCloud会备份设置信息
  • tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

说一下OC的反射机制

在OC中,反射是指程序在运行时,获取和修改类的信息,基于OC的Runtime机制,主要从三个层面使用反射

  • NSObject类集成的一些反射API
  • 系统Foundation框架提供了一些方法反射的API
  • 使用objc/runtime提供的一些API

反射常用的方法

  • isMemberOfClass // 对象是否是某个类型的对象
  • isKindOfClass // 对象是否是某个类型或某个类型子类的对象
  • respondsToSelector // 是否能响应某个方法
  • conformsToProtocol // 是否遵循某个协议

class方法和object_getClass方法有什么区别?

  1. 实例class方法就直接返回object_getClass(self),类class方法直接返回self
  2. object_getClass(类对象),则返回的isa指向的元类

反射的应用场景

  • JSON与模型之间的相互转换
  • Method Swizzling
  • KVO的实现原理
  • 实现NSCoding的自动归档和自动解档

实现description方法能取到什么效果

当使用log打印该对象或断点po对象时,可以详细的知道该对象的信息,方便代码调试

静态库和动态库的区别

静态库

  1. 以.a 和 .framework为文件后缀名
    • .a 主要是二进制文件,不包含资源。需要自己添加头文件
    • .framework 可以包含头文件 + 资源信息

好处

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

动态库

  1. 以.tbd(之前叫.dylib) 和 .framework 为文件后缀名

好处

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

区别

静态库

  1. 链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝

动态库

  1. 链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用,节省内存

代理的作用

代理又叫委托,是一种设计模式,是对象与对象之间的通信交互,解除了对象之间的耦合性

  • 改变或传递控制链。允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以减少框架复杂度

为什么代理要用weak?

  • 避免循环引用。
  • 如果使用strong修饰且代理是self的话,会出现循环引用,从而造成内存泄漏

代理的delegate和dataSource有什么区别?

  • 代理的delegatedataSource是为了职责分离
    • delegate负责事件处理
    • datasource负责数据源处理

block和代理的区别

  • delegateblock都可以实现回调传值
    • block写法简练,可以直接访问上下文,代码阅读性好,适合与状态无关的操作,更加面向结果,使用过程中需要注意避免造成循环引用。
    • delegate更像一个生产流水线,每个回调方法是生产线上的一个处理步骤,一个回调的变动可能会引起另一个回调的变动,其更加面向过程
  • 一般情况下,简单功能的回调用block,3个以上系列函数的回调选择delegate
  • delegate运行成本低,block的运行成本高
  • block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。
  • delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作

对self = [super init]的理解

参考链接 juejin.cn/post/708045…

[super init] 到底做了什么?

[super init] 是面向对象的体现,调用父类的init方法来为子类从父类继承来的属性进行初始化

当init方法需要重新alloc一块空间时,正确的写法如下

- (id) init {
    id tmp = self;      
    // [self class]将获得self指向的实例对应的类实例
    self = [[self class] alloc];          
    [tmp release];
    //other staffs
    return self;
}

为什么把**[super init]**的值赋值给self?

为了防止父类的初始化方法释放了self指向的空间后,又重新alloc了一块空间。这样的话,[super init]可能alloc失败,这时就不再执行if中的语句。

super作为消息接受者的实质是什么

super并不是真正的指针,[super message]的实质是由self来接受父类的message。

**switch **语句 **if **语句区别与联系

  • 均表示条件的判断
  • switch语句表达式只能处理整型、字符型和枚举类型,而选择流程语句则没有这样的限制。
  • switch语句比选择流程控制语句效率更高

BAD_ACCESS在什么情况下出现

这种问题在开发时经常遇到。原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等

结构体与数组有什么区别?

  1. 结构体可以存不同类型的元素,而数组只能存同一类型
  2. 结构体类型需要我们自已定义,数组是用别的类型加 [元素个数]
  3. 结构体内存分配方式很特别,使用对齐原则,不一定是所有元素的字节数和,而数组一定是所有元素的字节数和。
  4. 结构体指针可以指针名->结构体元素名(取元素);数组不行

浅复制和深复制的区别

  • 浅复制:只复制指向对象的指针,而不复制引用对象本身
  • 深复制:复制引用对象本身,会在堆区重新开辟新的内存块
  • 通俗的讲:浅复制好比你和你的影子,你完蛋,你的影子也完蛋;深复制好比你和你的克隆人,你完蛋,你的克隆人还活着

集合类型的深浅拷贝

数据类型copymultableCopy
不可变类型浅拷贝单层深拷贝
可变类型单层深拷贝单层深拷贝

iOS 方法内局部变量 (对象) 的内存释放过程

  1. ARC 环境下,方法在将要执行结束的时候,局部变量的指针都会被置为 nil
  2. 此时在 objc_storeStrong 函数内部,被局部变量强引用的对象会被执行 release 操作。
  3. 最终对象是否会被释放还是要取决于是否依旧有其他指针强引用 (比如: 全局变量/属性 等)

KVC中的集合运算符

简单集合操作符

作用于Array或者Set中相对于集合操作符右侧的属性(只能用在集合对象中,对象属性必须为数字类型)

  • @avg:将集合中属性键路径所指对象转换为 double,计算其平均值,返回该平均值的 NSNumber 对象。当均值为 nil 的时候,返回 0
  • @count :返回集合中对象总数的 NSNumber 对象。操作符右边没有键路径
  • @max :比较由操作符右边的键路径指定的属性值,并返回比较结果的最大值。
    • 最大值由指定的键路径所指对象的 compare: 方法决定,因此参加比较的对象必须支持和另一个对象的比较。
    • 如果右侧键路径所指对象值为 nil, 则忽略,不影响比较结果。
  • @min 和 @max 一样,但是返回的是集合中的最小值。
  • @sum 返回右侧键路径指定的属性值的总和。
    • 每一个比较值都转换为 double,然后计算值的总和,最后返回总和值的 NSNumber 对象。
    • 如果右侧键路径所指对象值为 nil,则忽略。

对象操作符

  • @distinctUnionOfObjects 和 @unionOfObjects, 返回一个由操作符右边的 key path 所指定的对象属性组成的数组。其中 @distinctUnionOfObjects 会对数组去重,而 @unionOfObjects 不会。

数组和集合操作符

  • 作用对象是嵌套的集合,也就是说,是一个集合且其内部每个元素是一个集合
    • @distinctUnionOfArrays / @unionOfArrays 返回一个数组,其中包含这个集合中每个数组对于这个操作符右面指定的 key path 进行操作之后的值。 distinct 版本会移除重复的值。
    • @distinctUnionOfSets 和 @distinctUnionOfArrays 差不多, 但是它期望的是一个包含着 NSSet 对象的 NSSet ,并且会返回一个 NSSet 对象。因为集合不能包含重复的值,所以它只有 distinct 操作。

哪些情况会导致App崩溃,可以用什么方法拦截并化解?

  1. unrecognized selector sent to instance 方法找不到
  2. 数组越界,插入空值
  3. [NSDictionary initWithObjects:forKeys:]使用此方法初始化字典时,objects和keys的数量不一致时
  4. NSMutableDictionary,setObject:forKey:或者removeObjectForKey:时,key为nil
  5. setValue:forUndefinedKey:,使用KVC对对象进行存取值时传入错误的key或者对不可变字典进行赋值
  6. NSUserDefaults 存储时key为nil
  7. 对字符串操作时,传递的下标超出范围,判断是否存在前缀,后缀子串时,子串为空
  8. 使用C字符串初始化字符串时,传入null
  9. 对可变集合或字符串使用copy修饰并进行修改操作
  10. 在空间未添加到父元素上之前,就使用autoLayout进行布局
  11. KVO在对象销毁时,没有移除KVO或者多次移除KVO
  12. 野指针访问
  13. 死锁
  14. 除0

XML数据解析方式各有什么不同?

DOM解析

  1. 必须完成DOM树的构造,在处理规模较大的XML文档时就很耗内存,占用资源较多
  2. 读入整个XML文档并构建一个驻留内存的树结构(节点树)
  3. 通过遍历树结构可以检索任意XML节点,读取它的属性和值
  4. 通常情况下,可以借助XPath查询XML节点

SAX解析

  1. 它是事件驱动模型

  2. 解析XML文档时每遇到一个开始或者结束标签、属性或者一条指令时,程序就产生一个事件进行相应的处理

  3. 一边读取XML文档一边处理,不必等整个文档加载完才采取措施

  4. 当在读取解析过程中遇到需要处理的对象,会发出通知进行处理。

    SAX相对于DOM来说更适合操作大的XML文档。

OC与 JS交互方式有哪些?

  1. 通过拦截URL, 在onStart等方法里编写逻辑

  2. 使用MessageHandler(WKWebView)

    • 当JS端想传一些数据给iOS,window.webkit.messageHandlers.<方法名>.postMessage(<数据>)
    • 在OC中处理WKScriptMessageHandler的代理方法。name和上方JS中的方法名相对应
    - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
    
  3. JavaScriptCore (UIWebView)

  4. 使用三方库WebViewJavascriptBridge,可提供 js 调OC以及O调JS

    1. 设置 webViewBridge
    _bridge = [WKWebViewJavascriptBridge bridgeForWebView:self.webView];
    [_bridge setWebViewDelegate:self];
    2. 注册handler方法,需要和 前段协商好 方法名字,是供 JS调用Native 使用的。
        [_bridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {
            // OC调用
            NSString *scanResult = @"http://www.baidu.com";
            // js 回调传参
            responseCallback(scanResult);
        }];
    3. OC掉用JS
      [_bridge callHandler:@"testJSFunction" data:@"一个字符串" responseCallback:^(id responseData) {
            NSLog(@"调用完JS后的回调:%@",responseData);
        }];
    

说一下iOS 中的APNS远程推送原理

  • iOS 系统向苹果的APNS服务器请求手机端的deviceToken
  • App接收到手机端的deviceToken,然后通过接口传递给服务器
  • App服务器需要发送推送消息时,构建消息体完成后,将消息体、需要接收推送的用户的devicetokens传递给APNS 服务器
  • APNS 服务器根据对应的 deviceToken 发送到用户的手机上

以下代码打印结果是什么

NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults];
BOOL flag = NO;
[userdefault setObject:@(flag) forKey:@"flag"];

if ([userdefault objectForKey:@"flag"]) {
    BOOL eq = [userdefault objectForKey:@"flag"];
    if (eq) {
        NSLog(@"a");
    }
  else {
        NSLog(@"b");
    }
}
else {
    BOOL eq = [userdefault objectForKey:@"flag"];
    if (eq) {
        NSLog(@"c");
    }
  else {
        NSLog(@"d");
    }
}

打印结果 a

  1. flag被包装成 oc 对象(NSNumber),OC对象有值,转 BOOL 都是 YES
  2. 这里应该使用boolForKey