OC的动态性:会把编译和链接是需要执行的逻辑延迟到运行时,例如使用 id 所修饰的变量会在运行的时候才确定具体类型是什么,runtime 的方法交换等。
循环引用:当一个对象间接或直接地持有另一个对象,而这个对象又有强指针指向该对象,就会引起循环应用无法释放的问题。
1.block的使用 使用 weakSelf处理
2.delegate 作为属性用weak修饰,也可以防止野指针的出现
3.NSTimer 最为成员变量。解决方式
在程序中除了在 dealloc方法去明确使用 invalid 然后赋值为nil
如果无法明确知道什么时候才可以停止,可以添加了一个类别让原来的通过@selector执行任务的方式转换为block的方式。
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(xx_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)xx_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userinfo;
if(block) {
block();
}
}
然后当控制释放的时候,停止定时器就可以
-(void)dealloc{
NSLog(@"dealloc");
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer:%@",self.timer);
}
属性修饰
assign:基本数据类型,结构体
weak:delegate(野指针,循环引用),IB引进来的UI控件(视图层级结构)
copy:不可变类型(指向不同对象),block(保证是放在堆上)
strong:修饰对象,强引用
retain:和strong类似,在修饰block的时候作用相当于assign
类别作用:
在保持原有类的情况下,增加其方法(UIScrollView,图片缓存裁剪处理)
讲一个庞大的类根据作用分解到不同的类别中,便于维护
static修饰的变量并不会改变作用范围,改变的只是内存分配方式(存放在静态区在整个运行期间一直存在)
数据持久化:原理都是数据保存到本地文件中
1.属性列表:一般使用NSUserDefaults操作(沙盒library/preference)
2.对象归档:将对象转化为NSData写入带本地文件,对象需要实现NSCoding协议
[NSKeyedArchiver archiveRootObject:self toFile:fileName];
3.SQLite3:FMDB封装库
4.Core Data:对SQLite基于面向对象的封装
栈区(stack)由编译器自动分配释放,存放方法(函数)的参数值,局部变量的值等,是一块连续的内存的区域。即栈顶的地址和栈的最大容量是系统预先规定好的。
堆区(heap)一般由程序员分配释放,是不连续的内存区域,从而堆获得的空间比较灵活。有我们去new/alloc来创建的对象,就是放在堆下面。
栈区(stack)由编译器自动分配释放,存放方法(函数)的参数值,局部变量的值等,是一块连续的内存的区域。即栈顶的地址和栈的最大容量是系统预先规定好的。
堆区(heap)一般由程序员分配释放,是不连续的内存区域,从而堆获得的空间比较灵活。有我们去new/alloc来创建的对象,就是放在堆下面。
结构体:
struct CGPoint {
CGFloat x;
CGFloat y;
};
typedef struct CGPoint CGPoint;
TableViewCell \ 页面优化:
1.尽量减少透明图层的使用来减少渲染次数,opaque = YES
2.减少对象的创建或者使用更为轻的对象,如当视图元素不需要响应用户的触摸事件使用CALayer代替UIView
3.当大量文字需要显示,cpu需要耗费比较多的资源去进行渲染和排版,所以这个时候可以选择CoreText 来处理
4.避免大图使用,程序解码比较耗时
5.复杂布局使用autoLayout会影响性能,更好的是选择手动布局
6.使用CoreGraphics进行绘制的时候,如果绘制比较耗时,可以异步处理后再返回主线程显示
7.TableViewCell的重用,避免在heightForCell方法进行较多的处理,将动态高度的缓存
7.离屏渲染(系统会在屏幕缓冲外开辟新的缓冲去处理),设置shadow时候不设置shadowPath,在设置圆角不同时使用 maskToBound和cornerRdius有时会导致,或者使用CAShapeLayer 和BezierPath结合的的方式绘制圆角。
7.如果不需要要显示透明图层,那么将试图的opaque设置为YES(默认),会让系统会将试图作为不透明去处理而加快渲染。
但如果需要使用到透明,则设置为NO。
8.减少视图的层级结构
4.可以根据需要是使用预加载或者延迟加载。
8.使用Instrument工具去检测
线程加锁 : 解决多线程同时访问同一资源导致的问题,一般通过加锁处理或者使用串行队列处理:
//串行队列
dispatch_queue_create("", DISPATCH_QUEUE_SERIAL), ^ {
}
1.GCD信号量
+(NSString*)getContacts{
//获取通讯录权限
ABAuthorizationStatus authStatus = ABAddressBookGetAuthorizationStatus();
if(authStatus != kABAuthorizationStatusAuthorized)
{
//是否有通讯录权限
__block BOOL accessGranted = NO;
ABAddressBookRef tmpAddressBook = ABAddressBookCreateWithOptions(NULL,NULL);
dispatch_semaphore_t sema=dispatch_semaphore_create(0);//创建信号量 为0
ABAddressBookRequestAccessWithCompletion(tmpAddressBook, ^(bool granted,CFErrorRef error){
accessGranted = granted;
dispatch_semaphore_signal(sema);//信号量 +1
});
//当信号量 > 0 才继续运行,并且信号量 - 1
dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER);
if(accessGranted ==NO){
return @"[]";
}
}
//读取联系人----------此处省略联系人读取步骤-------------------}
2.NSLock *lock = [NSLock alloc]init];
[lock lock];
.....
[lock unlock];
3.条件锁(基于信号量)
NSConditionLock *conditionLock = nil;
BOOL canLock = [conditionLock tryLockWhenCondition:kCondition_A];
if (canLock) {
FDLog(@"线程A lock, 请等待");
[conditionLock unlock];
}else{
FDLog(@"线程A 条件不满足,未加lock");
}
4.NSRecursiveLock 递归锁,同一个线程可以多次加锁,但是不会引起死锁,如果是NSLock,则会导致崩溃
- (void)reverseDebug:(NSUInteger )num lock:(NSRecursiveLock *)lock
{
[lock lock];
if (num<=0) {
FDLog(@"结束");
return;
}
[self reverseDebug:num-1 lock:lock];//递归
[lock unlock];//还是需要解锁
}
5.@synchronized(self) {
...
}
6.atomic的线程安全只限于setter和getter方法。会在setter方法里面加锁。
当我们去启动 RunLoop,系统会自动在内部创建自动释放池。
数组遍历
当使用block的方式进行遍历,会阻塞线程,直到遍历结束。
UIView的 drawRect,layoutSubviews 方法调用时机:
两者都是在视图将要显示(viewWillAppear之后,viewDidAppear之前调用,layoutSubviews只要addSubviews就可以调用,但是drawRect方法必须设置了frame才能调用,否则就不会被调用)
layoutSubviews
1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
4、滚动一个UIScrollView会触发layoutSubviews
5、旋转Screen会触发父UIView上的layoutSubviews事件
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
7、调用setNeedsLayout(延时到下个更新周期调用)
drawRect(在调用前会先调用sizeToFit方法,可以再这个方法计算坐标处理)
1.在UIView初始化时设置rect大小,drawRect 就会自动调用(在视图将要显示时候调用,只调用一次,viewDidLoad两方法之后掉用的),否则不被自动调用。
2.调用setNeedsDisplay(异步执行)
layoutSubviews:UIView 需要对子控件进行手动的布局,则可以重写此方法。只有在autoresizing和constraint-based behaviors of subviews不能提供我们想要的布局结果的时候,我们才应该重写此方法。
layoutSubviews方法调用先于drawRect
MVC优点:
体现在低藕性与重用性:
数据层与视图层的互相独立,可单独地对某一模块进行改变,而不会影响到其余模块,也可以根据实际需要灵活地重用各个模块。
NSOperation与GCD区别:
GCD是基于C语言的一套api;而NSOperaion底层是基于GCD开发,因此效率方面GCD更好。
而NSOperation更适用于一些复杂的任务:
1.可以对队列暂停,恢复,取消操作(NSOperationQueue cancelAllOperations setSuspended),不过这里暂停并不是暂停当前的任务而是下一个任务,恢复会从第一个没有执行的任务开始。
2.可以建立任务间的依赖关系(NSOperation setDependency)
3.NSBlockOperation NSInvocationOperation可以设置任务的优先级(setQueuePriority),
而GCD只可以设置队列的优先级.
TCP三次握手:
客户端向服务端发送请求 - 服务端返回数据包给客户端 - 客户端返回响应包给服务端,连接建立
TCP释放连接四次握手:
客户端向服务端发送断开请求 - 服务端接收到请求后先返回响应给客户端,然后连接断开后再返回一次数据包给客户端- 客户端收到服务端的返回,返回响应给服务端。
9.CoreData多线程安全处理
CoreData包含(实体对象,模型对象,上下文,持久化存储协调器)
线程注意点:
1.manageObject,manageObjectContext只能在自己的线程处理,不能跨线程
2.对于持久化存储协调器(NSPersistentStoreCoordinator)可以多线程共享
3.magageObject的线程间传递通过id,并且通过objectWithID来获取
解决方法:
共同使用一个 NSPersistentStoreCoordinator,以及两个独立的 Contexts,一个context 负责主线程与UI协作,一个context在后台负责耗时的处理,用Notifications的方式通知主线程的NSManagedObjectContext进行mergeChangesFromContextDidSaveNotification 对变化进行合并处理。
//这个通知是系统定义的通知
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
if (note.object == self.privateContext) {
dispatch_async(dispatch_get_main_queue(), ^{
//返回主线程合并处理,这里的note.object不能再主线程进行使用,需要合并处理
[self.mainContext performBlock:^{
[self.mainContext mergeChangesFromContextDidSaveNotification:note];
}];
});
}
}];
14.网络安全:
1.为了防止非法使用api,在每次向服务器请求的使用,将appKey和加密后的appSecret也发送给服务器,服务器根据appKey找到对应的appSecret与从客户端传递过来进行比对,一致的话就是合法请求。(appkey 和 secret key相当于当前账户的另外一套账号和密码机制)
2.使用https协议。
82.常见的crash情况
1.访问野指针,比如访问一个已经释放的对象的属性方法(解决方式是当一个指针所指向的对象如果已被释放,则将指针置nil)
2.访问数组类对象越界或插入了空对象
3.访问了不存在的方法
4.当某个对象会被多个线程修改(加锁处理)
5.NSNotification KVO的非对称添加删除(访问野指针)
数据库事务处理
当我们对数据库进行更新操作时,默认会将操作一条条地往数据库提交。而使用事务处理,则是将所有的操作一次性提交给数据库,当处理的过程中出现问题,则可以进行回滚操作不进行任何修改,保证数据的完整性。
所以当需要对大量的数据进行更新操作时,使用事务在效率和安全性都会大大提高。
16.断点续传
//使用HEAD方法,仅获取目标文件的信息,而不做实际的下载工作。
//[request setHTTPMethod:@"HEAD"];
/**
设置断点续传的思路:
HeaderField:头域(请求头部的字段)
可以通过指定range的范围逐步地下载指定范围内的数据,待下载完成后,再将这些数据拼接成一个文件。
1根据HEAD方法获取到要下载的文件的总大小、
2在磁盘上建立一个临时的缓冲文件,该文件的大小与目标文件大小一致
3缓冲文件中所有字节都是默认为0
4开启多线程,分别加载不同的range头指定的数据块,待数据块加载完成以后,将其分别写入对应的偏移地址。
5所有数据下载完成以后,表示文件下载完成,将临时文件名更改为目标文件。
开发的难点:
0在写入文件之前,首先要建立一个同等大小的文件。
1文件的读写问题,在oc里默认是覆盖,追加,如果要指定位置,需要用seek方法,移动文件指针。
2在多线程写入文件时,文件的锁定操作是一个问题。
*/
[request setValue:@"bytes=0-499" forKeyPath:@"range"];//表di示只读取数据的第0个字节到第499个字节。
线程间通信:
1.performSelectorOnMainThread,去主线程执行
performSelectorInBackground ,后台线程执行
2.通过NSMachPort端口
将端口添加到当前线程的runloop里,通过这个端口与其他线程进行消息的发送接收(通过delegate 来进行消息接受的回调,通过回调方法传递过来的参数,获取对方的端口,通过端口就可以发送消息)。
NSPort *myPort = [NSMachPort port];
//2. 设置port的代理回调对象myPort.delegate=self;
//3. 把port加入runloop,接收port消息
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
- (void)handlePortMessage:(NSMessagePort*)message{
//
NSPort*localPort = [message valueForKeyPath:@"localPort"];
NSPort*remotePort = [message valueForKeyPath:@"remotePort"];
//使用端口发送消息
[remotePort sendBeforeDate:[NSDatedate]
msgid:kMsg2
components:nilfrom:localPort
reserved:0];
}
APP通信方式:
1.URL Scheme:App1通过openURL的方法跳转到App2,并且在URL中带上想要的参数(需要在info.plist中配置好URL types),常用于 分享到微信朋友圈微博等功能。
2.Keychain:微信登陆,使用同一个账号平台,实现自动登录其他的平台。每个程序都有一个独立的keychain存储,只需要使用对应的登录SDK,通过共享keychain中的数据,那么就可以实现统一账户登录了。(比如密码、证书等等,就需要使用更为安全的keychain了。keychain里保存的信息不会因App被删除而丢失,在用户重新安装App后依然有效,数据还在。它是一个sqlite数据库,其保存的所有数据都是加密过的。)
3.UIPasteboard(剪切板功能):在淘宝app中将链接自定义成淘口令,引导用户进行复制,并去QQ好友对话中粘贴。然后QQ好友收到消息进行粘贴后后再打开自己的淘宝app,淘宝app每次从后台切到前台时,就会检查系统剪切板中是否有淘口令,如果有淘口令就进行解析并跳转到对于的商品页面。
绘制方式:
1.使用 drawInContext drawRect 方法,方法会自动创建画布,只需要获取上下文绘制。
2.手动创建画布
UIGraphicsBeginImageContext(CGSizeMake(400,400));
//获取上下文
CGContextRef context =UIGraphicsGetCurrentContext();
//使用CG来绘制
CGFloat lineWidth = 2.f;
CGRect allRect =CGRectMake(0, 0, 100, 100);
CGRect circleRect =CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f);
CGPointcenter =CGPointMake(20, 20);
[[UIColor blueColor]setStroke];
[[UIColor grayColor]setFill];
CGContextSetLineWidth(context, lineWidth);
//绘制
CGContextStrokeEllipseInRect(context, circleRect);
//使用beizer绘制
CGFloat startAngle = - ((float)M_PI/ 2.f);
// Draw progress
UIBezierPath*processPath = [UIBezierPath bezierPath];
processPath.lineCapStyle=kCGLineCapButt;
processPath.lineWidth= lineWidth * 2.f;
CGFloatradius = 30;
CGFloatendAngle = 30 + startAngle;
[processPath addArcWithCenter:centerradius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
[[UIColor blackColor]set];
[processPath stroke];
//获取绘制的图片
UIImage* im =UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
//将绘制的图片显示处理
UIImageView*imageView = [[UIImageViewalloc]initWithFrame:CGRectMake(50, 50, 200, 200)];
[self.view addSubview:imageView];
[imageView setImage:im];
socket长连接设计(AsyncSocket):
使用单例模式维持一个长连接,并且定时地向服务器发送心跳包来判断是否在连接中,否则重新连接。客户端需要检测连接状态,通过delegate来对连接状态进行回调,包括:正在连接,连接中,连接断开,重新连接。并且提供对应的方法让用户去进行一些常用的操作,包括:开始连接,退出登录后的断开连接,消息的收发等
当const修饰一个指针变量的时候,可以指定指针不可修挂,或者指针指向的数据不可修改
const int *a(数据不可修改,指针可以) int *const a(指针可修改,数据不可变)
UIView,UIViewController,UIApplication直接继承自UIResponder,而UIWindow继承自UIView,UIResponder 下面有对应的响应用户触摸的方法
watchdog超时机制:
当应用未能及时响应用户界面事件,如在主线程操作网络,文件操作等耗时操作导致程序卡主了,就会会杀死程序并生成watchdog超时日志。
38.观察者模式:
是一种订阅-发布模式。可以同时对一个对象进行观察,当对象的状态发生改变,观察者就会接收到对应的通知,他的优点在于观察者与消息发布者并不需要知道对方的细节,只需要做好自己的业务,并且观察者的数量并没有限制,是一种低藕的一对多的观察方式。
在ios里典型的就是NSNotification和KVO,前者常用语系统的事件通知,如键盘事件,前后台切换,或者模块之间的通信。KVO就是对对象属性的观察。
struct class 区别:
struct可以继承,可以多态,可以定义函数,和class最大区别在于变量class的默认访问控制是private,而struct默认是public;对于对象内存的管理有系统的回收机制处理,而 struct 定义的对象则是在使用完毕后自动清理分配的内存。
当需要有大量的逻辑处理使用类,如果只是CGPoint,CGSize这些轻量结构,使用struct。
枚举typedef NS_ENUM
使用情景:当需要列举的状态有限的,并且数量不多(3-4左右),使用枚举
沙盒机制(用来存放非代码文件(图片,音频,视频,属性列表(plist), sqlite数据库,文本文件,其他等等)):
Document文件夹用于保存程序运行中需要创建的文件,如sqlite文件
Library Preferences:存放应用的偏好设置(有系统维护,不能直接去修改,需要通过NSUserDefault来添加偏好设置)
Cache:缓存文件夹,在程序退出后不会删除(iTunes不会备份)
tmp:临时文件夹(在文件使用结束后删除),在手机重启目录文件会被删除;在内存底的情况下也可能会被删除,不会被iTunes备份
.app文件包,应用程序本身,不能去修改。
如果你做个记事本的app,那么用户写了东西,总要把东西存起来。那么这个文件则是用户自行生成的,就放在documents文件夹里面。
如果你有一个app,需要和服务器配合,经常从服务器下载东西,展示给用户看。那么这些下载下来的东西就放在library/cache
//获取沙盒主目录路径
NSString *homeDir = NSHomeDirectory();
//获取Documents目录路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
//获取Library的目录路径
NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
//获取Caches目录路径
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
//获取tmp目录路径
NSString *tmpDir =NSTemporaryDirectory();
进程
ios中app的运行是单进程的,进程是资源分配的基本单位,而线程是cpu调度的基本单位,是进程中的一个实体。线程没有独立的存储控件,进程下的线程共享该进程下的资源。
进程间通信方式:
1.管道
2.消息队列
3.socket
4.共享内存(常用)
objc_msgForward消息转发
当我们在对一个对象发送消息的时候,运行时调用objc_msgSend方法,根据累的分发表去寻找该方法,如果找不到就去父类找,如果依然找不到就会调用objc_msgForward进行消息转发,首先它会尝试去寻找在运行的时候是否动态地去实现了这个方法,如果没有就去寻找有没有其他的对象可以相应该方法,如果仍然没有,,则会调用forwardInvocation方法来来将消息转发给其他的对象,如果以上三步都没有处理掉就会跑出异常。
(_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候)
BAD_ACCESS在什么情况下出现?
访问了野指针,比如访问已经释放的对象的成员变量或者向他发消息
所以当指针变量所指向的对象内存被释放掉后,需要对指针赋值为nil,防止产生“野指针”。
检测UIViewController内存泄漏的原理:
1、添加一个myDealloc方法并且在里面输出对应的信息,在类方法load 里实现默认的dealloc 与自定义定位方法进行方法交换,那么根据打印处理的信息,就可以判断一个VC有没有释放掉。
2、利用ARC中weak变量不持有对象,并且在对象释放时会自动置为nil的特性,在适当的时候来检测VC是否在内存驻留。
Left Join[左联结]
返回包括左表中的所有记录和右表中联结字段相等的记录
Right Join[右联结]
返回包括右表中的所有记录和右表中联结字段相等的记录
Inner Join[等值联结]
只返回两个表中字段相等的行
sqlite优化:
1.查询优化:索引建立(防止全文检索,防止索引建立太多)
2.大量的更新操作:事务(效率,安全)
3.表的建立(灵活,字段设置)
网络请求中如何提高性能
1.在于服务器交换的数据格式方面,gson要比xml效率高,如果使用xml那么在大量数据的情况下使用基于事件的解析方式要比dom树的方式效率高
2.对请求响应的数据进行压缩 gzip
3.对请求数据进行缓存,可以使用系统默认的NSURLCache 对GET请求进行缓存,只需要在请求时设置好对应的缓存策略。
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@""] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:3];
connection= [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
ios 循环引用
1.计时器NSTimer,当我使用计时器时候,内部会有一个引用指向VC,导致VC我发释放,dealloc不会被调用。
2.delegate
3.block
如果赋值没有通过setter方法或者KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这时是不会触发kvo机制
KVO
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew = 0x01,//改变后的值
NSKeyValueObservingOptionOld = 0x02,//改变前的值
NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04, //addobserving之后会马上调用observeValueForKeyPath,不会等到值改变
NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08//分2次调用。在值改变之前和值改变之后
};
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeOldKey;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (__bridge void*)self) {
if ([keyPath isEqualToString:kKeyPathForNavigationItemRightBarButtonItems]) {
//取值
NSArray *rightBarButtonItems = [change objectForKey:NSKeyValueChangeNewKey];
}
}
}
当我们通过KVO对对象属性进行监听,如果注册的选项使用NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld,那么在接受消息出会将新和旧的值都会传递过来。如果加上NSKeyValueObservingOptionPrior选项那么在改变值得前后都会个发一次通知。
原来类似于手动触发KVO通知(因为默认下类别添加的属性因缺少setter getter方法而无法使用,通过runtime的关系属性来添加属性,如果需要对属性的改变通过KVO进行监听,因为系统这个时候是不会自动触发通知,所以就需要手动去触发):
[self willChangeValueForKey:@"mj_footer"]; // KVO
objc_setAssociatedObject(self, &MJRefreshFooterKey,//修改属性
mj_footer, OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"mj_footer"];//KVO
72.MJRefresh基本原理
对于使用下拉刷新使用的header刷新控件和下拉加载的footer刷新控件都是将这些控件放在scrollview的可见区域外,而对于下拉加载的footer控件的添加则要判断scrollview的contentSize高度和frame的高度,如果内容是超出可见区域content高度大于frame高度,则将footer空间添加到contentSize高度之后,否则就加在frame的高度之后。
因为对于header还是footer刷新的动作都是通过KVO对scrollview的y轴偏移量变化来处理,而整个下拉上拉的状态可以在:默认状态,正在下拉,刷新中,返回默认状态这几个状态中,所以将他们的这些特性抽象出来他们的父类,在里面添加KVO的消息机制,添加KVO所触发的钩子方法,不做任何实现,具体实现在header,footer的类中根据偏移量的不同是处理不同的状态变化,重写状态属性的setter方法,当偏移量发生改变,在对应的响应方法这里只需要判断并且设置当前刷新状态,而在状态属性的stter方法里,根据状态的不同来设置不同的contentInset,视图的改变。
在这里为了table或者collection的方便添加Header或者footer,添加scrollview的类别,这样就需要在里面使用到下拉刷新的header属性和上啦加载的footer属性,这个时候就是用runtime机制的关系属性的方法来处理,并且在这两个setter方法添加KVO的手动触发通知willChangeValueForKey和didChangeValueForKey。
73.通过UIPanGestureRecognizer实现控件的拖拽效果
-(void)panAction:(UIPanGestureRecognizer *)pan
{
//以self.playerView的左上角为坐标原点
//CGPoint point=[pan locationInView:self.pointView];
//获取的点是以手指按下的点为原点的
CGPoint point1 = [pan translationInView:pan.view];
UIView *targetView = pan.view;
targetView.center = CGPointMake(targetView.center.x + point1.x, targetView.center.y + point1.y);
//清空位移数据,避免拖拽事件的位移叠加
[pan setTranslation:CGPointZero inView:pan.view];
}
数据传值方式
1.变量之间的数据传递
2.指针类型之间的地址传递
3.代理设置模式的数据传值
4.通过的block传递
5.系统通知传值(userInfo)
crash日志分析
在我们每次编译(需要将debug的选项改为dsym)或打包版本时候都会自动产生一个dsym文件,这是一个16进制的函数地址映射文件,首先检查.dsym和.crash文件的UUID是一致的,然后通过这个文件和崩溃日志.crash文件,使用XCODE自带的symbolicatecrash命令行工具,就可以导出一个.log文件,里面就可以让我们定位到程序哪里出问题。所以每次发布版本的时候就需要将.xcarchive文件夹保存下来,里面就有我们所需要的文件。这里还需要注意要。
Time Profiler
按照固定的时间间隔来跟踪每一个线程的堆栈信息,从而让我们方便地看到程序运行过程中各个方法正在消耗CPU时间
id声明的对象具有运行时的特性,知道运行时才回去决定他的类型,即可以指向任意继承自NSObject的对象
82.判断cell是否在屏幕内
1. 获取当前cell对于tableview的位置 rectForRowAtIndexPath
2.获取table y轴上的偏移量,将cell的frame的y坐标 - 偏移量,获取相对于内容视图的位置
3.可视区域为table 的frame,然后通过函数 CGRectIntersectsRect 判断两个位置是否存在重叠
4.这里如果是加入到导航栏中的table,还需要考虑到contentInset内编剧
CGFloat offsetY = self.tableView.contentOffset.y;
CGRect contentRect = self.tableView.frame;
CGRect rectCell = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
rectCell.origin.y = rectCell.origin.y - offsetY;
if(CGRectIntersectsRect(contentRect, rectCell)){
NSLog(@"再屏幕内");
}else{
NSLog(@"--不再屏幕内");
}
解决UITableView中Cell重用机制导致内容出错的方法总结
1.不使用重用机制
// UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; //改为以下的方法
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; //根据indexPath准确地取出一行
每个cell指定不同的重用标识符
2.内容出错往往是加载过慢导致内容没有及时地更新,可以使用缩略图,显示默认图片,预加载进缓存中等方式处理。
哪些途径可以让ViewController瘦下来
1.将视图处理单独封装(如各种窗口,cell)
2.将业务逻辑的处理,如网络数据库处理等可以放到对应的model里进行
Objective-C如何对【已有的方法】,添加自己的功能代码以实现类似记录日志这样的功能?
1.创建一个方法,在里面调用原有的方法,并且加上记录功能
2.使用runtime 的方法交换,对原有方法和自定义的方法进行交换。
CADisplayLink保持着和屏幕刷新率形同的频率进行回调
//创建对象
self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
//注册到runloop中
[_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];//停止
[self.displayLink invalidate];
self.displayLink = nil;
对于一些需要高频率刷新以致到达流畅效果的,如动画,绘制,视频画面等,一般使用TA。
当我们使用readonly去修饰属性,意味着不会生成setter方法,那么通过访问方式如obj.name self.name这些实际就是访问setter方法,但是因为没有所以这样调用会报错,所以修改属性的方法就是在类内部使用_name这种方式去修改。当然可以用KVC的方式去修改(setter方法->accessInstanceVariablesDirectly yes->成员变量_name _isName name isName->setValues forUndefineKey该方法默认抛异常,可以去重写)
查找:
get<key>,<key>,is<key> 的顺序方法查找getter方法,如果没找到回去找类似于<key>AtIndex类似的数组方法。
+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_key,_isKey,key,isKey 的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+(BOOL)accessInstanceVariablesDirectly返回NO的话,那么会直接调用valueForUndefinedKey:
还没有找到的话,调用valueForUndefinedKey:
什么时候用@autoreleasepool
根据Apple的文档,使用场景如下:
1.写循环,循环里面包含了大量临时创建的对象。(本文的例子,相册,本地文件的遍历就会产生比较多开销较大的对象,使用block来遍历,在遍历的时候会在内部创建自动释放池来对对象及时释放)
2.长时间在后台运行的任务。(如长时间运行在后台的网络服务)
97.github + xcode结合使用
特点:
分布式:可以将代码提交到本地代码库,在确定后再更新到服务器上。
流程:
一般在开发中,多人合作开发的时候,版本控制非常重要,所以一定要有稳定的主分支Master,开发功能的分支developer,预发布的分支release这三个重要分支。每次有新功能和需求的时候每个开发人员就从developer分支分别拉取项目开发,最后合并入developer,功能完成后就并入release,修改bug时在release分支操作,修复完成后分别并入Master和developer分支,最后从Master分支拉取最终的代码打包上传APP Store
常用命令:
commit 将代码提交到本地代码库
push 将本地代码库提交到服务器
pull 将服务器代码更新到本地上
merge 分之合并
viewController的生命周期
初始化:
1.initWithNibName(通过代码调用,如present,pushNavigation)
2.initWithCoder(如果使用 storyboard 调用VC,VC这个时候是放在storyboard中),然后调用 awakeFromNib
如果view为 nil
loadView (系统通过代码方式创建一个空的view),如果自己覆盖,则需要同样按照系统的方法去写
UIView *view = [[UIView alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.view = view;
不要使用 [super loadView]
viewDidLoad:view加载完毕
viewWillAppear:控制器的view将要显示
viewWillLayoutSubviews:控制器的view将要布局子控件
viewDidLayoutSubviews:控制器的view布局子控件完成
这期间系统可能会多次调用viewWillLayoutSubviews 、 viewDidLayoutSubviews 俩个方法
viewDidAppear:控制器的view完全显示
viewWillDisappear:控制器的view即将消失的时候
viewDidDisappear:控制器的view完全消失的时候
loadView viewDidLoad
loadView方法在控制器的view为Nil的时候会调用,若控制器有关联的 Xib 文件,该方法会从 Xib 文件中加载 view;如果没有,则创建空白 UIView 对象。
如果用storyboard初始化控制器,就不用调用loadview方法了。如果重写这个方法给控制器创建view则这个view必须是一个单例,而且不能被其他的控制器使用.并且不可以调用super。
不建议使用loadview,可以根据自己的需要在storyboard或者viewdidload中创建自己需要的view给控制器,如果使用 Interface Builder 创建 view,则务必不要重写该方法。
viewDidlLoad :view 被加载到内存后调用,不管什么情况都会被调用,用于视图的初始化。
集合遍历方法
1.对于数据量比较大,或者在便利过程中会产生一些消耗较大的临时,使用block的形式遍历更好,因为使用多线程的方式,并且内部会自动创建一个autoreleasepool,对临时创建的对象及时释放。
2.一般的遍历,如果不需要使用到下标,那么使用for in的方式更直观效率也高。
3.对于block的数组方式,除了NSEnumerationConcurrent这种会在子线程内遍历,其余都是在主线程遍历。NSEnumerationConcurrent使用了GCD的group原理,当遍历完成,返回主线程
常量
define 宏:只是在预处理器里进行文本替换,不能声明类型,会多次地分配内存。除了定义常量,还可以定义函数宏
#define MIN(A,B) A < B ? A : B
const 常量:只分配一次内存
修饰局部变量
static const (内存分配在静态区,在运行周期中保持一份)
声明全局常量:
extern NSString *const kName
UIKIT_EXTERN
FOUNDATION_EXPORT
autorelease 对象释放
1.autorelease 本质上就是延迟调用 release,autorelease pool,其实这个pool本质上是一个stack,扔到pool中的对象等价于入栈
2.默认下,对象会在当前runloop迭代结束的时候释放。
viewDidAppear 调用之前,NSAutoreleasePool会在当前runLoop迭代结束的时候被销毁,向池中的对象发送release消息,并且pop弹出栈中。
3.手动指定 @autoreleasepool {},当出了@autoreleasepool {}的作用域时,当前autoreleasepool被drain,其中的autoreleased对象被release
4.对于每一个Runloop,系统会隐式创建一个Autorelease pool(自然会有多个Autorelease pool),这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。
- (void)viewDidLoad {
[superviewDidLoad];
// @autoreleasepool {
// NSString *string = [NSString stringWithFormat:@"leichunfeng"];//对象创建时计数器+1,有变量指向它,+1,计数器为= 2
// string_weak_ = string;
// }
//出了池的作用域,池被释放,对象计数器 - 1,局部变量为nil,计数器-1,这时候对象被释放
// NSString *string = nil;
// @autoreleasepool {
// string = [NSString stringWithFormat:@"leichunfeng"];
// string_weak_ = string;
// }
出了池的作用域,池被释放,对象计数器 - 1,局部变量这个时候还存,所以只有在viewdidload结束的时候,才会置nil,计数器-1,这时候对象被释放,所以对象在viewdidload内还存在,在viewwillappear才会是nil
NSLog(@"string: %@", string_weak_);
}
block对象就是一个结构体,里面有isa指针指向自己的类(global malloc stack),有desc结构体描述block的信息,引用到的__block变量,最重要的block结构体有一个函数指针,指向block代码块。
KVO,NSNotification Delegate都是同步发送消息的
网友评论