assgin
对基础数据类型 (NSInteger
)和C
数据类型(int
,float
,double
,char
,等)copy
用于String
,block
retain
用于NSObject
和其他子类。readonly
只生成getter
方法readwrite
生成setter和getter
方法。
assign:
默认类型,setter
方法直接赋值,而不进行retain
操作retain:setter
方法对参数进行release
旧值,再retain
新值。
copy:setter
方法进行Copy
操作,与retain
一样
@public
在任何地方都能直接访问对象的成员变量@package:
在同一个包下就可以直接访问,比如说在同一个框架@protected
可以在当前类及其子类对象方法中直接访问(系统默认下是用它来修饰的)@private
只能在当前类的对象方法中直接访问,如果子类要访问需要调用父类的get/set
方法
NSHashTable
是可变的set
,可以对加入的对象设置引用类型,可设置弱引用
,不影响源对象生命周期NSMapTable
是可变的Dic
,可以对加入的对象设置引用类型,可设置弱引用
,不影响源对象生命周期- 当弱引用的对象重置为nil,则自动从
NSHashTable
和NSMapTable
删除。
#include
和#import
都可以引入头文件,但是#import
只会引入一次。#import
虽然只会引入一次,但是还会导致相互引入的问题。@class
可以在头文件引入类,类可以是不存在的,可以在头文件@class
引入类,在.m
用#import
引入类实现从而解决循环引用的问题。
我们知道copy
关键词是复制一份内存地址,用新的指针指向。是属于深拷贝,而用strong
关键字是通过一个指针指向对象的内存地址,通过内存地址访问对象,是属于浅拷贝。对于strong
声明的字符串 数组字典其他地方可以通过地址直接修改对象
通过copy
修饰的NSMutableArry
对象变成了NSArray
,这样在编译类型是NSMutableArray
,在运行时是NSArray
,这样我们在编译时候调用NSMutableArray
方法是不报错的,但是在运行时候类型不一样从而导致崩溃发生。
我们可以在.h
文件声明属性用readonly
关键词,在.m
声明同样的属性用readwrite
默认的关键词。
/// .h
@interface ClassA : NSObject
@property (nonatomic, assign, readonly) int number;
@end
/// .m
@interface ClassA ()
@property (nonatomic, assign) int number;
@end
@implementation ClassA
@end
block
使用起来更加简单,比如访问作用域的变量还有代码逻辑的连贯性。代理更能表现是面向接口,能更好的描述功能和作用。如果是对外面向的接口推荐使用代理,日常开发页面之间的传值可以使用block
.并且block
和代理之间,block
的执行会比较快一些。
通过block
内部访问外部的变量,block
会自动copy
到自己的闭包空间。也就是复制了一份变量在自己的作用域内部,所以是无法修改外部的变量。通过__block
关键词修饰之后,通过复制变量的引用地址来修改和访问外部变量的。
类别可以添加属性 添加的方法不实现会报警告。可以添加实例变量,但是只能自己类访问。扩展可以添加方法,不实现不会报警告。不能添加实例变量,属性可以通过关联对象实现
分类可以将一个臃肿的类分散到多个文件中,可以将功能按照模块划分,多人多人开发一个类。分类可以给系统类添加方法,还可以拦截系统方法。继承拥有对类完全控制权,分类只对于引入文件有效。分类可以在不获取原来代码基础上增加方法,不可以删除方法,也不可以新增属性,分类具有更高的优先级。继承可以添加和删除方法,也可以新增属性。
分类只对于本次有效,推荐用分类。
这个其实是考察我们KVC
的基本原理,都是可以的.
isMemberOfClass
只能判断是否是当前的类,isKindOfClass
可以用来判断是否是当前类或者子类。
isa
其实是一个结构体,isa
对象指向当前类 当前类指向父类,父类指向元类也就是NSObject
,元类指向自己。作用是方便找到对应所在的方法。
这个第一次解除Block
时候应该经常用到,在Block
内部使用self
调用属性或者方法。
我们知道创建对象 加载图片 加载文件 创建线程都需要消耗内存,特别是加载图片和加载文件会大量消耗内存。我们还知道在大量循环中创建临时变量就直线性的内存暴涨。通过上面我们可以才去进一步的优化内存,对于创建对象,因为单利会驻留在内存里面,不要创建消耗内存大的对象作为单利。加载图片imageName:会把图片驻留在内存里面做缓存提高下一次访问速度,我们可以把大图片用imageWithContentsOfFile的方法进行加载。尽可能减少多线程创建和数量,使用NSCache作为缓存机制替换我们常用的数组和字段作为缓存手段。在大量循环中用外部变量防止多次创建变量或者在循环内部使用runloop.如果是列表形式,做到数据重用。尽可能少的创建和消耗内存
weak
属性其实是系统维护的一个Hash
表,系统会把weak
声明的属性的内存地址作为key
存放在hash
表里面。当这个属性引用技术为0
走dealloc
方法时候,通过对应内存地址作为key
再从hash
表移出出去。
每个类里面都有一个method_list
数组存储着这个类所有的方法和实现,通过selector
对应的方法名称找到对应的方法和实现。
load
放在会在第一次加载类方法会调用,调用顺序是先父类,然后子类,然后分类。initialize
会在第一次调用类方法或者实例方法时候被调用。
可以通过KVC
或者通过ivar
进行访问.
我们可以利用分类,通过交换方法实现。可以在方法之前也可以在方法后面。
@interface ClassA : NSObject
@end
@implementation ClassA
- (void)printMyName {
NSLog(@"josercc");
}
@end
@interface ClassA (Print)
@end
@implementation ClassA (Print)
+ (void)load {
Method method1 = class_getClassMethod(self, @selector(printMyName));
Method method2 = class_getClassMethod(self, @selector(printMyName1));
method_exchangeImplementations(method1, method2);
}
- (void)printMyName1 {
NSLog(@"Hello");
[self printMyName1];
}
@end
strong
会持有对象,会使对象引用计数加1weak
不会持有对象 只是存储了对象的内存地址assgin
用于声明基本类型 被assgin
声明的会存放在栈区copy
会复制一份内存地址,一般用于NSString NSArray NSDictionary
__weak
用来修饰实例变量__block
用来修饰可以在block
内部修改外部变量
atomatic
是相对线程安全的因为会自动在set
和get
方法进行加锁,但是会额外的消耗性能nonatomic
是线程不安全的性能好
- (void)viewDidLoad {
UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
alertLabel.text = @"Wait 4 seconds...";
[self.view addSubview:alertLabel];
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[backgroundQueue addOperationWithBlock:^{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];
alertLabel.text = @"Ready to go!”
}];
}
我们是在子线程操作的UI应该在主线程操作UI不会更新(
在Xcode11
测试依然会更新,只是会报不能在子线程更新UI的警告-[UILabel setText:] must be used from main thread only
)。
OC
是没有多继承的,可以通过协议进行代替。
OC
并不存在严格来说的私有方法和私有变量。对于用@public
修饰的属性和方法在.h
都可以被外部调用。在.m
实现的方法和实例变量只能本类调用就称作私有的。OC
是一个有运行时的特性,所以我们可以获取方法列表找到私有方法通过消息转发调用。对于私有变量我们可以通过KVC
,或者Ivar
进行访问。所以OC
不存在严格意义上面的私有方法和私有变量
对于const
来说 声明在谁前面就意味着谁不能修改,如果const
在第一位则向右移一位。比如int
const a
等效果于const int a
都是让整形的a
不可变。声明const
可以让编译时候代码更紧凑,让系统修改变量时候随时产生报错,防止BUG
的产生。
UITableView
有两个实例变量,visiableCells
保存当前界面显示的Cell
的数组,reusableTableCells
保存在可以重用的Cell
的字典。当第一次加载界面,reusableTableCells
没有任何重用的Cell
就会走UITableViewCell
的初始化方法进行创建,之后添加到visiableCells
数组里面。当界面滚动时候,原本展示的Cell
消失在屏幕之后。对应的Cell
对象就会添加到reusableTableCells
里面,即而从visiableCells
数组里面进行移出。当一个新的cell
将展示时候从reusableTableCells
查看是否存在重用的,如果存在就展示重用的,如果没有就重新走创建的流程。
当手机内存运行不足时候会调用ViewController
的didReceiveMemoryWarning
方法,默认时尝试释放ViewController
所拥有的View
。我们也可以重写做其他释放内存的任务。
delegate
是一对一的关系,notification
是一对多的关系。delegate
通畅用于开放接口用于其他模块的调用,notification
可以做到模块分离,通畅用于模块之间的通信。
id
代表在OC
里面的对象的指针,nil
代表是指针指向一个空的对象。
NSTimer
因为是添加到runloop
中的走的是默认的mode
,当滑动表格的时候就会切换对应mode
停止timer
。runloop
因为优先在触发时候执行输入源,才会执行runloop
中的任务,虽有会有50-100毫秒的误差。
我们想做一个精准的timer
就需要创建一个新的runloop
来不受其他输入源的影响。
添加Timer到当前的runloop
设置为NSRunLoopCommonModes
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerStart)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
- 在新线程添加
NSTimer
dispatch_async(dispatch_queue_create("", 0), ^{
@autoreleasepool {
self->_timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerStart)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
});
- GCD
timer =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"timer");
});
dispatch_resume(timer);
对比还是第三种方案简单,第二种执行的任务会在子线程,容易写出BUG
,第一种容易遗漏添加到runloop
设置对应的mode
。第三种有现成的代码块,而且还十分的精准。
系统分配16
个字节,但是真正占用只有8
个字节。
selector
只是代表方法名称,而method
包含了名称和实现。
反射机制就是通过字符串反射为对应的类,协议,方法。或者将协议,方法或者类反射为字符串。通常用于做模块化跳转,或者用于做deeplink
等运行时创建类等功能。
协议默认为@require
,使用时候要注意用weak
声明代理,防止循环引用。
通知底层核心是存在一个notication_map
的字典,通知的名称作为Key
,Value
为数组结构,数组的每个元素包含了监听的对象,方法名称,还有传递的参数。当发送通知的时候,会在notication_map
的字典当中找到对应key
的数组,通过遍历数组,对数组里面所有的监听者发送通知。当发送参数和监听的参数不一样的时候,会被忽略,这就是为什么名字相同却收不到通知。
当接受通知的参数为空可以接受发送通知参数为空或者不为空
可以接受到通知
/// 当object=nil;则都可以接受到.
/// 当不为空的时候,则key为object过滤Observer。
NSString *name = @"addNotification";
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(receData:)
name:name
object:nil];
[center postNotificationName:name object:@(1)];
[center postNotificationName:name object:nil];
NSString *name = @"addNotification";
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
NSObject *key = [NSObject new];
[center addObserver:self
selector:@selector(receData:)
name:name
object:key];
/// 1. 不接受
/// 2. 不接受
/// 3. 接受 当object设置的时候,下次一定要传过来才能接受到通知
[center postNotificationName:name object:@(1)];
[center postNotificationName:name object:nil];
[center postNotificationName:name object:key];
KVO
系统通过isa
混淆技术通过创建类的不可见子类,通过重写set
和get
方法来实现监听机制的。对于没有调用set
和get
方法是调用不了监听机制的,需要手动调用willChangeValueForKey
和didChangeValueForKey
触发。
不能,因为编译之后类的内存大小已经固定不能再添加实例变量,但是可以动态的添加实例变量。
心跳就是为了检测TCP
是否还在链接,通常由客户端发起,等待服务器响应。如果10秒没有回应就代表服务器或者客户端链接出现了问题。
通过单利创建线程,在线程内部通过NSMachPort
监听加入到当前的runloop
中,访问新建的线程没有任务就退出了。
WebSocket
是应用的一层协议,是基于一次握手之后,就通过TCP
通道传输,和HTTP
没关任何关系。WebSocket
是基于Frame
传输的,可以将传输数据分为几片Frame
。解决了大数据传输问题,可以将大数据分割称不同小的Frame
传输。可以和HTTP
一样边生成边传输,提高了传输效率。
断点续传的原理是通过控制head
中的Range
值来做的,关键是需要后台服务器支持Range
。
Cookie
是客户端会吧会话的SessionID
保存,之后发起请求把回话的SessionID
带给服务器。Session
是保存会话身份,标识身份是SessionID
,服务器可以同Session
同过客户端传递的SessionID
获取到身份信息.
非对称加密是有一堆密钥 公钥 私钥组成,私钥只能一方掌管。常用的比如RSA
。
对称加密就是加密和解密都使用同样的密钥,常用的AES,DES,3DES
。
- 客户端发起请求到服务器
- 服务器返回证书给客户端
- 客户端根据根证书对服务器返回的证书进行验证
- 客户端根据对称密钥根据服务器证书加密返给服务器
- 服务器使用私钥对客户端的密钥解密
- 双方使用密钥进行加密传输
- TCP
TCP是面向传输流 传输可靠 不丢包。没有传输大小限制,支持一对一,需要三次握手链接。
- UDP
面向字节流 传输不可靠 容易丢包。传输大小限制64K,支持一对一,一对一,多对多,不需要握手就可以链接
GET
用于查询资源POST
用于创建资源PUT
用于修改资源DELETE
用于删除资源HEAD
测试服务器性能CONNECT SSL
通信OPTIONS
测试服务器性能TRACE
测试链接
- POST
提交参数加密 默认最大支持2M文件传输(服务器配置) 通常创建修改删除资源用POST请求
- GET
提交参数可见 最大支持参数1024K大小。支持缓存,所以平时可以来做CND加速。
单利是不管怎么初始化,在进程运行期间只存在一个实例对象
GCD
在内部同过控制一个静态变量来是否走Block
方法创建对象,内部通过信号量控制调用。就算是在多线程,其他没有调用完毕就等待,等调用完毕再去看对象是否创建完毕.
- 保持应用的持续运行
- 处理App的各种事件
- 节省CPU资源,提升性能。
- 负责渲染界面的UI
RunLoop
进行处理事件的时候会自动创建一个AutoreleasePool
,在处理事件过程中会将发送autorelease
消息的对象添加到AutoreleasePool
中。等待RunLoop
处理事件结束,就释放当前的AutoreleasePool
。AutoreleasePool
则会将所有的对象进行release
-1操作。
@autoreleasepool {
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
因为滑动操作当前的RunLoop
运行在Tracking Mode
上面,为了不打断用户的操作,我们可以在Default Mode
上面进行刷新数据,也就是等待滑动结束之后,当前的RunLoop
从Tracking Mode
切换到Default Mode
再去更新数据.
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
GCD
只有在回到主线程的时候才会获取当前的RunLoop
,会触发RunLoop
的Source1
事件。
- 查看答案
- 设置图元数据
- 着色器-shader 计算图元数据(位置·颜色·其他)
- 光栅化-rasterization 渲染为像素
- fragment shader,决定最终成像
- 其他操作(显示·隐藏·融合)
因为对于UIView
和NSView
来说,他们只负责更改属性和负责交互。负责渲染呈现UI
的是CALayer
,但是渲染对于iOS
和macOS
没有什么不同。为了让代码复用,框架简单。但是又要区分iOS
和macOS
端的交互,就将UIKit
和AppleKit
底层都依赖于Core Animation
来绘制界面。