【iOS】最新面试题

KVO 内部实现原理

  1. KVO是基于runtime机制实现的。
  2. 当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。
  3. 派生类在被重写的 setter 方法实现真正的通知机制(Person􏰀 NSKVONotifying_Person)

不用中间变量,用两种方法交换 A 和 B 的值

A=A+B;
B=A-B;
A=A-B;
或者
A = A^B; 
B = A^B; 
A = A^B;

runtime 实现的机制是什么

runtime,运行时机制,它是一套C语言库。
实际上我们编写的所有OC代码,最终都是转成了runtime库的东西,

比如:
类转成了runtime库里面的结构体等数据类型,
方法转成了 runtime库里面的C语言函数,平时调方法都是转成了objc_msgSend 函数(所以说OC有个消息发送机制)

因此,可以说runtime是OC的底层实现,是OC的幕后执行者 有了runtime库,能做什么事情呢?

runtime库里面包含了跟类、成员 变量、方法相关的API,比如获取类里面的所有成员变量,为类动态 添加成员变量,动态改变类的方法实现,为类动态添加新的方法等 因此,有了runtime,想怎么改就怎么改。

是否使用 Core Text 或者 Core Image

CoreText

  • 随意修改文本的样式
  • 图文混排(纯C语言)
  • 国外:Niumb

Core Image(滤镜处理)

  • 能调节图片的各种属性(对比度, 色温, 色差等)

NSNotification、KVO、delegate 的区别和用法是什么?

通知比较灵活:1个通知能被多个对象接收, 1个对象能接收多个通知

KVO性能不好(底层会动态产生新的类),只能监听某个对象属性的改 变, 不推荐使用(1个对象的属性能被多个对象监听, 1个 对象能监听 多个对象的其他属性)

代理比较规范,但是代码多(默认是1对1)

delegate 代理的作用?

代理的目的是改变或传递控制链。
允许一个类在某些特定时刻通知到其他类,而不 需要获取到那些类的指针。可以减少框架复杂度。
另外一点,代理可以理解为java 中的回调监听机制的一种类似。

简单说一下事件响应的流程?

  1. 一个 UIView 发出一个事件之后,首先上传给其父视图;
  2. 父视图上传给其所在的控制器;
  3. 如果其控制器对事件进行处理,事件传递将终止,否则继续上传父视图;
  4. 直到遇到响应者才会停止,否则事件将一直上传,直到 UIWindow。

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

因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个 不可变的副本。

如果我们使用是 strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

【复制详解】
浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指 针复制。
深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有 一层是深复制。
完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都 是对象复制。
非集合类对象的 copy 与 mutableCopy [不可变对象 copy] // 浅复制 [不可变对象 mutableCopy] //深复制
[可变对象 copy] //深复制 [可变对象 mutableCopy] //深复制
集合类对象的 copy 与 mutableCopy [不可变对象 copy] // 浅复制 [不可变对象 mutableCopy] //单层深复制
[可变对象 copy] //单层深复制
[可变对象 mutableCopy] //单层深复制
这里需要注意的是集合对象的内容复制仅限于对象本身,对象元素仍然是 指针复制

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

因为 copy 策略拷贝出来的是一个不可变对象,然而却把它当成可变对象使用,很容 易造成程序奔溃。

这里还有一个问题,该属性使用了同步锁,会在创建时生成一些额外的代码用于帮 助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然
很小但是不必要额外开销,在 iOS 开发中应该使用 nonatomic 替代 atomic

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

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

// 实现不可变版本拷贝
- (id)copyWithZone:(NSZone *)zone; // 实现可变版本拷贝
- (id)mutableCopyWithZone:(NSZone *)zone;
// 重写带 copy 关键字的 setter
- (void)setName:(NSString *)name {
    _name = [name copy];
}

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

+(void)load; 当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息。

load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子 类, 子类优先于分类

由于 load 方法会在类被 import 时调用一次,而这时往往是改变类的行为的最佳时 机,在这里可以使用例如 method swizlling 来修改原有的方法

+(void)initialize;
也是在第一次使用这个类的时候会调用这个方法,也就是说 initialize 也是懒加载。

总结:
在 Objective-C 中,runtime 会自动调用每个类的这两个方法
+load 会在类初始加载时调用
+initialize 会在第一次调用类的类方法或实例方法之前被调用

IBOutlet 连出来的视图属性为什么可以被设置成 weak?

因为父控件的 subViews 数组已经对它有一个强引用

1.首先要明确什么时候可以使用weak修饰?
一种情况是 : 在 ARC 中,在有可能出现循环引用的时候
另一种情况是 : 自身已经对它进行一次强引用,没有必要再强引用一次

2.然后看IBOutlet连出来的视图的引用关系
UIViewController →强引用→ UIView →强引用→ IBOutlet连出视图


3.综上所诉
所以问题场景满足第二种使用weak的条件, 既UIViewController不需要对IBOutlet连出来的视图再用strong修饰, 用weak即可。

什么情况使用 weak 关键字

在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性

自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong

weak修饰的属性所指的对象遭到摧毁时,属性值也会清空(nil)

请简述 UITableView 的复用机制

每次创建 cell 的时候通过 dequeueReusableCellWithIdentifier:方法创建 cell,
它先到 缓存池中找指定标识的 cell,

如果没有找到指定标识的 cell,就直接返回 nil,且会通过 initWithStyle:reuseIdentifier:创建一个 cell

当 cell 离开界面就会被放到缓存池中,以供下次复用

如何高性能的给 UIImageView 加个圆角?

不好的解决方案:
使用下面的方式会强制 Core Animation 提前渲染屏幕的离屏绘制, 而离 屏绘制就会给性能带来负面影响,会有卡顿的现象出现

self.view.layer.cornerRadius = 5;
self.view.layer.masksToBounds = YES;

正确的解决方案:使用绘图技术

- (UIImage *)circleImage {
  // NO 代表透明
  UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
  // 获得上下文
  CGContextRef ctx = UIGraphicsGetCurrentContext();
  // 添加一个圆
  CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  CGContextAddEllipseInRect(ctx, rect);
  // 裁剪 CGContextClip(ctx);
  // 将图片画上去
  [self drawInRect:rect];
  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  // 关闭上下文 UIGraphicsEndImageContext();
  return image;
}

还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给 UIImageView 添加了的圆 角,其实也是通过绘图技术来实现的

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; 
UIImage *anotherImage = [UIImage imageNamed:@"image"];

UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:50] addClip]; 
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

[self.view addSubview:imageView];

描述下 SDWebImage 里面给 UIImageView 加 载图片的逻辑

SDWebImage 中为 UIImageView 提供了一个分类 UIImageView+WebCache.h, 这个分类中有一个最常用的接口 sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后在替换占位图片 加载图片的过程大致如下:

首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以 url 作为数据的索引先在内存中寻找是否有对应的缓存

如果缓存未找到就会利用通过 MD5 处理过的 key 来继续在磁盘中查询对应的数据,

如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来

如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片下载后的图片会加入缓存中,并写入磁盘中 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来。

你是怎么封装一个 view 的

可以通过纯代码或者 xib 的方式来封装子控件建立一个跟 view 相关的模型,然后将模型数据传给 view,通过模型上的数据给 view 的子控件赋值。

/**
 * 纯代码初始化控件时一定会走这个方法
 */
- (instancetype)initWithFrame:(CGRect)frame {
  if(self = [super initWithFrame:frame]) {
      [self setup];
  }
    return self;
}

 /**
  * 通过 xib 初始化控件时一定会走这个方法 
  */
- (id)initWithCoder:(NSCoder *)aDecoder {
  if(self = [super initWithCoder:aDecoder]) {
      [self setup];
  }
      return self;
}
- (void)setup {
    // 初始化代码 
}

触摸事件的传递

触摸事件的传递是从父控件传递到子控件 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件 不能接受触摸事件的四种情况 不接收用户交互,即:userInteractionEnabled = NO 隐藏,即:hidden = YES
透明,即:alpha <= 0.01
未启用,即:enabled = NO
提示:UIImageView 的 userInteractionEnabled 默认就是 NO,因此 UIImageView 以 及它的子控件默认是不能接收触摸事件的
如何找到最合适处理事件的控件:
首先,判断自己能否接收触摸事件 可以通过重写 hitTest:withEvent:方法验证 其次,判断触摸点是否在自己身上
对应方法 pointInside:withEvent: 从后往前(先遍历最后添加的子控件)遍历子控件,重复前面的两个步骤 如果没有符合条件的子控件,那么就自己处理

事件响应者链

如果当前 view 是控制器的 view,那么就传递给控制器;如果控制器不存在,则将其传递给它的父控件。

在视图层次结构的最顶层视图也不能处理接收到的事件或消息,则将事件或消息传 递给 UIWindow 对象进行处理。

如果 UIWindow 对象也不处理,则将事件或消息传递给 UIApplication 对象

如果 UIApplication 也不能处理该事件或消息,则将其丢弃

补充:如何判断上一个响应者
如果当前这个 view 是控制器的 view,那么控制器就是上一个响应者 如果当前这个 view 不是控制器的 view,那么父控件就是上一个响应者

一个 objc 对象的 isa 的指针指向什么?有什 么作用?

每一个对象内部都有一个 isa 指针,这个指针是指向它的真实类型 根据这个指针就能知道将来调用哪个类的方法

下面的代码输出什么?

@implementation Son : Father 
- (id)init {
    if (self = [super init]) {
        NSLog(@"%@", NSStringFromClass([self class])); // Son
        NSLog(@"%@", NSStringFromClass([super class]));  //Son
    }
    return self; 
}
@end

// 答案:都输出 Son

这个题目主要是考察关于 objc 中对 self 和 super 的理解:
self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者

当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中 再找;
而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法 调用[self class] 时,会转化成 objc_msgSend 函数 id objc_msgSend(id self, SEL op, ...)

调 用 [super class] 时 , 会 转 化 成 objc_msgSendSuper 函 数 id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

第一个参数是 objc_super 这样一个结构体,其定义如下 struct objc_super { __unsafe_unretained id receiver;
__unsafe_unretained Class super_class; };

第一个成员是 receiver, 类似于上面的 objc_msgSend 函数第一个参数 self 第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法 后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son

objc Runtime 开源代码对- (Class)class 方法的实现

  • (Class)class {
    return object_getClass(self);
    }

以下代码运行结果如何?

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1"); 
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); 
    });
    NSLog(@"3"); 
}

// 答案:主线程死锁

使用 block 时什么情况会发生引用循环,如 何解决?

系统的某些 block api 中,UIView 的 block 版本写动画时不需要考虑,但也有一 些 api 需要考虑

以下这些使用方式不会引起循环引用的问题

[UIView animateWithDuration:duration animations:^ {
    [self.superview layoutIfNeeded]; 
}];

[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
    self.someProperty = xyz; 
}];

[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * notification)
{ 
    self.someProperty = xyz; 
}];

但如果方法中的一些参数是成员变量,那么可以造成循环引用,如:GCD 、 NSNotificationCenter 调用就要小心一点,比如 GCD 内部如果引用了 self,而且 GCD 的参数是 成员变量,则要考虑到循环引用,举例如下:

// 【GCD】分析:self-->_operationsQueue-->block-->self 形成闭环,就造成了循环引用
__weak typeof(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
    [weakSelf doSomething];
    [weakSelf doSomethingElse]; 
});
// 【NSNotificationCenter】分析:self-->_observer-->block-->self 形成闭环,就造成了循环引用
__weak typeof(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) {
    [weakSelf dismissModalViewControllerAnimated:YES];
}];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容