iOS -基础总结(持续更新)

1、@synthesize @dynamic 的区别

@property有两个对应的词,@synthsize @dynamic如果都没写,那么默认就是@synthsize var = _var;
@synthsize 如果没有手动实现setter getter方法那么自动生成 ,自动生成_var变量。
@dynamic告诉编译器:属性的setter,getter方法有用户自己实现,不自动生成.假如一个属性被声明为@dynamic var 那么如果不实现setter getter方法,编译阶段不会报错,但是一旦使用instance.var = someVar ,crash

2、NSMutableArray,NSMutableString用copy修饰会crash
iOS -基础总结(持续更新)_第1张图片
图片.png
3、strong、retain、weak、assign、copy、nonatomic、atomic、nonullable、nullable

weak 如引用 一般用于代理声明的时候使用,weak 可以在对象为 nil 的时候自动把指针置为 nil 防止野指针的出现;assign 没有此功能。
assign 一般用于基本数据类型的声明,例如:int double float 等。
strong :strong修饰的属性一般不会自动释放;在OC中,对象默认是强指针,在实际开放中一般属性对象一般用strong来修饰。
retain 在 mrc 中和 ARC 中的 strong 一样。
nonullable 表示对象不应该为空。
nullable 表示对象可以是 NULL 或 nil
copy 遵循 NSCopying 协议的对象 实例化的对象都应该用 copy

4、block中weakSelf、strongSelf

是不是所有的block都要用weak self?
当block不是self的属性时,block内部使用self也不会造成内存泄露,当 block 是 self 的属性的时候,在 block 外使用 weakself, 在 block 内部 strongself .这样会在使用后立马释放.以及在 block 体内修改一个变量的时候,在外面声明的时候记住使用__ block 修饰.
block 为什么使用 copy 修饰?
使用了外部变量,这种情况也正式我们平时所常用的方式,Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.所以在使用Block属性时使用Copy修饰,而在ARC模式下,系统也会默认对Block进行copy操作.

5、泛型、__kindof的使用,以及 iOS9新关键词

1、nonnull表示不能为空。用法

@property (nonnull, nonatomic, copy) NSString *name;//写法一
@property (nonatomic, copy) NSString *__nonnull name;//写法二,小写时为两个下划线
@property (nonatomic, copy) NSString *_Nonnull name;//写法三,大写时为一个下划线

2、nullable表示可以为空。用法

@property (nullable, nonatomic, copy) NSString *gender;//写法一
@property (nonatomic, copy) NSString * __nullable gender;//写法二,小写时为两个下划线
@property (nonatomic, copy) NSString * _Nullable gender;//写法三,大写时为一个下划线
  • 每个属性都要写关键字很麻烦,用下面的方法(UIImageView.h 文件)。
NS_ASSUME_NONNULL_BEGIN

@class UIImage;

NS_CLASS_AVAILABLE_IOS(2_0) @interface UIImageView : UIView 

- (instancetype)initWithImage:(nullable UIImage *)image;
- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage NS_AVAILABLE_IOS(3_0);

@property (nullable, nonatomic, strong) UIImage *image; // default is nil
@property (nullable, nonatomic, strong) UIImage *highlightedImage NS_AVAILABLE_IOS(3_0); // default is nil
@property (nonatomic, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default is NO

@property (nonatomic, getter=isHighlighted) BOOL highlighted NS_AVAILABLE_IOS(3_0); // default is NO

// these allow a set of images to be animated. the array may contain multiple copies of the same

@property (nullable, nonatomic, copy) NSArray *animationImages; // The array must contain UIImages. Setting hides the single image. default is nil
@property (nullable, nonatomic, copy) NSArray *highlightedAnimationImages NS_AVAILABLE_IOS(3_0); // The array must contain UIImages. Setting hides the single image. default is nil

@property (nonatomic) NSTimeInterval animationDuration;         // for one cycle of images. default is number of images * 1/30th of a second (i.e. 30 fps)
@property (nonatomic) NSInteger      animationRepeatCount;      // 0 means infinite (default is 0)

// When tintColor is non-nil, any template images set on the image view will be colorized with that color.
// The tintColor is inherited through the superview hierarchy. See UIView for more information.
@property (null_resettable, nonatomic, strong) UIColor *tintColor NS_AVAILABLE_IOS(7_0);

- (void)startAnimating;
- (void)stopAnimating;
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly, getter=isAnimating) BOOL animating;
#else
- (BOOL)isAnimating;
#endif

// if YES, the UIImageView will display a focused appearance when any of its immediate or distant superviews become focused
@property (nonatomic) BOOL adjustsImageWhenAncestorFocused UIKIT_AVAILABLE_TVOS_ONLY(9_0);

// if adjustsImageWhenAncestorFocused is set, the image view may display its image in a larger frame when focused.
// this layout guide can be used to align other elements with the image view's focused frame.
@property(readonly,strong) UILayoutGuide *focusedFrameGuide UIKIT_AVAILABLE_TVOS_ONLY(9_0);

@end

NS_ASSUME_NONNULL_END
  • 注意:NS_ASSUME_NONNULL_BEGIN
    NS_ASSUME_NONNULL_END 要成对出现,不然报错。
    一般用于头文件.h 将声明包含起来针对所有属性添加 nonnull 修饰

3、null_resettable 作用:setter可为空, getter不可为空。

@property (null_resettable, nonatomic, strong) UIColor *tintColor NS_AVAILABLE_IOS(7_0);
  • 当看到由null_resettable修饰的属性时,就应该猜想这个属性的初始化采用了懒加载方式。

4、泛型

  • 例如下面的数组中只能存放Image类型的对象。以及注意写法。
@property (nullable, nonatomic, copy) NSArray *animationImages; // The array must contain UIImages. Setting hides the single image. default is nil
@property (nullable, nonatomic, copy) NSArray *highlightedAnimationImages NS_AVAILABLE_IOS(3_0); // The array must contain UIImages. Setting hides the single image. default is nil

除了使用系统的泛型以外,我们还可以使用自定义泛型。
例如

@property( nonatomic, copy) NSArray *userModelArr;

5、协变性与逆变性

__covariant - 协变性,子类型可以强转到父类型(里氏替换原则)。
__contravariant - 逆变性,父类型可以强转到子类型

例如

@interface NSArray<__covariant ObjectType> : NSObject 

6、__kindof
加__kindof修饰后,该方法的返回值原本是NSArray,但是方法里边却返回了一个NSArray的子类NSMutableArray,也就是说,加__kindof修饰后,本类及其子类都是返回,调用使用时也可以使用本类或者本类的子类去接收方法的返回值。

- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;  // Used by the delegate to acquire an already allocated cell, in lieu of allocating a new one.
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0); // newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered
- (nullable __kindof UITableViewHeaderFooterView *)dequeueReusableHeaderFooterViewWithIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);  // like dequeueReusableCellWithIdentifier:, but for headers/footers

7、* __weak __strong *

__weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }

    };

__weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。

__block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。

但是__block有一点:这只是限制在ARC环境下。在非arc下,__block是可以避免引用循环的

8、__unsafe_unretained
__unsafe_unretained:和__weak 一样,唯一的区别便是,对象即使被销毁,指针也不会自动置空, 此时指针指向的是一个无用的野地址。如果使用此指针,程序会抛出 BAD_ACCESS 的异常。

9、* __bridge、__bridge_transfer、__bridge_retained *
在开发iOS应用程序时我们有时会用到Core Foundation对象简称CF,例如Core Graphics、Core Text,并且我们可能需要将CF对象和OC对象进行互相转化,我们知道,ARC环境下编译器不会自动管理CF对象的内存,所以当我们创建了一个CF对象以后就需要我们使用CFRelease将其手动释放,那么CF和OC相互转化的时候该如何管理内存呢?答案就是我们在需要时可以使用__bridge,__bridge_transfer,__bridge_retained
(1)__bridge:CF和OC对象转化时只涉及对象类型不涉及对象所有权的转化;

NSURL *url = [[NSURL alloc] initWithString:@"http://www.baidu.com"];
CFURLRef ref = (__bridge CFURLRef)url;

(2)__bridge_transfer:常用在讲CF对象转换成OC对象时,将CF对象的所有权交给OC对象,此时ARC就能自动管理该内存;

CFStringRef cfString= CFURLCreateStringByAddingPercentEscapes(
                                                                  NULL,
                                                                  (__bridge CFStringRef)text,
                                                                  NULL,
                                                                  CFSTR("!*’();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
NSString *ocString = (__bridge_transfer CFStringRef)cfString;

(3)__bridge_retained:(与__bridge_transfer相反)常用在将OC对象转换成CF对象时,将OC对象的所有权交给CF对象来管理;当使用_bridge_retained标识符以后,代表OC要将对象所有权交给CF对象自己来管理,所以我们要在ref使用完成以后用CFRelease将其手动释放.

NSURL *url = [[NSURL alloc] initWithString:@"http://www.baidu.com"];
CFURLRef ref = (__bridge_retained CFURLRef)url;
CFRelease(ref);
6、NSTimer model枚举值
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:@{@"key":@"value"} repeats:true];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:@{@"key":@"value"} repeats:true];
        [[NSRunLoop currentRunLoop] run];

    });

这行代码的作用就是打开当前线程的runLoop,在cocoaTouch框架中只有主线程的RunLoop是默认打开的,而其他线程的RunLoop如果需要使用就必须手动打开,所以如果我们是想要添加到主线程的RunLoop的话,是不需要手动打开RunLoop的。

timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:@{@"key":@"value"} repeats:true];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

为什么在滚动scrollView的时候NSTimer会停止工作,这里就涉及到了RunLoop的几个Mode了。

Default mode(NSDefaultRunLoopMode)
默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。
Connection mode(NSConnectionReplyMode)
处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。
Modal mode(NSModalPanelRunLoopMode)
处理modal panels事件。
Event tracking mode(UITrackingRunLoopMode)
在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。
Common mode(NSRunLoopCommonModes)
这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定义modes。

我们上面的代码,包括第一段代码都是创建NSTimer以后并添加到Runloop的默认模式—— NSDefaultRunLoopMode,而当我们滚动scrollView的时候runloop将会切换到UITrackingRunLoopMode,同事关闭默认模式,而我们的NSTimer很不幸的处于默认模式中,所以当然就停止工作了,而我们的第二段代码同样处于默认模式中,但是由于并不是与主线程处于同一个线程中,所以能够继续工作。
除了像第二段代码那样处理,我们还可以这样来处理NSTimer,能够让他在任何情况下工作:

timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:@{@"key":@"value"} repeats:true];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
7、运算与、运算或、三目运算
/*
     
     按位运算符 异或(真真得假,假假得假,真假得真)
     
     0 ^ 1 得 1
     
     1 ^ 1 得 0
     
     0 ^ 0 得 0
     
     1 ^ 0 得 1
     
     
     按位与&(真真得真,真假得假,假假得假)
     
     0 & 0 =0
     
     0 & 1 =0
     
     1 & 0 =0
     
     1 & 1 =1
     
     */
    
    /*
     按位或运算符(|)
     0 | 0 = 0, 0 | 1 = 1, 1 | 0 = 1, 1 | 1 = 1
     
     */

①不用加法获取两个数的和

int jw = a & b;
    int jg = a ^ b;
    while(jw)
    {
        int t_a = jg;
        int t_b = jw <<1;
        jw = t_a & t_b;
        jg = t_a ^ t_b;
    }
DebugLog(@"%d",jg);

②不用第三个变量交换两个数的值

int a = 3; int b = 5;
    
    /*
     a = 011 = 0 * 2^2 + 1 * 2^1 + 1 * 2^0 = 3
     
     b = 101 = 1 * 2^2 + 0 * 2^1 + 1 * 2^0 = 5
     
     */
    
    a = a ^ b;//011 ^ 101 = 110 = 6
    b = b ^ a;//101 ^ 110 = 011 = 3
    a = a ^ b;//110 ^ 011 = 101 = 5

三目运算符

   int max = (a > b?a : b) > c?(a > b?a:b):c;
    int min = (a > b?b : a) > c?c:(a > b?b:a);
    int mid = (a + b + c - max - min);
    printf("这三个数的中间值是:%d",mid);
8、Runtime原理

1、 runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者。
2、 RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
3、 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
4、 OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
5、 只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

  • 常见作用
    1. 动态的添加对象的成员变量和方法,修改属性值和方法
    2. 动态交换两个方法的实现
    3. 实现分类也可以添加属性
    4. 实现NSCoding的自动归档和解档
    5. 实现字典转模型的自动转换
    6. 动态创建一个类(比如KVO的底层实现)

runtime 的简单使用:http://www.jianshu.com/writer#/notebooks/5614218/notes/11629842

9、KVC、KVO、NSNotificationCenter、delegate

KVC概述

  • KVC是Key Value Coding的简称。它是一种可以通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。

  • 关键方法定义在 NSKeyValueCodingProtocol

  • KVC支持类对象和内建基本数据类型。
    KVC使用

  • 获取值
    valueForKey: 传入NSString属性的名字。
    valueForKeyPath: 属性的路径,xx.xx
    valueForUndefinedKey 默认实现是抛出异常,可重写这个函数做错误处理

  • 修改值
    setValue:forKey:
    setValue:forKeyPath:
    setValue:forUnderfinedKey:
    setNilValueForKey: 对非类对象属性设置nil时调用,默认抛出异常。

KVC运用了isa-swizzing技术。isa-swizzing就是类型混合指针机制。KVC通过isa-swizzing实现其内部查找定位。isa指针(is kind of 的意思)指向维护分发表的对象的类,该分发表实际上包含了指向实现类中的方法的指针和其他数据。

KVO
键值观察Key-Value-Observer就是观察者模式。
观察者模式的定义:一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的。观察者模式较完美地将目标对象与观察者对象解耦。

当需要检测其他类的属性值变化,但又不想被观察的类知道,有点像FBI监视嫌疑人,这个时候就可以使用KVO了。

KVO同KVC一样都依赖于Runtime的动态机制

KVO的实现分析

使用观察者模式需要被观察者的配合,当被观察者的状态发生变化的时候通过事先定义好的接口(协议)通知观察者。在KVO的使用中我们并不需要向被观察者添加额外的代码,就能在被观察的属性变化的时候得到通知,这个功能是如何实现的呢?同KVC一样依赖于强大的Runtime机制。

系统实现KVO有以下几个步骤:

  • 当类A的对象第一次被观察的时候,系统会在运行期动态创建类A的派生类。我们称为B。
  • 在派生类B中重写类A的setter方法,B类在被重写的setter方法中实现通知机制。
  • 类B重写会 class方法,将自己伪装成类A。类B还会重写dealloc方法释放资源。
  • 系统将所有指向类A对象的isa指针指向类B的对象。

KVO同KVC一样,通过 isa-swizzling 技术来实现。当观察者被注册为一个对象的属性的观察对象的isa指针被修改,指向一个中间类,而不是在真实的类。其结果是,isa指针的值并不一定反映实例的实际类。

NSNotificationCenter
通知:通知中心实际上是在程序内部提供了消息广播的一种机制。通知中心不能在进程间进行通信。实际上就是一个二传手,把接收到的消息,根据内部的一个消息转发表,来将消息转发给需要的对象。通知中心是基于观察者模式的,它允许注册、删除观察者。

一个NSNotificationCenter可以有许多的通知消息NSNotification,对于每一个NSNotification可以有很多的观察者Observer来接收通知。

  • 注意通知发送之前先注册通知,否则接收不到发送的通知。
    通知代码量少,写作上比较方便,在不确定哪些位置接收消息的时候使用通知。通知可以实现一对多,即发送一个通知,在需要的地方可以只要注册了该通知,都可以执行对应的操作。
  • 通知的劣势:如果通知过多,会造成通知的管理复杂,如果管理不好,你会接收到莫名其妙的消息,而无法追踪。

delegate
A要做的事交给 B 去做,在 A 中声明 B 要做的事,B 遵循 A 的协议去完成 A 交给的事。一般是一对一的。执行效率上要比通知好一些。

  • 注意 delegate 修饰的时候使用 weak 防止出现循环引用。在对象为 nil 的时候指针也会自动置为 nil,防止野指针的出现。
10、线程之间的通信
- (void)viewDidLoad
{
    // 1.给定图片的url
    NSURL *url = [NSURL URLWithString:@"http://b.hiphotos.baidu.com/image/pic/item/e4dde71190ef76c666af095f9e16fdfaaf516741.jpg"];
    // 2.开启线程,在后台执行download方法
   [self performSelectorInBackground:@selector(download:) withObject:url];
}

- (void)download:(NSURL *)url
{
    // 在子线程下载图片
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 设置图片,执行self.imageView的setImage:方法
//    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

    // 另一张设置图片的方法
    // 回到主线程中执行 showImage:方法,在此方法中设置图片
    [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

}

-(void)showImage:(UIImage *)image
{
    // 更新UI
    self.imageView.image = image;
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 1.下载图片(耗时)
    dispatch_async(queue, ^{
        NSLog(@"%@", [NSThread currentThread]);
        // 1.创建URL
        NSURL *url = [NSURL  URLWithString:@"http://stimgcn1.s-msn.com/msnportal/ent/2015/08/04/7a59dbe7-3c18-4fae-bb56-305dab5e6951.jpg"];
        // 2.通过NSData下载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 3.将NSData转换为图片
        UIImage *image = [UIImage imageWithData:data];

        // 4.更新UI
//        self.imageView.image = image;
//        NSLog(@"更新UI完毕");
        // 如果是通过异步函数调用, 那么会先执行完所有的代码, 再更新UI
        // 如果是同步函数调用, 那么会先更新UI, 再执行其它代码
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"%@", [NSThread currentThread]);
            self.imageView.image = image;
            NSLog(@"更新UI完毕");
        });
        NSLog(@"Other");
    });
NSOperation
1.第一种方法

// 1.创建一个新的队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.添加任务(操作)
    [queue addOperationWithBlock:^{
        // 2.1在子线程中下载图片
        NSURL *url  = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 2.2回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];

2.第二种方法(添加依赖)

/ 1.创建一个队列
    // 一般情况下, 在做企业开发时候, 都会定义一个全局的自定义队列, 便于使用
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.添加一个操作下载第一张图片
    __block UIImage *image1 = nil;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url  = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        image1 = [UIImage imageWithData:data];
    }];

    // 3.添加一个操作下载第二张图片
    __block UIImage *image2 = nil;
     NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url  = [NSURL URLWithString:@"http://imgcache.mysodao.com/img1/M02/EE/B5/CgAPDE-kEtqjE8CWAAg9m-Zz4qo025-22365300.JPG"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        image2 = [UIImage imageWithData:data];
    }];
    // 4.添加一个操作合成图片
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
        [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
        UIImage *res = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        // 5.回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = res;
        }];
    }];

    // 6.添加依赖

    [op3 addDependency:op1];
    [op3 addDependency:op2];

    // 7.添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
11、@try 与 @catch
  • 作用 : 开发者将引发异常的代码放在 @try 代码块中, 程序出现异常 使用 @catch 代码块进行捕捉;
    -- 每个代码块作用 : @try 代码块存放可能出现异常的代码, @catch 代码块 异常处理逻辑, @finally 代码块回收资源;

-- @try 与 @catch 对应关系 : 一个 @try 代码块 可以对应 多个 @catch 代码块;
-- {} 省略问题 : 异常捕获的 @try @catch @finally 的花括号不可省略;

ios中很少用到try 和catch**

简单的来说,Apple虽然同时提供了错误处理(NSError)和异常处理(exception)两种机制,但是Apple更加提倡开发者使用NSError来处理程序运行中可恢复的错误。而异常被推荐用来处理不可恢复的错误。

原因有几个,在非gc情况下,exception容易造成内存管理问题(文档有描述即使是arc下,也不是安全的);exception使用block造成额外的开销,效率较低等等。

try{
//1:抛出异常的代码
//2:代码
}catch(){
//3:代码
//4:抛出异常
}finally{
//5:代码
}
//6:代码

首先要明确的一点是:不管try是否抛出异常,finally语句块都会执行。

整个try,catch,finally执行有以下几种情况:

1:try语句块没有抛出异常。如果是这种情况,程序会执行try,finally以及finally块之后的代码;

2:try语句块抛出了异常并且catch有匹配的异常。当遇到try里面抛出的异常后,try块里面剩下的代码就不执行了,跳转到catch块里面。

这里又可以分为2种情况。第一种,抛出的异常被后面的catch捕获,而catch又没有抛出新的异常,那么执行顺序是1356 ;第二种,如果catch里面又抛出新的异常,顺序是1345,然后将新的异常返回给方法调用者,6就不执行了 ;

3:try语句块抛出了异常,但是后面的catch没有能匹配的异常。那么会执行try和finally里面的语句也就是15,然后将该异常返回给方法调用者,不执行6 。
总结:
如果异常不能被捕捉的话,finally{}后面的语句就不会执行了,而finally{}一定被执行
try catch还有一个灵活的巧用:

有时候我们加的全局断点并不能跳到异常的代码块,并且没有答应任何异常信息,
我们根据异常的上下文 找到异常代码块但是不知道到底是报的什么异常,
那么可以对那个异常代码块包上一个try catch ,
然后在catch中打印exception的内容,这样就能够知道到底是出现了什么异常。
每当出现bug或者crash的时候,我们总是习惯性的加入了NSLog或则单步调试。

我们来看一下 AF 里面的一段代码

if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
            if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
                @try {
                    [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];

                    if (context == AFTaskCountOfBytesSentContext) {
                        [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
                    }

                    if (context == AFTaskCountOfBytesReceivedContext) {
                        [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
                    }
                }
                @catch (NSException * __unused exception) {}
            }
        }

阿里云里面的一段代码

@try {
            result = block(self);
        } @catch (NSException *exception) {
            tcs.exception = exception;
            return;
        }
12、const、static、extern

①const与宏的区别

  • const简介:之前常用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量。

    • 编译时刻:宏是预编译(编译之前处理),const是编译阶段。

    • 编译检查:宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。

    • 宏的好处:宏能定义一些函数,方法。 const不能。

    • 宏的坏处:使用大量宏,容易造成编译时间久,每次都需要重新替换。
      ②const作用:限制类型

  • 1.const仅仅用来修饰右边的变量(基本数据变量p,指针变量*p)

  • 2.被const修饰的变量是只读的。

// 定义变量
    int a = 1;

    // 允许修改值
    a = 20;

    // const两种用法
    // const:修饰基本变量p
    // 这两种写法是一样的,const只修饰右边的基本变量b
    const int b = 20; // b:只读变量
    int const b = 20; // b:只读变量

    // 不允许修改值
    b = 1;

    // const:修饰指针变量*p,带*的变量,就是指针变量.
    // 定义一个指向int类型的指针变量,指向a的地址
    int *p = &a;

    int c = 10;

    p = &c;

    // 允许修改p指向的地址,
    // 允许修改p访问内存空间的值
    *p = 20;

    // const修饰指针变量访问的内存空间,修饰的是右边*p1,
    // 两种方式一样
    const int *p1; // *p1:常量 p1:变量
    int const *p1; // *p1:常量 p1:变量

    // const修饰指针变量p1
    int * const p1; // *p1:变量 p1:常量

    // 第一个const修饰*p1 第二个const修饰 p1
    // 两种方式一样
    const int * const p1; // *p1:常量 p1:常量

    int const * const p1;  // *p1:常量 p1:常量

③static

  • 修饰局部变量:
1.延长局部变量的生命周期,程序结束才会销毁。

2.局部变量只会生成一份内存,只会初始化一次。

3.改变局部变量的作用域。
  • 修饰全局变量
1.只能在本文件中访问,修改全局变量的作用域,生命周期不会改

2.避免重复定义全局变量

④extern
用来获取全局变量(包括全局静态变量)的值,不能用于定义变量。
⑤static与const联合使用

static  NSString * const key = @"name";

⑥extern与const联合使用
开发中使用场景:在多个文件中经常使用的同一个字符串常量,可以使用extern与const组合。
tatic与const组合:在每个文件都需要定义一份静态全局变量。
extern与const组合:只需要定义一份全局变量,多个文件共享。

13、#import和#include和@class用法

include <> :用于对系统文件的引用,编译器会在系统文件目录下去查找该文件。

include "xx.h":用于对用户自定义的文件的引用,编译器首先会去用户目录下查找,然后去安装目录,最后去系统目录查找。

注意: #include: 如果CLass A 导入了Class B, Class B的头文件里又导入了Class A的头文件, 这样会发生循环引用.所以我们尽量不要在.h文件中导入用户自定义的类文件.h (但是如果在.h文件中有代理的话可以在.h中直接导入另一个.h 或者可以把代理单独写一个.h文件)
而 #import 如果 CLass A 导入了Class B, Class B的头文件里又导入了Class A的头文件 , 这样会发生交叉导入.

import

  功能与include基本相同,不过它避免了重复引用的问题。所以在OC中我们基本用的都是import。

@class 一般用于在.h头文件中需要定义一个某个类型的属性的时候用到的, 防止在.h文件中导入另一个.h文件, 防止循环导入, 但如果在.h用@class 后一定要在.m中#import 对应的.h文件 注意:不要在.m中导入另一个类的.m文件

14、static inline 内联函数

防止反汇编,提高运行效率

函数之间调用,是内存地址之间的调用,当函数调用完毕之后还会返回原来函数执行的地址。函数调用有时间开销,内联函数就是为了解决这一问题。
不用inline修饰的函数, 汇编时会出现 call 指令.调用call指令就是就需要:
    (1)将下一条指令的所在地址入栈
    (2)并将子程序的起始地址送入PC(于是CPU的下一条指令就会转去执行子程序).

为什么inline能取代宏?

优点相比于函数:

    1) inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快.

    2)集成了宏的优点,使用时直接用代码替换(像宏一样);

优点相比于宏:

    1)避免了宏的缺点:需要预编译.因为inline内联函数也是函数,不需要预编译.

    2)编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。

    3)可以使用所在类的保护成员及私有成员。

inline内联函数的说明

1.内联函数只是我们向编译器提供的申请,编译器不一定采取inline形式调用函数.
2.内联函数不能承载大量的代码.如果内联函数的函数体过大,编译器会自动放弃内联.
3.内联函数内不允许使用循环语句或开关语句.
4.内联函数的定义须在调用之前.

例如:AF 里面的代码

static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
    return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
}

YYText 里面的代码

static inline CGSize YYTextClipCGSize(CGSize size) {
    if (size.width > YYTextContainerMaxSize.width) size.width = YYTextContainerMaxSize.width;
    if (size.height > YYTextContainerMaxSize.height) size.height = YYTextContainerMaxSize.height;
    return size;
}

static inline UIEdgeInsets UIEdgeInsetRotateVertical(UIEdgeInsets insets) {
    UIEdgeInsets one;
    one.top = insets.left;
    one.left = insets.bottom;
    one.bottom = insets.right;
    one.right = insets.top;
    return one;
}

你可能感兴趣的:(iOS -基础总结(持续更新))