笔记2

作者: allen852 | 来源:发表于2017-03-10 14:48 被阅读0次

     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都是同步发送消息的


    相关文章

      网友评论

          本文标题:笔记2

          本文链接:https://www.haomeiwen.com/subject/dsqjgttx.html