剑客
关注科技互联网

iOS开发几年了,你清楚OC中的这些东西么

前言

几年前笔者是使用Objective-C进行iOS开发, 不过在两年前Apple发布swift的时候,就开始了swift的学习, 在swift1.2发布后就正式并且一直都使用了swift进行iOS的开发了, 之后就是对swift持续不断的学习, 近来swift3.0的发布, 更多的人会选择swift来进行iOS的开发看上去更是成为了一种趋势, 不过一个合格的iOS开发者对oc以及c语言的掌握是必不可少的技能, 本篇中主要是写一些大家平时都可能用到但是不一定知道的oc的东西

  1. oc中的对象的创建: 首先会通过 +(id)alloc 动态的分配所有的变量以及父类定义的变量所需要的足够内存, 同时会清除所有的分配的内存空间, 全部置为0

  2. 同时接着需要调用class的 -(id)init 方法, 这个方法给每个变量设置初始值

  3. 返回的类型为id, id是一个可以指向任意类型的指针(不用 * 号), 这个在一定程度上可以完成 多态 的效果

  4. 对oc中的class文件的理解: class, extension, category

ZJPerson.h文件

iOS开发几年了,你清楚OC中的这些东西么

ZJPerson.m文件

iOS开发几年了,你清楚OC中的这些东西么

ZJPerson.m文件

iOS开发几年了,你清楚OC中的这些东西么

m

5.   [[XXObject alloc] init] 初始化方法不需要参数的时候, 和 [XXObject new] 方法相同

6.   通过字面量来初始化对象, 例如

  NSString *string = @"string"; == [[NSString alloc] initWithString:@"string"];等初始化方法    NSNumber *myBOOL = @YES; == [[NSNumber alloc] initWithBool:YES];  NSNumber *myFloat = @3.14f; ==  NSNumber *myInt = @42; ==  NSNumber *myLong = @42L; ==...

7.  oc(c)中多行宏的定义(这个在swift…中更方便直接一个全局的函数就搞定了): 在除了最后一行的每一行结尾加一条反斜杠 /

iOS开发几年了,你清楚OC中的这些东西么

iOS开发几年了,你清楚OC中的这些东西么

8.  比较是否相同: 使用 if(a==b) {}, 如果a,b是对象类型, 那么比较的是指针是否相同, 而不是比较值是否相同, 如果a, b是基本类型(int, double…), 那么比较的是值是否相同; 使用if ([a isEqual: b]) { }, 则比较的是a,b的值是否相同

9.  初始化基本类型的时候尽量设置初始值, 因为编译器分配的初始值并不确定, 但是对象类型会默认初始化为nil

10.  条件判断: 当对象不为nil(有内存地址)的时候, 或者基本类型非0, 或者bool类型为true, 这个时候条件都为真, 其他情况条件为假

11.  oc中属性的getter和setter

@property (nonatomic) NSString *name;

  • 例如当有这样一个name属性的时候, 默认是readWrite的, 编译器会自动生成一个set (setName:)和get(-(NSString *)name)方法, 这个时候可以通过set或者get方法访问到name, 如果申明为(readonly), 那么将只会生成get方法

    [self setName:@"set name"];
    NSString *getName = [self name];
    也可以通过点语法访问(实际上是会自动调用set和get方法)
    self.name = @"set name";
    NSString *dotName = self.name;
  • 同时你可以重写name的get(懒加载…)和setter(拦截set方法)…

    对应name属性, 编译器会生成(synthesize)一个 _name 允许我们直接通过指针访问变量, 而不会调用get方法, 所以通过_xx访问的变量不会调用懒加载(get方法), 所以在写懒加载方法的时候, 不能使用self.xx(造成死循环), 而要使用_xx

    - (NSString *)name
    {
    // 这里面不能使用self.name , 因为点语法会调用这个get方法, 造成死循环
    if (_name == nil) 
    {
        _name = @"name";
    }
        return _name;
    }
  • 同时这个synthesize的名字我们是可以自己修改的, 使用如下的语法

    @synthesize name = customName;

  • 那么这个时候就不能通过   __name访问到name了, 因为我们已经指定了通过customName才能访问到了

    NSString *getName = customName;

  • 当然如果, 你是这样写的 @synthesize name; , 并没有指定名字, 这个时候访问的时候就直接使用变量名而不需要加下划线( _ )了 name = @"set name"; ????这个时候就比较爽了, 和swift,java这些一样, 不需要self,this了;

12.  oc的属性默认是atomic(原子的), 也就是说是线程安全的, 这个时候是不允许重写set和get方法的, 因为内部的setter和getter会做出处理, 保证线程安全, 但是我们经常使用的是noatomic, 因为访问的速度比较快, 并且可以自己重写getter和setter

13.  oc中的对象是动态管理(内存)的, 是分配在heap(堆)上所以需要一个指针来指向它(才能访问), 所以对象类型需要用 星号 NSString * str ;

14.  oc中的对象管理在ARC下是用引用计数来管理的, 当有一个强引用对象A指向这个对象B的时候, B引用计数加一, 当这个对象A销毁的时候,B的引用计数减一, 直到B的引用计数为0的时候就被自动销毁, 当然这个时候如果A强引用B, B同时强引用A就造成了循环引用, 两者都不会被销毁, 就造成了内存泄漏, 解决方法是将一方标记为 weak 或者unsafe_unretained(垂悬指针, 和swift中的[unowned self]类似, 所以运用不当会造成野指针的问题)

15.  oc中的属性默认是strong的, 所以需要显示的指定为其他的(weak, unsafe_unretained…)

16. NSObject * __weak someObject = [[NSObject alloc] init]; , 这个someObject没有对象强引用他, 所以这行代码之后会立马被置为nil, NSObject * __weak someObject = self.someObject , 这个someObject在这行代码之后不会立刻被置为nil, 而是会在所在的代码块结束后被置为nil

17. 对于属性的赋值(深浅拷贝)

@property (nonatomic) NSString *name;
  NSMutableString *str = [NSMutableString stringWithString:@"初始"];
  ViewController *ob = [ViewController new];
  ob.name = str;  ---- 浅拷贝
  NSLog(@"%@", ob.name); --- 初始
  [str appendString:@"+1"];
  NSLog(@"%@", ob.name); --- 初始+1
  这里出现ob.name改变的原因就是: 属性name是strong(默认)类型的,ob.name = str; 这行代码赋值后, 实际上只是name强指向了str, 所以当str的内容改变的时候, ob.name也改变了
  NSLog(@"%@", ob.name); --- 初始
  str = [NSMutableString stringWithString:@"改变"];
  NSLog(@"%@", ob.name); --- 初始
  但是这样的赋值, 直接改变str之后并不会影响原来的str的指针指向的内容, 所以ob.name仍然指向原来的str, 因此内容并未改变
  如果将上面的  ob.name = str; 改为 ob.name = [str mutableCopy]; 那么将上面的两种操作都不会影响ob.name ---- 深拷贝
  如果name被修饰为copy
  @property (nonatomic, copy) NSString *name;
  那么上面的操作都不会改变ob.name的内容 ---- 深拷贝

18. 分类(category)定义的函数和属性在运行时中和原生的class中定义的东西并没有区别 At runtime, there’s no difference between a method added by a category and one that is implemented by the original class

19. 不过分类中定义的属性, 编译器并不会自动生成getter和setter, 以及_XX变量来访问,需要自己提供getter和setter, 并且需要使用运行时才能绑定这个属性到这个类中, 实现原生类中定义的属性的效果

///例如可能是这样的使用
static const void *propertyKey = &propertyKey;
/// 将value通过运行时绑定到self
objc_setAssociatedObject(self, propertyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/// 将value在运行时中通过propertyKey取出绑定的值
id value = objc_getAssociatedObject(self, propertyKey);

20. 同时分类也可以用来将一个复杂的类中的代码分块(swift的extension可以有相似的作用), 使得代码组织更好, 例如可以将tableView的delegate, 和Datasource在分类中实现,

@implementation ViewController(tableview)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
...
@end

21. 但是在使用category来扩展Cocoa的原生类的时候, 要注意函数的命名如果是和原生已有的函数名相同,那么将会发生不可预料的结果(不能确定哪一个方法在运行时会被调用), 因此建议在自己的函数名前面加上前缀, 就像重写 +load() 来实现各种 黑魔法 的时候也是可能会发生不可预料的结果, 因为同一个项目中可能有多个地方重写了这个类的 +load方法

22. 初始化NSArray的时候, 如果通过 NSArray *arr1 = @[object1, object2]; , 不需要以nil结尾, 如果通过构造方法初始化, 则需要传入nil结尾, 同时, 如果中间的对象有nil,  那么将在中间nil就结束了, NSArray *arr2 = [NSArray arrayWithObjects:object1, object2, object3, nil, object4, object5, nil] 这个arr2只可能会存储第一个nil前的对象

23. 如果在数组中一定要存储 nil , 那么只能用 NSNull 来代替

24. 如果NSArray中存储的是NSArray, NSDictionary, NSString, NSData, NSDate , NSNumber这些类型的对象, 那就可以直接写入disk并且读取disk的数据做持久化数据操作 [array writeToURL:fileURL atomically:YES] , 但是如果是有其他的类型, 就需要使用归档来实现了

25. 在for-in快速枚举中, 不能够修改(增删)被枚举的对象(数组, 字典,集合)

26. 在写代码的时候, 进行条件判断的时候, 经常会出现这样的代码 if (a = 1){...} , 这样写编译器是会报错的, 需要写成 if(a == 1) {...} , 当然你非要使用一个等号也是可以的, 需要额外加一个括号,   if ((a = 1)) {...}

27. 实际上绝大多数情况下都是我们写条件判断的时候都是使用 == , 而非 = , 也就只有当我们写构造方法的时候才可能会写到 = ,像这样   if (self = [super init]) {...} , 其实这并不是使用 = 来判断条件相等是正确的, 只是在这里, 通过 [super init] 方法会返回一个id对象, 通过, self = [super init] , 把这个对象赋值给self, 这个时候的 if就是用来判断, 被赋值后的self是否为nil, 而不是 self是否等于[super init]返回的对象.

28. 在oc中block是object类型的, 所以是可以存储在NSArray…中, 同时在调用block的时候, 如果block为nil(未赋值), 那么程序将crash.

29. oc中block可以捕获变量, 什么意思呢 — 就是block会默认捕获到变量的值, 在之后不受到原来变量的改变的影响, 例如

int anInteger = 42;

  void (^testBlock)(void) = ^{
      NSLog(@"Integer is: %i", anInteger);
  };

  anInteger = 84;

  testBlock(); ---- 输出的值仍然为 42

30. 第二种block捕获变量的方式, 是捕获变量的指针, 被捕获的变量值改变, 则block中的变量值也改变了,不过需要对变量进行 __block 标记, 例如上面的代码, 只改变一点, 结果就变了

 __block int anInteger = 42;

  void (^testBlock)(void) = ^{
      NSLog(@"Integer is: %i", anInteger);
  };

  anInteger = 84;

  testBlock(); --- 输出值这时是 84

31. 伴随着block能够捕获变量的能力的一个问题就是, 循环引用, 在ARC中, 只要不是用到纯C语言的库,管理内存的工作都不需要我们完成, 但是循环引用却是我们需要解决的, 最常见的就是当block捕获的变量是一个对象的属性(方法)的时候, 也就是会捕获到self, 那么这个时候就可能会造成循环引用(block属性应该被标记为copy), 解决方法也很简单, 使用一个对self弱引用的指针即可, 这个写法就很多了, 笔者习惯的写法是: __weak typeof(self) weakSelf = self; , 那么在block中使用 weakSelf 替代 self 调用相关的属性或者方法, 就不会造成循环引用

32. 使用weakSelf能够解决block捕获self造成的循环引用的内存泄漏问题, 但是带来的另一个问题就是, 特别是在多线程中,可能在block中代码 正在执行 的时候, self被销毁了, 因为使用weakSelf捕获到的是self的弱引用, 那么后续的代码就不能够继续执行了, 这个时候为了保证在这个block中self即使被销毁block里面的代码也能正常执行, 我们需要的另一个操作就是, 将weakSelf强引用一次, 让他的引用计数加1, 就能处理这个问题, 就是Apple在wwdc中提到的 weak-strong-dance , 笔者习惯的书写方式是: __strong typeof(self) strongSelf = weakSelf; ,,, 当然这个必须要明白的是, 这个block里面的strongSelf能够保证里面代码执行完毕的前提是程序能够执行到block, 如果在执行block之前self已经被销毁了, 那么这个block肯定是不会被调用的(block的引用计数已经为0).

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址