题目都是我遇到过的 iOS 面试题,分享出来大家互相学习,有空就会补上答案,有不对的地方还请读者指点
====================一条普通的分割线====================
30、initWithFrame 与initWithCoder 之间的区别是什么?
当从代码实例化UIView的时候,initWithFrame会执行;
您定义的每个新的视图对象都应该包含initWithFrame:初始化方法。该方法负责在创建对象时对类进行初始化,使之处于已知的状态。在通过代码创建您的视图实例时,需要使用这个方法。
当从文件加载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