美文网首页
iOS 面试题(一)

iOS 面试题(一)

作者: 搬砖的crystal | 来源:发表于2022-03-09 14:02 被阅读0次

1.instancetypeid的区别

区别一:
  • 在ARC环境下:instancetype用来在编译期确定实例的类型,而使用id的话,编译器不检查类型,运行时检查类型。
  • 在MRC环境下:instancetypeid一样,不做具体类型检查。
区别二:
  • id可以作为方法的参数,但instancetype不可以。instancetype只适用于初始化方法和便利构造器的返回值类型。

2. @synthesize@dynamic区别

在声明property属性后,有2种实现选择:

@synthesize

编译器期间,让编译器自动生成getter/setter方法。
当有自定义的存或取方法时,自定义会屏蔽自动生成该方法。

@dynamic

告诉编译器,不自动生成getter/setter方法,避免编译期间产生警告,然后由自己实现存取方法。
或存取方法在运行时动态创建绑定:主要使用在CoreData的实现NSManagedObject子类时使用,由Core Data框架在程序运行的时动态生成子类属性。

同时重写getter/setter方法

很少同时重写getter/setter方法,一般的话,大概都是使用懒加载方法,然后重写getter方法,做一个非空判断。要同时重写属性的getter/setter,系统就会报错误:Use of undeclared identifier '_string'; did you mean 'string'?

OC最初设定@property@synthesize的作用:
@property的作用是定义属性,声明getter/setter方法。(注意:属性不是变量)
@synthesize的作用是实现属性的,如getter/setter方法。

后来因为使用@property灰常频繁,就简略了@synthesize的表达。
从Xcode4.4以后@property已经独揽了@synthesize的功能。
主要有三个作用:
(1)生成了私有的带下划线的的成员变量因此子类不可以直接访问,但是可以通过getter/setter方法访问。那么如果想让定义的成员变量让子类直接访问那么只能在.h文件中定义成员变量了,因为它默认是@protected
(2)生成了getter/setter方法的实现当:用@property声明的成员属性,相当于自动生成了getter/setter方法,如果重写了getter/setter方法,与@property声明的成员属性就不是一个成员属性了,是另外一个实例变量,而这个实例变量需要手动声明。所以会报错误。

总结:一定要分清属性和变量的区别,不能混淆。@synthesize声明的属性=变量。意思是,将属性的getter/setter方法,作用于这个变量。

同时重写解决办法如下:

@interface ViewController ()
@property (nonatomic, strong)NSString *string;
@end
@implementation ViewController

//添加以下代码解决
@synthesize string = _string;

-(NSString *)string{
    return _string;
}

-(void)setString:(NSString *)string{
    _string = string;
}

-(void)viewDidLoad{
    [super viewDidLoad];
}

@end

3.NSProxy和NSObject

https://www.jianshu.com/p/0c31fed0a27a

4.传值通知和推送通知(本地&远程)

https://www.jianshu.com/p/d5011177ba90

5.imageNameImageWithContextOfFile

  • imageName的方式加载时,图片使用完毕后缓存到内存中,内存消耗多,加载数速度快。即使生成的对象被autoReleasePool释放了,这份缓存也不释放,如果图像比较大,或者图像比较多,用这种方式会消耗很大的内存。imageName采用了缓存机制,如果缓存中已加载了图片,直接从缓存读取,每次不需要再去读取文件,效率更高。
    *ImageWithContextOfFile图片时不会缓存的,加载速度慢。
  • 大量使用imageNamed方式会在不需要缓存的地方额外增加开销CPU的时间。当应用程序需要加载一张比较大的图片并且使用一次性,那么使用imageWithContentsOfFile是最合适的。

6.NSCacheNSDcitionary

NSCache使用很方便,提供了类似可变字典的实现方式,但它比可变字典更适用于实现缓存。

  • 最重要的原因是NSCache是线程安全的,使用NSMutableDictionary自定义实现缓存的时候需要考虑加锁和释放锁,NSCache已经帮我们做好了这一步。
  • 其次,内存不足时NSCache会自动释放存储的对象,不需要手动干预,如果是自定义实现需要监听内存状态然后做进一步删除对象的操作。
  • 还有一点NSCache的键key不会被复制,所以key不需要实现NSCopying协议。
    以上三点就是NSCache相比于NSMutableDictionary实现缓存功能的优点,在需要实现缓存的时候应优先考虑使用NSCache
#import "DJViewController.h"
@interface DJViewController ()<NSCacheDelegate>

@end

@implementation DJViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    NSCache *cache = [[NSCache alloc]init];
    //最大可缓存对象的个数为5个
    [cache setCountLimit:5];
    cache.delegate = self;
    
    NSString *test = @"Hello World!";
    [cache setObject:test forKey:@"Test"];
    
    for (int i = 0; i < 10; i++) {
        [cache setObject:[NSString stringWithFormat:@"world__%d",i] forKey:[NSString stringWithFormat:@"hello__%d",i]];
        NSLog(@"add %d",i);
    }
    
    for (int i = 0; i < 10; i++) {
        NSLog(@"get %d,%@",i,[cache objectForKey:[NSString stringWithFormat:@"hello__%d",i]]);
    }
    
    [cache removeAllObjects];
    
    for (int i = 0; i < 10; i++) {
        NSLog(@"get %d,%@",i,[cache objectForKey:[NSString stringWithFormat:@"hello__%d",i]]);
    }
    
}

//当缓存的对象即将被删除时调用此方法
-(void)cache:(NSCache *)cache willEvictObject:(id)obj{
    NSLog(@"remove %@",obj);
}

@end
//输出结果
2021-09-22 15:33:30.829312+0800 DJTestDemo[71357:240307] add 0
2021-09-22 15:33:30.829445+0800 DJTestDemo[71357:240307] add 1
2021-09-22 15:33:30.829540+0800 DJTestDemo[71357:240307] add 2
2021-09-22 15:33:30.829621+0800 DJTestDemo[71357:240307] add 3
2021-09-22 15:33:30.829729+0800 DJTestDemo[71357:240307] remove Hello World!
2021-09-22 15:33:30.829800+0800 DJTestDemo[71357:240307] add 4
2021-09-22 15:33:30.829900+0800 DJTestDemo[71357:240307] remove world__0
2021-09-22 15:33:30.829972+0800 DJTestDemo[71357:240307] add 5
2021-09-22 15:33:30.830055+0800 DJTestDemo[71357:240307] remove world__1
2021-09-22 15:33:30.830181+0800 DJTestDemo[71357:240307] add 6
2021-09-22 15:33:30.830484+0800 DJTestDemo[71357:240307] remove world__2
2021-09-22 15:33:30.830697+0800 DJTestDemo[71357:240307] add 7
2021-09-22 15:33:30.830922+0800 DJTestDemo[71357:240307] remove world__3
2021-09-22 15:33:30.831102+0800 DJTestDemo[71357:240307] add 8
2021-09-22 15:33:30.831310+0800 DJTestDemo[71357:240307] remove world__4
2021-09-22 15:33:30.831471+0800 DJTestDemo[71357:240307] add 9
2021-09-22 15:33:30.831675+0800 DJTestDemo[71357:240307] get 0,(null)
2021-09-22 15:33:30.842161+0800 DJTestDemo[71357:240307] get 1,(null)
2021-09-22 15:33:30.842600+0800 DJTestDemo[71357:240307] get 2,(null)
2021-09-22 15:33:30.842833+0800 DJTestDemo[71357:240307] get 3,(null)
2021-09-22 15:33:30.843000+0800 DJTestDemo[71357:240307] get 4,(null)
2021-09-22 15:33:30.843301+0800 DJTestDemo[71357:240307] get 5,world__5
2021-09-22 15:33:30.843423+0800 DJTestDemo[71357:240307] get 6,world__6
2021-09-22 15:33:30.843517+0800 DJTestDemo[71357:240307] get 7,world__7
2021-09-22 15:33:30.843688+0800 DJTestDemo[71357:240307] get 8,world__8
2021-09-22 15:33:30.844061+0800 DJTestDemo[71357:240307] get 9,world__9
2021-09-22 15:33:30.844323+0800 DJTestDemo[71357:240307] remove world__6
2021-09-22 15:33:30.844563+0800 DJTestDemo[71357:240307] remove world__7
2021-09-22 15:33:30.844952+0800 DJTestDemo[71357:240307] remove world__8
2021-09-22 15:33:30.845037+0800 DJTestDemo[71357:240307] remove world__9
2021-09-22 15:33:30.845546+0800 DJTestDemo[71357:240307] remove world__5
2021-09-22 15:33:30.845637+0800 DJTestDemo[71357:240307] get 0,(null)
2021-09-22 15:33:30.845800+0800 DJTestDemo[71357:240307] get 1,(null)
2021-09-22 15:33:30.846037+0800 DJTestDemo[71357:240307] get 2,(null)
2021-09-22 15:33:30.846305+0800 DJTestDemo[71357:240307] get 3,(null)
2021-09-22 15:33:30.846620+0800 DJTestDemo[71357:240307] get 4,(null)
2021-09-22 15:33:30.846852+0800 DJTestDemo[71357:240307] get 5,(null)
2021-09-22 15:33:30.847095+0800 DJTestDemo[71357:240307] get 6,(null)
2021-09-22 15:33:30.847342+0800 DJTestDemo[71357:240307] get 7,(null)
2021-09-22 15:33:30.847673+0800 DJTestDemo[71357:240307] get 8,(null)
2021-09-22 15:33:30.847882+0800 DJTestDemo[71357:240307] get 9,(null)

从输出可以看出,当我们要添加第六个对象时NSCache自动删除了我们添加的第一个对象并触发了NSCacheDelegate的回调方法,添加第七个时也是同样的,删除了缓存中的一个对象才能添加进去。

在第二个for循环中,我们通过key取出所有的缓存对象,前五个对象取出都为nil,因为在添加后面的对象时前面的被删除了,所以,当我们从缓存中获取对象时一定要判断是否为空,我们无法保证缓存中的某个对象不会被删除。

7.setNeedsDisplaysetNeedsLayout方法

https://www.jianshu.com/p/b296e03429d4

8. UILayerUiView

https://www.jianshu.com/p/708bee808dfc

9.layoutSubViewsdrawRect

layoutSubviews调用时机
  • init初始化不会触发layoutSubviews
  • addSubview会触发layoutSubviewsframe为0,是不调用的)。
  • 设置view的frame会触发layoutSubviews(前提是frame的值设置前后发生了变化)。
  • 滚动一个UIScrollView会触发layoutSubviews
  • 旋转屏幕会触发父UIView上的layoutSubviews事件(这个我们开发中会经常遇到,比如屏幕旋转时,为了界面美观我们需要修改子view的frame,那就会在layoutSubview中做相应的操作)。
  • 改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
  • 直接调用setLayoutSubviews(Apple是不建议这么做的)。
drawRect:调用机制
  • 调用时机:loadView ->ViewDidload ->drawRect:
  • 如果在UIView初始化时没有设置rect大小,将直接导致drawRect:不被自动调用。
  • 通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:
  • 直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发。drawRect:,但是有个前提条件是:view当前的rect不能为nil。
  • 该方法在调用sizeThatFits后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
drawRect:方法使用注意点:
  • 若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate的ref并且不能用于画图。
    drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay或者setNeedsDisplaynRect,让系统自动调该方法。
  • 若使用 CALayer绘图,只能在drawInContext: 中(类似于drawRect:)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法。
  • 若要实时画图,不能使gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕。

10.CPU和GPU

https://www.jianshu.com/p/412d7cd68756

11.点(pt)和像素(px)

px:pixel像素,屏幕上显示的最小单位。在同一个屏幕尺寸,更高的PPI(pixel per inch像素密度,指每英尺的像素数),就能显示更多的像素,渲染的内容会更清晰。
pt:point点,是一个标准的长度单位,与分辨率无关,1pt=1/72inch。根据屏幕像素的密度,一个点可以包含多个像素。例如,在标准的Retina显示屏上1pt里有2*2个px。

12.intNSInteger的区别

NSInteger是一个封装,它会识别当前操作系统的位数,自动返回最大的类型。

  • NSIntegerint的区别是NSInteger会根据系统的位数(32or64)自动选择int的最大数值(int or long)。
  • NSInteger默认值是null,而int默认值是0。
  • 声明为NSInteger的变量需要实例化,而声明为int的变量不需要实例化。
  • NSInteger是对象,用一个引用指向这个对象;而int是基本类型,直接存储数值。

13.#import#include

在C和C++里是没有#import的,只有#include用来包含头文件。#include就是将目标.h文件中的内容拷贝到当前文件中,并替换掉这句include。

但是这样做可能会因为重复引用带来编译错误,比如B和C都引用了A,D又同时引用了B和C,这样D引用了A两次。为了解决这个问题,OC加入了#import,就是为了使得头文件只被引用一次。

当引用关系很复杂时,编译引用所占的代码量就会大幅上升,因为被引用的头文件在引用的地方都被拷贝了一次。
为了解决这个问题,C语言引入了预编译头文件(PreCompiled Header),将公用的头文件放入预编译头文件中预先进行编译,然后在真正编译工程时再将预先编译好的产物加入到所有待编译的Source中去,来加快编译速度。

@import

关于 @import是iOS 7之后的新特性语法,这种方式叫Modules(模块导入) 或者 "semantic import(语义导入)" ,是一种更好的头部预处理的执行方式,这iOS 7之后你能通过@import语法来导入任何的framework,Modules是一种将所有可执行的framework打包在一起,貌似这种方式比起传统的#import更安全和更高效。
而且另外一个最大的改进就是使用@import之后,你不用在project settings那里添加framework,系统会自动帮你加载上了,方便了很多,也避免了很多不必要的错误,例如忘记了加入framework而出现的 "Linker Error"。

@class

在.h文件进用@class来声明,只告诉编译器,声明的类的名称。避免循环引用头文件。

14.全局和静态变量

static

(1)static修饰局部变量
局部变量:在函数/方法/代码块内声明的变量。它的生命周期、作用域都是在这个代码块内。存储在栈区(stack)一旦出了这个代码块,存储局部变量的这个栈内存就会被回收,局部变量也就被销毁。

当用static修饰局部变量时,变量被称为静态局部变量,和全局变量,静态全局变量一样,是存储在静态存储区。存储在静态存储区的变量,其内存直到 程序结束 才会被销毁。即,生命周期是整个源程序。
(2)static修饰全局变量

  • 当全局变量没有使用static修饰时,其存储在静态存储区,直到程序结束才销毁。也就是其作用域是整个源程序。我们可以使用extern关键字来引用这个全局变量。
  • 当全局变量使用static修饰时,其生命周期依旧是在程序结束时才销毁。但是其作用域只限于申明它的这个文件才可见。使用extern关键字无法引用这个全局变量。
const

主要强调变量是不可修改的。
const修饰的是其右边的值,也就是const右边的这个整体的值不能改变。

const*前:

const修饰*str这个整体,所以这个整体不能改变,这个整体是str指向的内存中的值。

//值不变
static NSString const *name = @"abc";
static const NSString *name = @"bac";   //两种写法等价

// name是 指针变量, *name是 指针指向的变量的值
const*后:

表示str指向的地址不能改变。

NSString * const str = @"abc";    // 地址不变
extern

主要是用来引用全局变量,它的原理是先在本文件中查找,查找不到再到其他文件中查找。

//.h中
@interface PDConst : NSObject

extern NSString *const appBaseURL;

@end
//.m中
@implementation PDConst

NSString *const currentBaseURL = @"http://192....";

@end

15.类

https://www.jianshu.com/p/40221ef8128e

16.字符串比较

OC中比较两个字符串是否相等时,应该用isEqualToString:而不能仅仅只是比较字符串的指针值。

//比较内容
NSString *str1=@"hello 1";  
NSString *str2;  
str2=[NSString stringWithFormat:@"hello %d", 1];  
if ([thing1 isEqualToString:thing2]){  
   NSLog(@"They are the same!")  
}  
//比较指针地址
if (str1==str2){  
   NSLog(@"They are the same!")  
} 

这是因为==运算符只判断str1和str2的指针数值,而不是他们所指的对象。由于str1,str2是不同的字符串,所以第二种比较方式会认为它们是不同的。
有时我们想检查两个对象的标识:str1和str2是同一个对象吗?这时就应该使用运算符==。如果是想查看等价性(即这两个字符串是否代表同一个事物吗),那么请使用isEqualToString:

17.NSNumberNSValue

作用

由于集合里只能存放对象,不可以存放基本数据类型,所以我们有时候需要讲一些对象比如基本数据类型,结构体等存到NSDictionaryNSArray中,我们就需要将这些数据类型或结构体包装成OC对象,以便集合能访问到。常用的用来包装这些类型的有NSNumberNSValue

区别
  • NSNumber只能包装基本数据类型,比如intfloatcharBOOL等。
  • NSValue可以包装任意一个对象,包括系统自定义的数据结构,结构体等等。
  • NSNumberNSValue的一个子类。

NSNumber

//包装
int age = 20;
NSNumber *num = [NSNumber numberWithInt:age];//将基本数据类型int对象age 包装成NSNumber对象
@(age);//直接包装

//拆封
[num intValue];
NSValue
将结构体包装成OC对象

CGPoint p = CGPointMake(1,2);
NSValue *val = [NSValue valueWithPoint:p];//将结构体p包装成NSValue对象

//拆
[value pointValue]

18.nilNilNull[NSNull null]

nil就是空对象。把一个对象置成nil之后,就不能对其进行retaincopy等引用计数相关的操作了。
在iOS中,Nil完全等同于nil
NUll就是C语言中的一个空指针,在Objective-C中也可以使用。
[NSNull null]是值为空的对象。

空对象和值为空的对象的区别:
“空对象”是已经释放了内存地址的对象,即不存在的对象。
“值为空的对象”是分配了地址,但是没有值得对象,是实际存在的对象。

19.沙盒

(1)应用程序沙盒目录
  • 应用程序包:和app同名,包含所有资源文件和可执行文件。
  • Document:存放其中的数据会备份到iCloud,不允许放下载的数据。用户自行生成的文件放入其中。
  • Library
    caches:用于存放一些缓存数据,保存应用运行时生成的需要持久化的数据。
    preference:存储偏好信息,苹果手机的设置应用会在该目录中查找应用的设置信息。
  • tmp:临时文件夹,不定期删除。
(2)获取目录的方法
//Home目录
NSString *homeDirectory = NSHomeDirectory();

//Document目录   documents (Documents)
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *path = [paths objectAtIndex:0];

//Libaray目录  various documentation, support, and configuration files, resources (Library)
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);
NSString *path = [paths objectAtIndex:0];

//Cache目录  location of discardable cache files (Library/Caches)
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES);
NSString *path = [paths objectAtIndex:0];

20.loadViewviewDidLoad

(1)loadView
作用

每次访问UIViewController的view(比如controller.viewself.view)而且view为nil,loadView方法就会被调用。loadView方法是用来负责创建UIViewController的view。

默认实现

默认实现即[super loadView]里面做了什么事情。
1)它会先去查找与UIViewController相关联的xib文件,通过加载xib文件来创建UIViewController的view。
如果在初始化UIViewController指定了xib文件名,就会根据传入的xib文件名加载对应的xib文件。

[[MJViewController alloc] initWithNibName:@"MJViewController" bundle:nil];

如果没有明显地传xib文件名,就会加载跟UIViewController同名的xib文件。
2)如果没有找到相关联的xib文件,就会创建一个空白的UIView,然后赋值给UIViewController的view属性。

使用

大家都知道UIViewController的view可以通过xib文件来创建,但是在某些情况下,xib不是那么地灵活,所以有时候我们想通过代码来创建UIView。 如果想通过代码来创建UIViewController的view,就要重写loadView方法,并且不需要调用[super loadView],因为在第3点里面已经提到:若没有xib文件,[super loadView]默认会创建一个空白的UIView。我们既然要通过代码来自定义UIView,那么就没必要事先创建一个空白的UIView,以节省不必要的开销。

(2)viewDidLoad

不管是通过xib文件还是重写loadView创建UIViewController的view,在view创建完毕后,最终都会调用viewDidLoad方法。
一般我们会在这里做界面上的初始化操作,比如往view中添加一些子视图、从数据库或者网络加载模型数据装配到子视图中。

相关文章

网友评论

      本文标题:iOS 面试题(一)

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