一,成员变量和属性
@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的特点
-
category
只能给某个已有的类扩充方法,不能扩充成员变量。 -
category
中也可以添加属性,只不过@property
只会生成setter
和getter
的声明,不会生成setter
和getter
的实现以及成员变量。 - 如果
category
中的方法和类中原有方法同名,运行时会优先调用category
中的方法。也就是,category
中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_
。 - 如果多个
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的区别
extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
extension在编译期决定,它就是类的一部分,但是category则完全不一样,它是在运行期决议的。extension在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它、extension伴随类的产生而产生,亦随之一起消亡。
extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension,除非创建子类再添加extension。而category不需要有类的源码,我们可以给系统提供的类添加category。
extension和category都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现。
Category VS Extension 原理详解
Protocol
利用继承、多态可以很好的保持"对扩展开放,对更改封闭"(即上文提到的开放-封闭原则OCP
),这也是面向对象语言保持开放-封闭原则最常见的办法。OC中还有另外两种语法来支持OCP:即Category和Protocol。
Protocol
只是声明一套接口,并不能提供具体实现,变相的也算是一种抽象基类的实现方式(OC本身语法并不支持抽象基类)。
Protocol只能提供一套公用的接口声明,并不能提供具体实现,它的行为是,我只负责声明,而不管谁去实现,去如何实现。 这样的话,我定义一套接口,可以使任意的类都用不同的方式去实现接口中的方法,就是为遵守了protocol
的类提供了一些额外访问这个类的一些接口,像delegate
和dataSource
用protocol
实现是最好的。
继承
继承,它基于Protocol
和Category
之间,既可以像protocol
一样只提供纯粹的接口,也可以像Category
一样提供接口的完整实现,可以自由定义类的实例变量,而且继承还可以对类以后的方法进行改写,所以继承的力量是最强大的。
在iOS开发中,继承是完全可以完成protocol
和category
的功能的,那么在开发过程中多多使用继承体系可好?
使用继承来进行扩展是一种耦合度很高的行为,对父类可以说是完全依赖,如果继承体系太过复杂,会造成难以维护的问题。如果仅仅只是对类进行扩展,并不建议使用继承,毕竟使用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
静态库和动态库在使用上的区别
静态库:连接时,静态库会被完整的复制到可执行文件中,被多次使用就有多份冗余拷贝
动态库:连接时不复制,程序运行时有系统动态加载到内存,提供程序调用,系统只加载一次,多个程序共用,节省内存
注意:项目中使用了自制的动态库,不能被上传到AppStore
多态
不同对象以自己的方式去响应相同消息的能力叫做多态,子类的指针可以指向父类
运行时
运行时机制是我们在运行时才确定这个对象的类,以及调用该类指定的方法
事件响应与响应者链
- 发生触摸事件后,系统会将事件加入到一个由
UIAPPlication
管理的队列事件中 -
UIAPPlication
会从事件对列中取出最前面的事件按照自下而上的响应原则 分发下去,通常最先发给主窗口(keyWindow
)。 - 主窗口会在视图中找到合适的响应视图,来处理触摸事件,调用视图中的事件具体处理方法,如:
touchesBegin
,touchesMove
,touchesEnd
等,事件在往上传递。
注意:事件是从父视图传递给子视图,如果父视图不接受触摸事件,子视图就不可能接收触摸事件。
视图不接收触摸事件的三种情况:
- 不接受用户交互:
userInteractionEnabled = NO;
- 隐藏:
hidden = YES;
- 透明度:
alpha = 0.0~0.01
大致触发过程:application –> window –> root view –>......–>lowest view
如何判断当前响应者的上一个响应者是谁呢?
- 判断当前view是不是控制器ViewController控制的view,如果是ViewController控制的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
NSTimer
的 target
对传入的参数都是强引用(即使是 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
对象有release
和retain
这样的通用方法。
(2)用id
修饰的对象,所有OC
的对象 id
都可以指向,编译器不会做类型校验,id
调用任何OC
中存在的方法都不会在编译器报错,说明id类型时运行时特性。
(3)NSobject*
指向的必须都是NSObject
的子类,调用的也是NSObject
里面的方法否则会报错
(4) instancetype
的作用,就是使那些非关联返回类型的方法返回所在类的类型!能够确定对象的类型,能够帮助编译器更好的为我们定位代码书写问题,
instancetype和 id 相同点: 都可以作为方法的返回类型
instancetype和 id 不相同点:
instancetype
可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;instancetype
只能作为返回值,不能像id那样作为参数。不是所有的
OC
对象都是NSObject
的子类,还有一些继承自NSProxy(虚基类)
。NSObjec*
可以指向的类型是id
的子集。
IOS 核心框架
CoreAnimation ,CoreGraphics ,CoreLocation ,AVFoundation ,Foundation
IOS 的核心机制
重用机制,
OC 内存管理机制,自动释放池,ARC
RunLoop
RunTime
Block
Responder Chain
NSOperation,GCD
delegate ,notification , block
delegate 是一对一的关系,接收者可以返回值发送者;
notification可以一对多的,接受者不可以返回值给发送者。
block 使得代码更加紧凑,便于阅读
对单例的理解
RSA 加密算法
我们发现服务器判断用户是否登录, 完全依赖于sessionId
, 一旦其被截获, 黑客就能够模拟出用户的请求。于是我们需要引入token
的概念: 用户登录成功后, 服务器不但为其分配了sessionId
, 还分配了token
, token
是维持登录状态的关键秘密数据。在服务器向客户端发送的token
数据,也需要加密。于是一次登录的细节再次扩展。
- 客户端向服务器第一次发起登录请求(不传输用户名和密码)。
- 服务器利用RSA算法产生一对公钥和私钥。并保留私钥, 将公钥发送给客户端。
- 客户端收到公钥后, 加密用户密码,向服务器发送用户名和加密后的用户密码; 同时另外产生一对公钥和私钥,自己保留私钥, 向服务器发送公钥; 于是第二次登录请求传输了用户名和加密后的密码以及客户端生成的公钥。
- 服务器利用保留的私钥对密文进行解密,得到真正的密码。 经过判断, 确定用户可以登录后,生成
sessionId
和token
, 同时利用客户端发送的公钥,对token
进行加密。最后将sessionId
和加密后的token
返还给客户端。
客户端利用自己生成的私钥对token
密文解密, 得到真正的token
。
+load 的执行顺序
规则一:父类先于子类调用
规则二:类先于分类调用
FatherClass(父类) -> SonClass(子类) -> SonClass+Load(分类) -> main -> appdelegate
同一个类的不同分类中相同方法的执行顺序
最后被加载进来的,也就是最后参加编译的分类文件中的方法会被执行。不管各个头文件的导入顺序的先后顺序
分类的方法,会覆盖本类中的方法
[self class] 和 [super class] 的区别
self
调用方法事实际上是通过objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
函数进行消息的发送,其中第一个参数是消息接收者,第二个参数op是调用的具体类的方法的selector,后面是 selector 方法的可变参数。如上例所示[self returnSomething]
实际上是id _Nullable objc_msgSend(self, @selector(returnSomething))
而returnSomething
方法会从[self class]
类中查找。super
调用方法事实际上是通过id _Nullable objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
函数进行消息的发送,但是第一个参数是一个objc_super结构体。
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
此时这个结构体的第一个成员变量receiver
就是子类,和 objc_msgSend 中的self相同。而第二个成员变量super_class就是指父类,调用 objc_msgSendSuper 的方法时会将这个结构体和returnSomething的selector传递过去。
在结构体函数里面做的事情类似这样:从objc_super结构体指向的super_class的方法列表开始找 returnSomething的selector,找到后再用objc_super->receiver去调用这个selector。找不到就会报错。