iOS面试题-UI篇

Size Classes 具体使用

  • 对屏幕进行分类

UIView和CALayer是什么关系?

  • UIView显示在屏幕上归功于CALayer,通过调用drawRect方法来渲染自身的内容,调节CALayer属性可以调整UIView的外观,
  • UIView继承自UIResponder,比起CALayer可以响应用户事件,Xcode6之后可以方便的通过视图调试功能查看图层之间的关系
  • UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它内部是由Core Animation来实现的,它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的跟绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性
  • UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示,如:
- (class) layerClass {
    // 使某个UIView的子类使用GL来进行绘制
    return ([CAEAGLLayer class]);
}
  • UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的显示。例如下面的代码会在目标View上敷上一层黑色的透明薄膜。
grayCover = [[CALayer alloc]init];
grayCover.backgroudColor = [[UIColor blackColor]colorWithAlphaComponent:0.2].CGColor;
[self.layer addSubLayer:grayCover];
  • 补充部分,这部分有深度了,大致了解一下吧,UIView的layer树形在系统内部被系统维护着三份copy
  • 逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份
  • 动画树,这是一个中间层,系统正是在这一层上更改属性,进行各种渲染操作
  • 显示树,这棵树的内容是当前正被显示在屏幕上的内容
  • 这三棵树的逻辑结构都是一样的,区别只有各自的属性

loadView的作用?

  • loadView用来自定义view,只要实现了这个方法,其他通过xib或storyboard创建的view都不会被加载
  • 看懂控制器view创建的这个图就行

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

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

IB中User Defined Runtime Attributes如何使用?

  • User Defined Runtime Attributes是一个不被看重但功能非常强大的的特性,它能够通过KVC的方式配置一些你在interface builder中不能配置的属性
  • 当你希望在IB中作尽可能多得事情,这个特性能够帮助你编写更加轻量级的viewcontroller

沙盒目录结构是怎样的?各自用于那些场景?

  • Application:存放程序源文件,上架前经过数字签名,上架后不可修改

  • Documents:常用目录,iCloud备份目录,存放数据

  • Library

  • Caches:存放体积大又不需要备份的数据

  • Preference:设置目录,iCloud会备份设置信息

  • tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

pushViewController和presentViewController有什么区别

  • 两者都是在多个试图控制器间跳转的函数
  • presentViewController提供的是一个模态视图控制器(modal)
  • pushViewController提供一个栈控制器数组,push/pop

请简述UITableView的复用机制

  • 每次创建cell的时候通过dequeueReusableCellWithIdentifier:方法创建cell,它先到缓存池中找指定标识的cell,如果没有就直接返回nil
  • 如果没有找到指定标识的cell,那么会通过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)];
imageView.center = CGPointMake(200, 300);
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];

使用drawRect有什么影响?

  • drawRect方法依赖Core Graphics框架来进行自定义的绘制
  • 缺点:它处理touch事件时每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例,那就会很糟糕了
  • 这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立Core Graphics上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制

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

  • SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后在替换占位图片
  • 加载图片的过程大致如下:
  • 首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
  • 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
  • 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
  • 下载后的图片会加入缓存中,并写入磁盘中
  • 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来

设计个简单的图片内存缓存器

  • 类似上面SDWebImage实现原理即可
  • 一定要有移除策略:释放数据模型对象

控制器的生命周期

  • 就是问的view的生命周期,下面已经按方法执行顺序进行了排序
// 自定义控制器view,这个方法只有实现了才会执行
- (void)loadView
{
    self.view = [[UIView alloc] init];
    self.view.backgroundColor = [UIColor orangeColor];
}
// view是懒加载,只要view加载完毕就调用这个方法
- (void)viewDidLoad
{
    [super viewDidLoad];

    NSLog(@"%s",__func__);
}

// view即将显示
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSLog(@"%s",__func__);
}
// view即将开始布局子控件
- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];

    NSLog(@"%s",__func__);
}
// view已经完成子控件的布局
- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    NSLog(@"%s",__func__);
}
// view已经出现
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    NSLog(@"%s",__func__);
}
// view即将消失
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    NSLog(@"%s",__func__);
}
// view已经消失
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    NSLog(@"%s",__func__);
}
// 收到内存警告
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    NSLog(@"%s",__func__);
}
// 方法已过期,即将销毁view
- (void)viewWillUnload
{

}
// 方法已过期,已经销毁view
- (void)viewDidUnload
{

}

你是怎么封装一个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
{
    // 初始化代码
}

如何进行iOS6、7的适配

  • 通过判断版本来控制,来执行响应的代码
  • 功能适配:保证同一个功能在6、7上都能用
  • UI适配:保证各自的显示风格
// iOS版本为7.0以上(包含7.0)
#define iOS7 ([[UIDevice currentDevice].systemVersion doubleValue]>=7.0)

如何渲染UILabel的文字?

  • 通过NSAttributedString/NSMutableAttributedString(富文本)

UIScrollView的contentSize能否在viewDidLoad中设置?

  • 因为UIScrollView的内容尺寸是根据其内部的内容来决定的,所以是可以在viewDidLoad中设置的
  • 补充:(这仅仅是一种特殊情况)
  • 前提,控制器B是控制器A的一个子控制器,且控制器B的内容只在控制器A的view的部分区域中显示
  • 假设控制器B的view中有一个UIScrollView这样一个子控件
  • 如果此时在控制器B的viewDidLoad中设置UIScrollView的contentSize的话会导致不准确的问题
  • 因为任何控制器的view在viewDidLoad的时候的尺寸都是不准确的,如果有子控件的尺寸依赖父控件的尺寸,在这个方法中设置会导致子控件的frame不准确,所以这时应该在下面的方法中设置子控件的尺寸
-(void)viewDidLayoutSubviews;

触摸事件的传递

  • 触摸事件的传递是从父控件传递到子控件

  • 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件

  • 不能接受触摸事件的四种情况

  • 不接收用户交互,即: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,那么父控件就是上一个响应者


https://mp.weixin.qq.com/s/6dqhFWgaKZRVsvNpceyo1A

iOS 性能优化:优化 App 启动速度

苹果是一家特别注重用户体验的公司,过去几年一直在优化 App 的启动时间,特别是去年的 WWDC 2019 keynote[1] 上提到,在过去一年苹果开发团队对启动时间提升了 200%

发布于:2天以前  |  23次阅读  |  详细内容 »

让你的应用远离越狱:iOS 14 App Attest 防护功能

当越狱在 iOS 设备第一次流行起来时,iOS 开发人员会尝试各种方法来保护自己的应用程序,以让应用免受盗版等不确定因素的困扰。有许多方法可以做到这一点,包括检查 Cydia 是否存在、检测应用程序是否可读取自身沙箱之外的文件、在检测到调试器时让应用程序崩溃等等。

发布于:9天以前  |  50次阅读  |  详细内容 »

探秘 iOS 14 的 WidgetKit

Widget Extension 提供了 small, medium, large 三个尺寸,不同尺寸可以展示不同的数据、不同的界面,开发者也可以锁定自己APP的 Widget 只有某类尺寸,相同的widget也能重复添加。作为添加在主屏幕上的控件,苹果用了 “At a glance” 来形容 widget ,所以 widget extension 是无法交互的,它能做的只有展示一些信息与点击两个作用,点击后就会引导至app,同时为了性能与耗电量的考虑,Widget extension 也不能展示视频和动态图像。

发布于:11天以前  |  75次阅读  |  详细内容 »

iOS14 Widget 万字指北,先人一步获得顶级流量

2020 年 6 月 22 日,苹果召开了第一次线上的开发者大会 - WWDC20。这次发布会上宣布了ARM架构Mac芯片(拳打Intel)、iOS 14 ATT(脚踢Facebook),可谓是一次载入史册(我是爸爸)的发布会了,当然还发布了被称为下一个顶级流量入口的Widget。踩着八月的尾巴,本次我们就来探究一下Widget。本文会从Widget初窥和Widget开发两个维度和章节来探究一下Widget, 其中初窥章节会带您简单的了解一下Widget,适合应用决策者阅读; 开发章节会带着您一步一步的完成设计开发Widget,适合程序员阅读。

发布于:11天以前  |  80次阅读  |  详细内容 »

OCRunner:完全体的iOS热修复方案

为了能够实现一篇文章的思路:Objective-C源码 -> 二进制补丁文件 ->热更新(具体是哪篇我忘了)。当时刚好开始了oc2mango翻译器的漫漫长路(顺带为了学习编译原理,嘻嘻),等基本完成以后,就开始肝OCRunner:完全兼容struct,enum,系统C函数调用,魔改libffi,生成补丁文件等,尽可能兼容Objective-C,为了做一个直接运行OC的快乐人。

发布于:11天以前  |  74次阅读  |  详细内容 »

iOS APP图标版本化

在我们的项目开发过程中,需要频繁打包给测试人员去测试,有时候我们都不知道测试机上安装的版本是否是最新的,这样会造成很多不必要的麻烦和成本。因此我们需要将buildNumber以水印的方式打在APPIcon上,可以很直观的知道当前是哪一个版本。

发布于:11天以前  |  71次阅读  |  详细内容 »

百度App iOS工程化实践: EasyBox破冰之旅

百度App从单一的搜索工具发展到今天以搜索和Feed流为双引擎的综合性内容消费服务平台,其复杂程度已然不可同日而语矣。 作为一个日活过亿的超级App,业务规模庞大,相关技术人员超过千人,客户端支持主流的移动技术,涉及近百业务方,技术形态复杂,各种组件近三百个,代码百万量级,由此带来的工程化问题是技术团队的一个极大挑战。

发布于:11天以前  |  80次阅读  |  详细内容 »

百度APP iOS暗黑模式适配的完美解决方案

在2019WWDC的开场演讲中,苹果公布了即将推出的iOS13 DarkMode的新特性。此新特性不仅可以在夜晚保护视力,而且对于使用OLED的最新一代设备而言,也可以帮助用户节省电量消耗。不过此特性只支持iOS13以上的系统,为了给全系统所有用户最好的体验,研发出了一套皮肤主题框架,不仅可以全系统支持DarkMode,还可以扩展多套皮肤主题;

发布于:11天以前  |  75次阅读  |  详细内容 »

iOS 14 - 使用 PHPicker 选择照片和视频

绝大多数的 App 都要和相册打交道,选择照片或者视频,要么用来发个朋友圈,要么是放到什么地方做个背景。从 AssertLibrary 到 Photos 框架,苹果已经在多年之前就给相册相关的 API 做过一次大升级了。

发布于:13天以前  |  97次阅读  |  详细内容 »

iOS开发体验优化方案

随着Flutter等跨端框架的出现,业务开发同学经常需要在Android/iOS上跨端进行业务开发,问题定位等。新的不熟悉的环境的搭建总会遇到各种各样的问题,导致搭建失败,特别是iOS开发环境,是最复杂的,不仅环境搭建繁琐,而且切分支后的打包速度很慢,所以我们设计实现了两个工具,用于优化闲鱼iOS开发体验。

发布于:25天以前  |  126次阅读  |  详细内容 »

iOS14:再见了,“流氓”APP!

最近和苹果有关的重大消息可能就是从8月1日开始,AppStore中国区火速下架未获版号的游戏APP,数量超过30000款,之前小智就和大家说过,这未必不是一件好事,众多低质和“流氓”APP将被最大限度隔绝在iOS系统之外。

发布于:28天以前  |  175次阅读  |  详细内容 »

iOS 14 苹果对 Objective-C Runtime 的优化

Objective-C 是一门古老的语言,诞生于 1984 年,跟随 Apple 一路浮沉,见证了乔布斯创建了 NeXT,也见证了乔布斯重回 Apple 重创辉煌,它用它特立独行的语法,堆砌了 UIKit,AppKit, Foundation 等一个个基石,时间来到 2020 年,面对汹涌的"后浪" Swift,"老前辈" Objective-C 也在发挥着自己的余热,即使面对越来越多阵地失守,唯有“老兵不死,只会慢慢凋亡"才能体现的悲壮。今年,Apple 给 Objective-C Runtime 带来了新的优化,接下来,让我们深入理解这些变化。

发布于:1月以前  |  274次阅读  |  详细内容 »

iOS14 隐私适配及部分解决方案

在刚刚结束的线上 WWDC 2020 发布会上苹果向我们展示了新的 iOS14 系统。iOS14 的适配,很重要的一环就集中在用户隐私和安全方面。 在 iOS13 及以前,当用户首次访问应用程序时,会被要求开放大量权限,比如相册、定位、联系人,实际上该应用可能仅仅需要一个选择图片功能,却被要求开放整个照片库的权限,这确实是不合理的。对于相册,在 iOS14 中引入了 “LimitedPhotos Library” 的概念,用户可以授予应用访问其一部分的照片,对于应用来说,仅能读取到用户选择让应用来读取的照片,让我们看到了 Apple 对于用户隐私的尊重。这仅仅是一部分,在iOS14 中,可以看到诸多类似的保护用户隐私的措施,也需要我们升级适配。 最近在调研 iOS14的适配方案,本文主要分享一下 iOS14 上对于隐私授权的变更和部分适配方案,欢迎补充指正。

发布于:2月以前  |  323次阅读  |  详细内容 »

Metal新特性:大幅度提升iOS端性能

Metal 是一个和 OpenGL ES 类似的面向底层的图形编程接口,通过使用相关的 api 可以直接操作 GPU ,最早在 2014 年的 WWDC 的时候发布。Metal 是 iOS 平台独有的,意味着它不能像 OpenGL ES 那样支持跨平台,但是它能最大的挖掘苹果移动设备的 GPU 能力,进行复杂的运算,像 Unity 等游戏引擎都通过 Metal 对 3D 能力进行了优化, App Store 还有相应的运用 Metal 技术的游戏专题。 闲鱼团队是比较早在客户端侧选择Flutter方案的技术团队,当前的闲鱼工程里也是一个较为复杂的Native-Flutter混合工程。作为一个2C的应用,性能和用户体验一直是闲鱼技术团队在开发中比较关注的点。而Metal这样的直接操作GPU的底层接口无疑会给闲鱼技术团队突破性能瓶颈提供一些新的思路。 下面会详细阐述一下这次大会Metal相关的新特性,以及对于闲鱼技术和整个淘系技术来说,这些新特性带来了哪些技术启发与思考。

发布于:2月以前  |  284次阅读  |  详细内容 »

最多阅读

快速配置 Sign In with Apple 1年以前  |  3176次阅读
给数组NSMutableArray排序 1年以前  |  2325次阅读
开篇 关于iOS越狱开发 1年以前  |  2322次阅读
APP适配iOS11 1年以前  |  2245次阅读
在越狱的iPhone设置上使用lldb调试 1年以前  |  2226次阅读
UITableViewCell高亮效果实现 1年以前  |  2159次阅读
App Store 审核指南[2017年最新版本] 1年以前  |  2075次阅读
使用 GPUImage 实现一个简单相机 1年以前  |  2040次阅读
所有iPhone设备尺寸汇总 1年以前  |  2011次阅读
关于Xcode不能打印崩溃日志 1年以前  |  1930次阅读
使用ssh访问越狱iPhone的两种方式 1年以前  |  1926次阅读
使用ssh 访问越狱iPhone的两种方式 1年以前  |  1794次阅读
UIDevice的简单使用 1年以前  |  1657次阅读
为对象添加一个释放时触发的block 1年以前  |  1620次阅读
使用最高权限操作iPhone手机 1年以前  |  1561次阅读