interviewiOS面试题面试

IOS知识点总结

2019-04-22  本文已影响144人  _既白_

一,成员变量和属性

@interface Person : NSObject
{
    NSString *_sex;
}
@property (nonatomic, copy) NSString *name;
@end


成员变量:

1. 成员变量的默认修饰是@protected。
2. 成员变量不会自动生成set和get方法,需要自己手动实现。
3. 成员变量不能用点语法调用,因为没有set和get方法,只能使用->调用。

属性

1. 属性的默认修饰是@protected。
2. 属性会自动生成set和get方法。
3. 属性用点语法调用,点语法实际上调用的是set和get方法。

二,int􏱰, long, NSInteger􏰱􏱱􏱲

32程序:NSInteger相当于int32_t,4个字节为int的别名。
64位程序:NSInteger相当于int64_t,8个字节为long long的别名。
NSInteger􏴝􏳪􏵴􏵨 表示当前系统下整型所占的最大字节􏱍􏰯􏲬􏴈􏷇􏰶􏴮􏵏􏹠,􏲺􏰙32位 􏰟int4 long4,64位􏰟int4,long8

三,全局变量,静态变量,局部变量

1. 全局变量

函数外面声明
可以跨文件访问
可以在声明时赋上初始值
如果没有赋初始值,系统自动赋值为0
存储位置:既非堆,也非栈,而是专门的【全局(静态)存储区static】!

A 文件中
float lastNum;//仅声明
float lastNum = 10.0;//声明和定义

B文件中
extern float lastNum; // 引用

关于extern关键字
要注意的是,全局变量可以在许多地方声明 为extern,但定义(赋初值)只能一次。
而上面的代码既声明,也定义了一个全局变量lastNum。其中,定义的时候并不需要extern专门来修饰。
倒是在其他不需要定义该全局变量的地方,需要extern来修饰该全局变量,声明要调用外部变量了。

注意:全局变量可以同一工程跨文件访问,可能会引起严重的混淆问题,名称尽量特殊化。

2. 静态变量

函数外面 或 内部声明(即可修饰原全局变量亦可修饰原局部变量)
仅声明该变量的文件可以访问
可以在声明时赋上初始值
如果没有赋初始值,系统自动赋值为0
存储位置:既非堆,也非栈,而是专门的【全局(静态)存储区static】!

static float lastNum;
static float lastNum = 10.0;
3. 局部变量(自动变量)

函数内部声明
仅当函数执行时存在
仅在本文件本函数内可访问
存储位置:自动保存在函数的每次执行的【栈帧】中,并随着函数结束后自动释放,另外,函数每次执行则保存在【栈】中

- (float)caculateResult{
 float a = 1.0;
 float b = 2.0;
 return a + b;
}
4. 内存分区
堆和栈首先要清楚的是程序对内存的使用分为以下几个区:

栈区(stack):由编译器自动分配和释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。与数据结构中的堆是两码事,分配方式类似于链表。
全局区(static):全局变量和静态变量存放在此。
文字常量区:常量字符串放在此,程序结束后由系统释放。
程序代码区:存放函数体的二进制代码。

import,include,@class

1. import和include都是包含某个文件
2. import可以防止重复包含
3. @class是声明有这个类,具体怎么实现没包括,防止循环引用头文件

#import是OC导入头文件的关键字,#include是C/C++倒入头文件的关键字
使用#import导入头文件只会导入一次,不会重复导入。相当于#include和#pragma once;
@class只告诉编译器某个类的声明,当执行时才去查看这个类的实现文件,可以解决头文件的相互包含。

category,extension,Protocol,继承

category

分类的使用:
第一,category的主要作用是为已经存在的类添加方法;
第二,把类的实现分开在几个不同的文件里面。

把类的实现分开在几个不同的文件里面的好处:

可以减少单个文件的体积
可以把不同的功能组织到不同的category里
可以由多个开发者共同完成一个类
可以按需加载想要的category
声明私有方法

第三,模拟多继承(另外可以模拟多继承的还有protocol)

category的特点

extension

extension被开发者称之为扩展、延展、匿名分类,和category不同的是extension不但可以声明方法,还可以声明属性、成员变量。extension 一般用于声明私有方法,私有属性,私有成员变量。

extension的存在形式

category是拥有.h文件和.m文件的东西。但是extension不然。extension只存在于一个.h文件中,或者extension只能寄生于一个类的.m文件中。比如,viewController.m文件中通常寄生这么个东西,其实这就是一个extension

#import "ViewController.h"
@interface ViewController ()
{
    NSString *_str;
}
@property (weak, nonatomic) IBOutlet UIButton *resultBtn;
- (NSString *)getString;
@end

注意:extension常用的形式并不是以一个单独的.h文件存在,而是寄生在类的.m文件中。

category和extension的区别

Protocol

利用继承、多态可以很好的保持"对扩展开放,对更改封闭"(即上文提到的开放-封闭原则OCP),这也是面向对象语言保持开放-封闭原则最常见的办法。OC中还有另外两种语法来支持OCP:即Category和Protocol

Protocol只是声明一套接口,并不能提供具体实现,变相的也算是一种抽象基类的实现方式(OC本身语法并不支持抽象基类)。

Protocol只能提供一套公用的接口声明,并不能提供具体实现,它的行为是,我只负责声明,而不管谁去实现,去如何实现。 这样的话,我定义一套接口,可以使任意的类都用不同的方式去实现接口中的方法,就是为遵守了protocol的类提供了一些额外访问这个类的一些接口,像delegatedataSourceprotocol实现是最好的。

继承

继承,它基于ProtocolCategory之间,既可以像protocol一样只提供纯粹的接口,也可以像Category一样提供接口的完整实现,可以自由定义类的实例变量,而且继承还可以对类以后的方法进行改写,所以继承的力量是最强大的。

在iOS开发中,继承是完全可以完成protocolcategory的功能的,那么在开发过程中多多使用继承体系可好?

使用继承来进行扩展是一种耦合度很高的行为,对父类可以说是完全依赖,如果继承体系太过复杂,会造成难以维护的问题。如果仅仅只是对类进行扩展,并不建议使用继承,毕竟使用protocol和category是很简单、轻松的。

category是可以被继承的。在某个父类中定义了category,那么他所有的子类都具有该category。

==、 isEqualToString、isEqual区别

==

==: 比较两个指针的值 , 是判断两个对象的引用(reference)是否一样,也就是内存地址是否一样。

isEqualToString

isEqualToString : 比较两个字符串是否相同

isEqual

Returns a Boolean value that indicates whether the receiver and a given object are equal.
官网解释:返回一个bool值判断两个对象是否相等

isEqual: 是 NSObject 的方法, 如果两个对象是相等的,那么他们必须有相同的哈希值, 包括判断 指针的等同性,类的等同性,最后调用对象的比较器进行比较。

nil, Nil, NULL,NSNull的区别

#### nil
nil 表示空对象。
object = nil,表示把这个对象释放掉了,引用计数为0,如果对这个对象引用计数加1 的操作都会报错。

#### NSNull
NSNull 称它为“值为空的对象”。

#### Nil
nil和Nil在使用上是没有严格限定的,也就是说凡是使用nil的地方都可以用Nil来代替,反之亦然。
只不过从编程人员的规约中我们约定俗成地将nil表示一个空对象,Nil表示一个空类。

#### NULL
NULL就是典型C语言的语法,它表示一个空指针。
参考代码如下:
int *ponit = NULL;

loadView、viewDidLoad、viewDidUnload区分

#### loadView

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

创建view方式:
(1)它会先去查找与controller相关联的xib文件,通过加载xib文件来创建controller的view

  a.如果在初始化controller指定了xib文件名,就会根据传入的xib文件名加载对应的xib文件
[[SYViewController alloc] initWithNibName:@"SYViewController" bundle:nil];
  b.如果没有明显地传xib文件名,就会加载跟controller同名的xib文件
[[SYViewController alloc] init];

(2) 如果没有找到相关联的xib文件,就会创建一个空白的UIView,然后赋值给controller的view属性,大致如下:

- (void)loadView 
{
    self.view = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
}


#### viewDidLoad
无论你是通过xib文件还是重写loadView方法创建controller的view,在view创建完毕后,最终都会调用viewDidLoad方法。

一般我们会在这里做界面上的初始化操作,比如往view中添加一些子视图、从数据库或者网络加载模型数据装配到子视图中。


#### viewDidUnload

1、调用时机
iOS设备的内存是极其有限的,如果应用程序占用的内存过多的话,系统就会对应用程序发出内存警告。controller就会收到didReceiveMemoryWarning消息。didReceiveMemoryWarning方法的默认实现是:如果当前controller的view不在应用程序的视图层次结构(View Hierarchy)中,即view的superview为nil的时候,就会将view释放,并且调用viewDidUnload方法。
2、作用
发出内存警告且view被释放的时候就会调用viewDidUnload方法,所以一般在释放资源,主要是释放界面元素相关的资源,将相关的实例都赋值为nil。例如:
 - (void)viewDidUnload {
     [super viewDidUnload];
     self.name = nil;
     self.pwd = nil;
 }

3、dealloc跟viewDidUnload的关系
当发出内存警告调用viewDidUnload方法时,只是释放了view,并没有释放controller,所以并不会调用dealloc方法。
即viewDidUnload和dealloc方法并没有任何关系,dealloc方法只会在controller被释放的时候调用。

程序启动过程

打开程序 --> 执行main函数 --> 执行并初始化UIAPPlicationMian函数(生成UIAPPlication对象,UIAppdelegate对象,创建RunLoop开启事件循环,加载资源(图片,静态文件,infoplist,storybord,xib等))-->监听和处理事件-> 结束程序

隐式动画 和 显式动画

隐式动画

隐式动画是系统框架自动完成的。苹果对UIView添加了一种基于block的动画方法:+animateWithDuration:animations:。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。CATransaction+begin+commit方法在+animateWithDuration:animations:内部自动调用,这样block中所有属性的改变都会被事务所包含。

显式动画

Core Animation提供的显式动画类型,显式动画是指用户自己通过beginAnimations:context:commitAnimations创建的动画。我们经常使用的CABasicAnimation,CAKeyframeAnimation,CATransitionAnimation,CAAnimationGroup等都是显式动画类型,这些CAAnimation类型可以直接提交到CALayer上。
无论是隐式动画还是显式动画,提交到layer后,经过一系列处理,最后都经过上文描述的绘制过程最终被渲染出来。

静态链接库 和 动态链接库

静态库和动态库的存在形式

静态库:.a 和 .framework

动态库:.dylib 和 .framework

静态库和动态库在使用上的区别
静态库:连接时,静态库会被完整的复制到可执行文件中,被多次使用就有多份冗余拷贝

image

动态库:连接时不复制,程序运行时有系统动态加载到内存,提供程序调用,系统只加载一次,多个程序共用,节省内存


image

注意:项目中使用了自制的动态库,不能被上传到AppStore

多态

不同对象以自己的方式去响应相同消息的能力叫做多态,子类的指针可以指向父类

运行时

运行时机制是我们在运行时才确定这个对象的类,以及调用该类指定的方法

事件响应与响应者链

注意:事件是从父视图传递给子视图,如果父视图不接受触摸事件,子视图就不可能接收触摸事件。

视图不接收触摸事件的三种情况:

大致触发过程:application –> window –> root view –>......–>lowest view

如何判断当前响应者的上一个响应者是谁呢?

frame, bounds

frame指的是:该view在父view坐标系中的位置和大小。(参考系是父视图坐标系)
bounds指的是:该View在自身坐标系中的位置和大小。(参考系是本身坐标系)

循环引用

循环引用的实质:多个对象相互之间有强引用,不能施放让系统回收
解决循环引用:一般是将 strong 引用改为 weak 引用。

场景一: 对象之间相互引用:

如:在使用UITableView 的时候,将 UITableView 给 Cell 使用,cell 中的 strong 引用会造成循环引用。

// controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TestTableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:@"UITableViewCellId" forIndexPath:indexPath];
    cell.tableView = tableView;
    return cell;
}

// cell
@interface TestTableViewCell : UITableViewCell
@property (nonatomic, strong) UITableView *tableView; // strong 造成循环引用
@end

解决:strong 改为 weak
// cell
@interface TestTableViewCell : UITableViewCell
@property (nonatomic, weak) UITableView *tableView; // strong 改为 weak
@end

场景二:block

block在copy时都会对block内部用到的对象进行强引用的。


self.testObject.testCircleBlock = ^{
   [self doSomething];
};

self将block作为自己的属性变量,而在block的方法体里面又引用了 self 本身,此时就很简单的形成了一个循环引用。
应该将 self 改为弱引用

__weak typeof(self) weakSelf = self;
 self.testObject.testCircleBlock = ^{
      __strong typeof (weakSelf) strongSelf = weakSelf;
      [strongSelf doSomething];
};


在 ARC 中,在被拷贝的 block 中无论是直接引用 self 还是通过引用 self 的成员变量间接引用 self,该 block 都会 retain self。
  // weak obj
    /#define WEAK_OBJ(type)  __weak typeof(type) weak##type = type;

    // strong obj
    /#define STRONG_OBJ(type)  __strong typeof(type) str##type = weak##type;

场景三,Delegate

delegate 属性的声明如下:

@property (nonatomic, weak) id <TestDelegate> delegate;

如果将 weak 改为 strong,则会造成循环引用
// self -> AViewController
BViewController *bVc = [BViewController new];
bVc = self; 
[self.navigationController pushViewController: bVc animated:YES];

   // 假如是 strong 的情况
   // bVc.delegate ===> AViewController (也就是 A 的引用计数 + 1)
   // AViewController 本身又是引用了 <BViewControllerDelegate> ===> delegate 引用计数 + 1
   // 导致: AViewController <======> Delegate ,也就循环引用啦

NSTimer

NSTimertarget 对传入的参数都是强引用(即使是 weak 对象)

 - (void)viewDidLoad {
    [super viewDidLoad];
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
    // 等同于 下面 RunLoop -> timer -> self
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

- (void)timerClicked {
    NSLog(@"timerClicked ----");
} 

这段代码发生内存泄露 页面消失 timerClicked函数一直执行
解决办法为 didMoveToParentViewController函数 将timer 置为空 或在合适位置将其timer取消

- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (nil == parent) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

NSString copy 或者用strong修饰的区别

(1)NSString的字符串的对象的值改变时,会开辟一块新的内存,NSMutableString的字符串的对象的值改变时,依旧是原地址。

(2)copy拷贝NSSting字符串时,拷贝指针,既浅拷贝;copy拷贝NSMutableString字符串时是重新生成一个新对象,即深拷贝。

(3)copy修饰的可变字符串属性类型始终是NSString,而不是NSMutableString,如果想让拷贝过来的对象是可变的,就要使用mutableCopy。(所有copy修饰的NSSting字符串不会被外界影响)

(4)strong表示强引用,对可变和不可变字符串都只有浅拷贝。对NSMutableString不存在深拷贝。

NSObject,id, instancetype

(1)OC里面已经有NSObject了,为啥还要用id,所有对象不知道类型的时候用NSObject代替不可以么?
不可以,因为OC里面,并不是所有的Foundation/Cocoa对象都继承息NSObject
比如NSProxy就不从NSObject继承。
所以你无法使用NSObject*指向这个对象,
即使NSProxy对象有releaseretain这样的通用方法。

(2)􏴈􏰒用id 修饰的对象,所有OC的对象 id都可以指向,编译器不会做类型校验,id调用任何OC中存在的方法都不会在编译器报错,说明id类型时运行时特性。

(3)NSobject* 指向的必须都是NSObject的子类,调用的也是NSObject里面的方法否则会报错

(4) instancetype的作用,就是使那些非关联返回类型的方法返回所在类的类型!能够确定对象的类型,能够帮助编译器更好的为我们定位代码书写问题,
instancetype和 id 相同点: 都可以作为方法的返回类型
instancetype和 id 不相同点:

IOS 核心框架

CoreAnimation ,CoreGraphics ,CoreLocation ,AVFoundation ,Foundation

IOS 的核心机制

重用机制,
OC 内存管理机制,自动释放池,ARC
RunLoop
RunTime
Block
Responder Chain
NSOperation,GCD

delegate ,notification , block

delegate 是一对一的关系,接收者可以返回值发送者;
notification可以一对多的,接受者不可以返回值给发送者。
block 使得代码更加紧凑,便于阅读

对单例的理解

屏幕快照 2019-04-21 19.31.22.png

KVO,Notification,delegate

RSA 加密算法

我们发现服务器判断用户是否登录, 完全依赖于sessionId, 一旦其被截获, 黑客就能够模拟出用户的请求。于是我们需要引入token的概念: 用户登录成功后, 服务器不但为其分配了sessionId, 还分配了tokentoken是维持登录状态的关键秘密数据。在服务器向客户端发送的token数据,也需要加密。于是一次登录的细节再次扩展。

客户端利用自己生成的私钥对token密文解密, 得到真正的token

上一篇下一篇

猜你喜欢

热点阅读