我遇到过的 iOS 面试题(Object-C)

题目都是我遇到过的 iOS 面试题,分享出来大家互相学习,有空就会补上答案,有不对的地方还请读者指点

====================一条普通的分割线====================

30、initWithFrame 与initWithCoder 之间的区别是什么?

  1. 当从代码实例化UIView的时候,initWithFrame会执行;

    您定义的每个新的视图对象都应该包含initWithFrame:初始化方法。该方法负责在创建对象时对类进行初始化,使之处于已知的状态。在通过代码创建您的视图实例时,需要使用这个方法。

  2. 当从文件加载UIView的时候,initWithCoder会执行。

    • 如果您从nib文件中装载定制视图类的实例,则需要知道:在iPhone
      OS中,装载nib的代码并不通过initWithFrame:方法来实例化新的视图对象,而是通过NSCoding协议定义的initWithCoder:方法来进行。

    • 即使您的视图采纳了NSCoding协议,Interface
      Builder也不知道它的定制属性,因此不知道如何将那些属性编码到nib文件中。所以,当您从nib文件装载定制视图时,initWithCoder:方法不具有进行正确初始化所需要的信息。为了解决这个问题,您可以在自己的类中实现awakeFromNib方法,特别用于从nib文件装载的定制类。

    • 一般来说要尽量在initWithCoder中做初始化操作,毕竟这是最合理的地方,只要你的控件支持序列化,那么它就能在任何被反序列化的时候执行初始化操作,这里适合做全局数据、状态的初始化工作,也适合手动添加子视图。

    • awakeFromNib相较于initWithCoder的优势是:当awakeFromNib执行的时候,各种IBOutlet也都连接好了;而initWithCoder调用的时候,虽然子视图已经被添加到视图层级中,但是还没有引用。要保证用户一定是通过xib创建的此控件,否则可能是一个空的视图,可以在initWithFrame里放置一个断言或者异常来通知控件的用户。

    • [self
      addSubview:self.backgroundView];(通过懒加载一个背景View,然后添加到视图层级上)。你的本意是在控件的最下面放置一个背景,却有可能将这个背景覆盖到控件的最上方,原因是用户可能会在xib里写入这个控件,然后往它上面添加一些子视图,这样一来,用户添加的这些子视图会在你添加背景之前先进入视图层级,你的背景被添加后就挡住了用户的子视图。如果你想支持用户的这种操作,可以把addSubview替换成insertSubview:atIndex:。

参考:http://www.jianshu.com/p/d90c7bc20132

29、把一个自定义对象数组序列化到磁盘

//在你的自定义对象实现NSCoding协议,然后在.m里面实现下面两个方法
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self) {
        self.objectID = [aDecoder decodeObjectForKey:@"objectID"];
        self.createdAt = [aDecoder decodeObjectForKey:@"createdAt"];
        self.updatedAt = [aDecoder decodeObjectForKey:@"updatedAt"];
    }
    return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.objectID forKey:@"objectID"];
    [aCoder encodeObject:self.createdAt forKey:@"createdAt"];
    [aCoder encodeObject:self.updatedAt forKey:@"updatedAt"];
}
//再然后用那个NSKeyedArchiver就可以了

28、几种数据存储的方式

1.XML属性列表(plist)归档
- 特点:只能存储OC常用数据类型(NSString、NSDictionary、NSArray、NSData、NSNumber等类型)而不能直接存储自定义模型对象
如果想存储自定义模型对象 -> 只能将自定义模型对象转换为字典存储;

//plist只能识别字典,数组
//读写数据 - > 实例代码
   NSString *docPath =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
    // 拼接要保存的地方的路径
    NSString *filePath = [docPathstringByAppendingPathComponent:@"str.plist"];
    // 1、写入数据
    [array writeToFile:filePath atomically:YES];
    // 2、 读取数据
    NSArray *array = [NSArray arrayWithContentsOfFile:filePath];

2.Preference(偏好设置)

  • 每个应用都有个NSUserDefaults实例,通过它来存取偏好设置,比如,保存用户名、字体大小、是否自动登录
    好处:存储数据不需要关心文件名称;快速存储键值对。
  • 底层实现:它其实就是一个字典 用途: 账户或者密码,开关状态
    注意:设置数据时,synchornize方法强制写入。UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入。

用法:

 NSUserDefaults *UserDefaults = [NSUserDefaultsstandardUserDefaults];
  // 1、写入
  [UserDefaults setBool:NO forKey:@"isLogined"];
  // 强制写入
  [defaults synchornize];  
  // 2、读取
  BOOL isVisble = [UserDefaults boolForKey:@"isLogined"];

3.NSKeyedArchiver归档(NSCoding)

  • 特点:
    可以存储自定义模型对象。NSKeyedArchiver归档相对较plist存储而言,它可以直接存储自定义模型对象,而plist文件需要将模型转为字典才可以存储自定义对象模型;归档不能存储大批量数据(相比较Sqlite而言),存储数据到文件是将所有的数据一下子存储到文件中,从文件中读取数据也是一下子读取所有的数据;
  • 缺点:
    假如你的文件中有100个对象了,然后你想在利用归档添加一个对象,你需要先把所有的数据解档出来,然后再加入你想添加的那个对象,同理,你想删除一个文件中的一个对象也是,需要解档出所有的对象,然后将其删除。性能低这样处理
  • 用法:
    基本使用:需要归档的模型类必须要遵守NSCoding协议,然后模型实现类中必须实现两个方法:1>encodeWithCoder -> 归档;2> initWithCoder: - > 解档

  • 注意:

//如果父类也遵守了NSCoding协议,请注意:
//应该在encodeWithCoder:方法中加上一句
[super encodeWithCode:encode]; // 确保继承的实例变量也能被编码,即也能被归档
//应该在initWithCoder:方法中加上一句
self = [super initWithCoder:decoder]; // 确保继承的实例变量也能被解码,即也能被恢复

4.SQLite3

  • SQLite是一款轻型的嵌入式数据库
//Sql语句

//增删改查

create table t_student (id integer, name text, age inetger, score real) ;
select * from t_student where age > 10 ;  //  条件查询
select * from t_student limit 4, 8 ; // 可以理解为:跳过最前面4条语句,然后取8条记录
insert into t_student (name, age) values (‘mj’, 10) ;
update t_student set age = 5 where age > 10 and name != ‘jack’ ;

delete from t_student whereage <=10 orage >30;
// 删表 : drop table if exists 表名 ; 
// 删除表数据 :delete from t_student;

5.Core Data

  • Core Data是对SQLite数据库的封装。框架提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite3数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,不需要编写任何SQL语句。
  • Core Data优缺点:
    优点:能够合理管理内存,避免使用sql的麻烦,高效
    缺点:它使基于SQLite的,所以并没有SQLite高效

参考:http://www.jianshu.com/p/14fd706b632d

27、hitTest 系统的默认实现(pointInside)

//例子
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView * view = [super hitTest:point withEvent:event];
    return view;
}
//默认实现
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
    BOOL isInside = [super pointInside:point withEvent:event];
    return isInside;
}

参考:http://www.jianshu.com/p/d8512dff2b3e

26、修改一个类的私有属性(KVC, runtime)

//方法一:KVC(键值编码)
//定义Person类
//.h文件
@interface Person : NSObject 
@private 
//name为私有属性
@property (nonatomic, copy) NSString *name; 
@end

//.m文件
@implementation Person
@end
//在某控制器中访问或修改Person类的私有属性name
//首先记得引入头文件,然后看下面具体实现

@implementation ViewController
- (void)viewDidLoad{
  [super viewDidLoad];
  Person *p = [Person new];
  //修改私有属性的值
  [p setValue:@"yyMae" forKey:@"name"];
  //访问私有属性的值
  NSString *name = [p valueForKey:@"name"];
}
//方法二:通过runtime获取或修改一个类私有属性的值
//同上定义Person类
//.h文件
@interface Person : NSObject 
@private 
//name为私有属性
@property (nonatomic, copy) NSString *name; 
@end

//.m文件
@implementation Person
@end
//在某控制器中访问或修改Person类的私有属性name
//首先记得引入头文件,然后看下面具体实现

@implementation ViewController
- (void)viewDidLoad{
  [super viewDidLoad];
  Person *p = [Person new];
  // IVar是runtime声明的一个宏 
  unsigned int count = 0; //count记录变量的数量
  // 获取类的所有属性变量 
  Ivar *members = class_copyIvarList([Person class], &count); 
  for (int i = 0; i < count; i++) { 
    Ivar ivar = members[i]; 
    // 取得属性名并转成字符串类型
    const char *memberName = ivar_getName(ivar);               
    NSLog(@"%s",memberName); 
    Ivar name = members[0]; 
    // 修改属性值 
    object_setIvar(Person, name, @"CGLueng"); 
}

25、runtime交换方法(class_getClassMethod, method_exchangeImplementations),一般在哪个函数(+(void)load)实现

  • 使用场景:在我们使用系统的方法时,功能有可能不够用,或者在想在调用系统的方法时,加一些判断。当然我可以继承系统的类,然后重写该方法。但是有可能项目做得时间比较长,一开始并没有继承。这时候再去继承就会花费一些时间。而且公司来了新人的,并不熟悉公司代码框架的时候,有可能会忘记继承。所以这时候我们就可以用运行时给系统方法动态添加一些代码。
  • 使用步骤:1.、创建你想交换方法所在类的分类。比如你想在调用 viewWillAppear时添加一些代码,那么你就新建一个UIViewController的分类。2、包含头文件#import < objc/runtime.h >。3、实现+ (void)load{}方法,在这个方法里面动态交换两个方法的地址,实现功能。这个方法会在程序加载分类到内存的时候就调用。
  • 例子:
#import <objc/runtime.h>
// load方法会在类第一次加载到内存的时候被调用  
 - (void)load {
    //方法交换只用执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"__NSArrayM");
        // 获取系统数组的selector
        SEL   systemSelector = @selector(objectAtIndex:);
        // 自己要交换的selector
        SEL   swizzledSelector = @selector(zwb_safeObjectAtIndex:);
        // 两个方法的Method
        Method originalMethod = class_getInstanceMethod(cls, systemSelector);
        Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
        //  动态添加方法
        if (class_addMethod(cls, systemSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
            // 添加成功的话将被交换方法的实现替换到这个并不存在的实现
            class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }else {
            //添加不成功,交换两个方法的实现
          method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
 - (id)zwb_safeObjectAtIndex:(NSUInteger)index {
    if (self.count > index) {
        //一定是自己调用自己,不会死循环,因为地址已经交换,其实调用的是系统的objectAtIndex
        return [self zwb_safeObjectAtIndex:index];
    }else {
        NSLog(@"数组越界");
        return nil;
    }
}

24、Category添加属性(objc_getAssociatedObject, objc_setAssociatedObject)

  • 关键点:
    1.#import < objc/runtime.h >
    2.@ dynamic indieBandName;
    3.objc_getAssociatedObject
    4.objc_setAssociatedObject
    -用法:
//.h
#import <Foundation/Foundation.h>

@interface NSObject (IndieBandName)

@property (nonatomic, strong) NSString *indieBandName;

@end
//.m
#import "NSObject+IndieBandName.h"
#import <objc/runtime.h>

static const void *IndieBanNameKey = &IndieBanNameKey;

@implementation NSObject (IndieBandName)

@dynamic indieBandName;

- (NSString *)indieBandName
{
    return objc_getAssociatedObject(self, IndieBanNameKey);
}

- (void)setIndieBandName:(NSString *)indieBandName
{
    objc_setAssociatedObject(self, IndieBanNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

23、数据库,对一个table加一列

  • ALTER TABLE 语句 ALTER TABLE 语句用于在已有的表中添加、修改或删除列。 SQL ALTER TABLE 语法
    如需在表中添加列,请使用下列语法:
ALTER TABLE table_name
ADD column_name datatype
  • 要删除表中的列,请使用下列语法:
ALTER TABLE table_name 
DROP COLUMN column_name
  • 注释:某些数据库系统不允许这种在数据库表中删除列的方式 (DROP COLUMN column_name)。
    要改变表中列的数据类型,请使用下列语法:
ALTER TABLE table_name
ALTER COLUMN column_name datatype
  • 题外话:对数据进行增删改查操作,一般会用到两个函数:sqlite3_exec()和sqlite3_prepare_v2()。
    (1) sqlite3_exec()函数一般用于你对数据库进行操作,而数据库只向你回馈你的操作是否成功,不返回数据库中的数据信息时。表格的创建就是如此,因为我们只关心表格是否创建成功,而不需要知道表格中到底有什么。
    (2) sqlite3_prepare_v2()函数用于你需要数据库对你反馈数据时,如查询数据,因为我们必须得到数据查询的结果,而不只是它是否成功的查询。

参考:http://www.w3school.com.cn/sql/sql_alter.asp

22、UIView 与 CALayer 的关系,圆角

  • 在iOS系统中,你能看得见摸得着的东西基本上都是UIView,而UIView之所以能显示在屏幕上,完全是因为它内部的一个层。在创建UIView对象时,UIView内部会自动创建一个层(即CALayer对象),它真正的绘图部分,是由一个CALayer类来管理。UIView通过layer属性可以访问这个层。通过操作这个CALayer对象,可以很方便地调整UIView的一些界面属性,比如:阴影、圆角大小、边框宽度和颜色等。UIView是iOS系统中界面元素的基础,所有的界面元素都是继承自它。UIView本身更像是一个CALayer的管理器,访问它的跟绘图、跟坐标有关的属性。例如frame、bounds等,实际上内部都是在访问它所包含的CALayer的相关属性。对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以。

参考:http://www.jianshu.com/p/c437188f9722

21、drawRect & setNeedsDisplay

  • iOS的绘图操作是在UIView类的drawRect方法中完成的,所以如果我们要想在一个UIView中绘图,需要写一个扩展UIView 的类,并重写drawRect方法,在这里进行绘图操作,程序会自动调用此方法进行绘图。重绘操作仍然在drawRect方法中完成,但是苹果不建议直接调用drawRect方法,当然如果你强直直接调用此方法,当然是没有效果的。苹果要求我们调用UIView类中的setNeedsDisplay方法,则程序会自动调用drawRect方法进行重绘。(调用setNeedsDisplay会自动调用drawRect)
    在UIView中,重写drawRect: (CGRect) aRect方法,可以自己定义想要画的图案。且此方法一般情况下只会画一次。也就是说这个drawRect方法一般情况下只会被掉用一次.。当某些情况下想要手动重画这个View,只需要掉用[self setNeedsDisplay]方法即可。
  • 1.如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。
    2.该方法在调用sizeThatFits后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
    3.通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
    4.直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。

参考:http://lizeqi.lofter.com/post/28e3a1_fdb00c

20、性能调优的注意点

19、GCD创建定时器

18、用GCD卡死主线程(dispatch_sync)

17、用信号量将异步方法写成同步

16、开启一个常驻线程(Runloop:source,timer)

15、NSOperation,GCD,NSThread

14、崩溃分析:符号化.crash文件的方法 ,atos命令

13、CoreText 图文混排 (CTFrame,CTLine,CTRun)

12、使用 tableView 遇到过哪些问题?如何解决?

11、简述一下 MVC 设计模式?

10、沙盒能储存哪些数据类型?如何将一张图片存进沙盒?

9、@selector(tapClick)和@selector(tapClick:)的区别是什么?

8、@2x、@3x图片分别用在哪些机型上?

7、不同导航控制器下的 UIViewcontroller如何实现数据交互?

6、请简述 TCP/IP、HTTP、Socket的区别?

5、不适用第三方工具时,如何实现图片的异步加载?

4、使用 block需要注意哪些问题?如何解决这些问题?

3、Notification、delegate、block 三者的区别?

2、什么情况需要用到单例?单例的弊端有哪些?

1、描述一下UIViewController的生命周期,从ViewController2返回到ViewController时,ViewController会执行哪些方法?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值