iOS面试题01-基础篇

category 和 extension 的区别

define 和 const常量有什么区别?

  • define在预处理阶段进行替换,const常量在编译阶段使用
  • 宏不做类型检查,仅仅进行替换,const常量有数据类型,会执行类型检查
  • define不能调试,const常量可以调试
  • define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段只有一份copy,效率更高
  • define可以定义一些简单的函数,const不可以

char *const p 与 const char * p的区别

  • char *const p --- p++这样的操作不合法 , *p='3' 合法
  • const char * p --- p++合法 , *p='3'不合法

block和weak修饰符的区别?

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

static关键字的作用

  • 函数(方法)体内 static 变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
  • 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
  • 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明 它的模块内;
  • 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
  • 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量

堆和栈的区别

从管理方式来讲
    对于栈来讲,是由编译器自动管理,无需我们手工控制;
    对于堆来说,释放工作由程序员控制,容易产生内存泄露(memory leak)
从申请大小大小方面讲
    栈空间比较小
    堆控件比较大
从数据存储方面来讲
    栈空间中一般存储基本类型,对象的地址
    堆空间一般存放对象本身,block的copy等
复制代码

风格纠错题

typedef enum {
    UserSex_Man,
    UserSex_Woman
}UserSex;

@interface UserModel:NSObject
@property (nonatomic,strong) NSString * name;
@property (nonatomic,strong) int age;
@property (nonatomic,strong) UserSex sex;

-(id)initUserModelWithUserName:(NSString*)name withAge:(int)age;
-(void)doLogIn;
@end
复制代码

修改后如下

typedef NS_ENUM(NSInteger, CYLSex)
{
  CYLSexMan,
  CYLSexWoman
};

@interface CYLUser : NSObject<NSCopying>

@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSUInteger age;
@property (nonatomic, assign, readwrite) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end
复制代码

Objective-C使用什么机制管理对象内存?

  • MRC 手动引用计数
  • ARC 自动引用计数,现在通常ARC 通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了

ARC通过什么方式帮助开发者管理内存?

  • 通过编译器在编译的时候,插入类似内存管理的代码

ARC下还会存在内存泄露吗?

  • 循环引用会导致内存泄露
  • Objective-C对象与CoreFoundation对象进行桥接的时候如果管理不当也会造成内存泄露
  • CoreFoundation中的对象不受ARC管理,需要开发者手动释放

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

首先明白什么情况使用weak关键字?
    在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性,代理属性也可使用assign
    自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong,但是建议使用weak
weak 和 assign的不同点
    weak策略在属性所指的对象遭到摧毁时,系统会将weak修饰的属性对象的指针指向nil,在OC给nil发消息是不会有什么问题的;如果使用assign策略在属性所指的对象遭到摧毁时,属性对象指针还指向原来的对象,由于对象已经被销毁,这时候就产生了野指针,如果这时候在给此对象发送消息,很容造成程序奔溃
    assigin 可以用于修饰非OC对象,而weak必须用于OC对象
复制代码

@property 的本质是什么?

  • @property其实就是在编译阶段由编译器自动帮我们生成ivar成员变量,getter方法,setter方法

@protocol 和 category 中如何使用 @property

  • 在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
  • category 使用 @property也是只会生成setter和getter方法声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数objc_setAssociatedObject,objc_getAssociatedObject

@property后面可以有哪些修饰符?

原子性---nonatomic特质
    如果不写默认情况为atomic(系统会自动加上同步锁,影响性能)
    在iOS开发中尽量指定为nonatomic,这样有助于提高程序的性能
读/写权限---readwrite(读写)、readooly (只读)
内存管理语义---assign、strong、 weak、unsafe_unretained、copy
方法名---getter=、setter=
复制代码

@property (nonatomic, getter=isOn) BOOL on;

// setter=这种不常用,也不推荐使用。故不在这里给出写法

不常用的:nonnull,null_resettable,nullable
复制代码

使用atomic一定是线程安全的吗?

不是,atomic的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。
举例:声明一个NSMutableArray的原子属性stuff,此时self.stuff 和self.stuff = othersulf都是线程安全的。但是,使用[self.stuff objectAtIndex:index]就不是线程安全的,需要用互斥锁来保证线程安全性
复制代码

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

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

怎么用 copy 关键字?

NSString、NSArray、NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性
block也经常使用copy关键字
    block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.
    在ARC中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但是建议写上copy,因为这样显示告知调用者“编译器会自动对 block 进行了 copy 操作”
复制代码

用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性,造成没有修改此NSString但却改变了的"奇怪"现象!
复制代码

这个写法会出什么问题: @property (copy) NSMutableArray *array;

因为copy策略拷贝出来的是一个不可变对象,然而却把它当成可变对象使用,很容易造成程序奔溃
这里还有一个问题,该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明nonatomic可以节省这些虽然很小但是不必要额外开销,在iOS开发中应该使用nonatomic替代atomic
复制代码

如何让自定义类可以用 copy 修饰符?如何重写带 copy 关键字的 setter?

若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyiog与NSMutableCopying协议,不过一般没什么必要,实现NSCopying协议就够了
复制代码

+(void)load; +(void)initialize;有什么用处?

+(void)load;
    当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息
    load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类
    由于 load 方法会在类被import 时调用一次,而这时往往是改变类的行为的最佳时机,在这里可以使用例如method swizlling 来修改原有的方法
    load 方法不会被类自动继承
+(void)initialize;
    也是在第一次使用这个类的时候会调用这个方法,也就是说 initialize也是懒加载
总结:
    在Objective-C中,runtime会自动调用每个类的这两个方法
    +load会在类初始加载时调用
    +initialize会在第一次调用类的类方法或实例方法之前被调用
    这两个方法是可选的,且只有在实现了它们时才会被调用
    两者的共同点:两个方法都只会被调用一次
复制代码

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

Foundation框架是使用OC实现的,Core Foundation是使用C实现的

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); // 这一句, 就等同于上一句

    MRC环境:直接强转

    -(void)bridgeInMRC
    {
      // 将Foundation对象转换为Core Foundation对象,直接强制类型转换即可
      NSString *strOC1 = [NSString stringWithFormat:@"xxxxxx"];
      CFStringRef strC1 = (CFStringRef)strOC1;
      NSLog(@"%@ %@", strOC1, strC1);
      [strOC1 release];
      CFRelease(strC1);

      // 将Core Foundation对象转换为Foundation对象,直接强制类型转换即可
      CFStringRef strC2 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);
      NSString *strOC2 = (NSString *)strC2;
      NSLog(@"%@ %@", strOC2, strC2);
      [strOC2 release];
      CFRelease(strC2);
    }
复制代码

KVO内部实现原理

KVO是基于runtime机制实现的
当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类
复制代码

如何手动触发一个value的KVO

自动触发的场景:在注册KVO之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了
想知道如何手动触发,必须知道自动触发 KVO 的原理,见上面的描述
手动触发演示
复制代码
@property (nonatomic, strong) NSDate *now;

- (void)viewDidLoad
{
    [super viewDidLoad];

    // “手动触发self.now的KVO”,必写。
    [self willChangeValueForKey:@"now"];

    // “手动触发self.now的KVO”,必写。
    [self didChangeValueForKey:@"now"];
}
复制代码

若一个类有实例变量NSString *_foo,调用setValue:forKey:时,是以foo还是_foo作为key?

都可以
复制代码

KVC的keyPath中的集合运算符如何使用?

必须用在集合对象上或普通对象的集合属性上
简单集合运算符有@avg, @count , @max , @min ,@sum
格式 @"@sum.age" 或 @"集合属性.@max.age"???
复制代码

KVC和KVO的keyPath一定是属性么?

可以是成员变量
复制代码

如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?

如何自己动手实现KVO

apple用什么方式实现对一个对象的KVO?

此题就是问KVO的实现原理
复制代码

转载于:https://juejin.im/post/5a6a9c916fb9a01c94060f41

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值