《Objective-C for Swift Developers》阅读笔记| 青训营笔记

207 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

总览

概念

  • xxx.h 和 xxx.m

    • xxx.h包含可以访问的「属性」和可以调用的「方法」
    • xxx.m用来实现这些方法
  • 命名空间的处理

    • 使用2-4个字母的前缀使得每个类名称独特

基础语法

  • 小的点

    • @表示接下来执行的代码是OC而不是C

    • @autoreleasepool 完成时自动释放内存

    • #import <Foundation/Foundation.h> 预处理指令,引入头文件,import保证了头文件只会被引入一次

      • 导入自己的头文件,用的是双引号
      • 导入系统的头文件,用的是尖括号
    • 常量指针

      const NSString *first = @"Hello";
      NSString const *first = @"Hello";
      ​
      NSString * const first = @"Hello"; // 常量指针
  • NSInteger

    • NSInteger和CGFloat会根据CPU的不同自动选择浮点数

    • 在绝大多数情况下面你要使用 NSInteger 和 CGFloat

    • // incorrectly try to use an NSInteger with %d

      • NSLog(@"%ld", (long)i);

数据类型

字符串

在OC中字符串是一个引用类型。NSString是一个Unicode(UTF-16)类型

  • 创建

    • NSString *foo = @"Hello, world!";
    • NSString *output = [NSString stringWithFormat:@"You picked %ld", (long)number];
    • NSString *output = [[NSString alloc] initWithFormat:@"You picked %ld", (long)number];
  • 常用方法

    • stringByReplacingOccurrencesofString: 替换字符串内容
    • stringByAppendingString: 合并字符串
    • stringByAppendingFormat: 合并带有合适的字符串
    • substringFromIndex: 截取字符串
    • componentsSeparatedByString: 拆分字符串,以数组的形式呈现
    • integerValue/doubleValue/floatValue: 把字符串转换成整数或浮点数
    • boolValue: 把字符串转换成布尔值,如果字符串以 "Y, y, T, t" 或数字 1~9 开头,则为 true。
  • 可变字符串

    • NSString是不可变的,NSMutable是可变的

    • 推荐把可变字符串放到不可变的容器里

    • 不推荐把不可变的字符串放到可变的容器里

    • 创建可变字符串

      • 第一种是创建一个现有的字符串的备份 NSString *hello = [@"Hello" mutableCopy];
      • 第二种是用 NSMutableString 的初始化器来创建 NSString *hello = [NSMutableString stringWithFormat:@"..."];

数字

NSNumber是一个对象,数据存储在这个对象里。它之所以会存在,是因为OC的数组和字典只能存储对象,不能直接放整数,也不能放浮点数和bool

所以要用NSNumber包一下

NSNumber *ten = [NSNumber numberWithInteger:10];
NSNumber *integerTen = @10;

数组

  • 创建

    • NSArray *villains = @[@"Weeping Angels", @"Cybermen", @"Daleks", @"Vashta Nerada"];

    • NSArray *villains = [NSArray arrayWithObjects:@"Weeping Angels", @"Cybermen", @"Daleks", @"Vashta Nerada", nil];

      • 结尾需要有 nil ,告知NSArray数组的结尾
  • 常用方法

    • count: 返回数组中元素的数量

    • indexOfObject: 返回元素所在位置

      • 如果访问不存在的对象,会返回一个很大的数字
    • objectAtIndex: 返回某一位置的元素

    • componentsJoinedByString: 把数组转换成字符串

  • 排序

    • NSArray *sorted = [villains sortUsingSelector:@selector(compare:)];

      • 这里的compare不能忘记:冒号
  • 高级用法

    • makeObjectsPerformSelector
    • enumerateObjectsUsingBlock
    • filteredArrayUsingPredicate

字典

在Swift中字典是有序的,而Object-C里面是无序的

  • 创建

    • @{…}
    • dictionaryWithObjectsAndKeys 先写值再写键
    NSDictionary *ships = [NSDictionary dictionaryWithObjectsAndKeys:
        @"Serenity", @"Firefly",
        @"Enterprise", @"Star Trek",
        @"Executor", @"Star Wars",
    nil ];
    // 其实是这样的。。。好反人类
    {
        Firefly = Serenity;
        "Star Trek" = Enterprise;
        "Star Wars" = Executor;
    }
    
    • 两种方法不要混着用
  • 其他方法

    • count
    • allKeys
    • allValues

集合

  • NSSet
  • NSMutableSet
  • NSCountedSet

OC中的泛型

先说结论:没有Swift中的好用,大多数OC中也不会用到。底层是「类型擦除」

NSValue 、NSData

整数可以用NSNumber包一下,CGRect、CGSize、CGPoint都是结构体而非对象,需要用NSValue包一下

NSData和Swift里一模一样

NSObject

每一个类都会继承自NSObject,一个包含了类和协议的实现文件,包括这些常用方法

  • copy
  • mutableCopy
  • isKindOfClass :参数为 [SomeClass class],判断某一对象是否是某一类型或这一类型的子类。
  • conformsToProtocol :如果一个对象遵从了某一协议,则返回 true
  • respondsToSelector :检查对象上能否运行某一方法
  • performSelector :在一个对象上运行某一方法

id 和 instancetype

id是一个数据类型,表示「任意OC对象」。它是一个指针,可以指向任意对象。

用instancetype去代替,返回当前类对应的实例

Block

就是Swift中的闭包。

// Swift 中
let universalGreeting = {
    print("Bah-weep-graaaaagnah wheep nini bong")
}
universalGreeting()
​
// OC 中
// ^printUniversalGreeting : 把block放到一个叫printUniversalGreeting的变量
// (void) 不需要参数
// ^{...}
void (^printUniversalGreeting)(void) = ^{
    NSLog(@"Bah-weep-graaaaagnah wheep nini bong");
};
printUniversalGreeting();
​
// 有参数的情况
// NSString* 返回一个字符串
// (NSString *) 必须要有NSString类型的参数
// =^ 把区块的返回值传给这个变量
// (NSString *name) 可选字符串参数
NSString* (^printUniversalGreeting)(NSString *) = ^(NSString *name) {
    return [NSString stringWithFormat:@"Live long and prosper, %@.",name];
};

还包括

  • 在blocks中获取值

    • NSInteger __block number = 0;
    • __block NSInteger number = 0;
  • 循环引用

    • swift里用weak 和 unowned解决,oc里用__weak

项目一

这个项目主要考虑了字符串的处理,需要OC与C一起使用

  • unichar

    • 在NSString中使用,表示字符串中的每一个字符
    • 不是一个对象,不能放到数组里面
  • c语言的字符数组转为OC的NSString

    char cstring[256];
    NSString *input = [NSString stringWithCString:cstring encoding:NSUTF8StringEncoding];
    
  • OC中得到字符转为NSString

    unichar letter = [input characterAtIndex:0];
    NSString * letterString = [NSString stringWithFormat:@"%C",letter];
    

// person.h@interface Person : NSObject
​
- (void)printGreeting;
​
@end// person.m
​
#import "Person.h"@implementation Person
​
// 没有括号
- (void)printGreeting {
    
    NSLog(@"Hello");
    
}
​
@end

提到了performSelector 的一个坑,选择器不会在意方法在哪里被声明,只要声明了就可以了。

方法

  • - (void)printGreetingTo:(NSString*)name atTimeOfDay:(NSString*)time;

    • atTimeOfDay 是参数time 的别名,可以不给第一个参数起名字,但后面的每一个参数都需要取一个名字

    • 如果用preformSelector

      • [person performSelector:**@selector**(printGreeting:atTimeOfDay:) withObject:@"hello" withObject:@"wo"]
  • 多返回值可以用返回字典实现

  • - 是实例方法 + 是类方法

    • 如何调用类方法

      • + (instancetype)personWithName:(NSString*)name { return [[self alloc] initWithName:name]; }

属性

  • 实例变量

    • 在interface最后加上括号
    • @public 表示的是全局变量,不加的话意味着只能在类的内部被访问到
    • 访问实例变量用→
    @interface Person : NSObject {
        @public
        NSString *name;
    }
    ​
    - (void)printGreeting;
    @end
    
  • property 属性(属性和实例变量的区分)

    • 用property后创建实例变量就不用花括号了

    • 默认增加了get和set方法

      • 用@property声明name,xcode自动创建name和setName,还创建了_name

      • _name是自动生成的,也可以改名字

        • @synthesize name = userName; 要再.m文件中
    • 可以用.符号去拿到属性值

      @interface Person : NSObject
      @property NSString *name;
      - (void)printGreeting;
      @endPerson *person = [Person new];
      person.name = @"Taylor"; // 方法一
      [person setName:@"Taylor"]; // 等价方法一- (void)printGreeting {
          NSLog(@"Hello, %@!", self.name); // 方法二
          NSLog(@"Hello, %@!", [self name]); // 方法三
          NSLog(@"Hello, %@!", _name); // 方法四
      }
      
    • 除了在自定义get和set里面要直接用实例变量,其他地方尽量都用属性

  • 私有属性

    • 使用类扩展,实现一个类的第二个接口,第二个接口写在xxx.m文件里
    • 只能写自己类的类扩展,不能写系统类的扩展
    @interface Person()
    @property NSString *name;
    @end@implementation Person
    - (void)printGreeting{
    ​
    }
    @end
    
  • 属性修饰符

    • 种类

      • strong: 属性的默认特征,也就是强引用,表示「储存在内存里」。
      • weak: 弱引用,用于避免循环引用。
      • copy: 拷贝,当属性有对象的时候,自动复制。
      • assign: 用来修饰基本数据类型,确保把值给了实例变量。
      • nonatomic: 原子性(atomic)属性,是指在多线程运行的时候,某一线程访问存或者取方法,其他线程不可以进入该存、取方法。非原子性(non-atomic)则相反,你要保证没有同时存取。
      • retain: strong 的旧格式,如果你看到了这个修饰符,说明真的接手了一个超级老的项目。
      • readonly: 只读,不能使用 setter 访问器。
      • readwrite: 读写,所有属性的默认特征,可以使用 getter 和 setter 访问器。
      • atomic: 看上面的 nonatomic,atomic 让代码更安全,也是所有属性的默认特征,会降低性能。
      • getter=: 改变 getter 访问器的名称。
      • setter=: 改变 setter 访问器的名称。
    • 默认修饰符

      • @property (strong, atomic, readwrite) NSString *name;
  • 属性调整

    在OC中无法同时完成对属性的读写,要分开来写

    CGRect frame = button.frame;
    button.frame = CGRectOffset(frame, 0, 50);
    

创建对象

  • 特点

    • 在 Objective-C 里面你不需要给属性设定默认值, Objective-C 会自动把对象设定为 nil,数字设定为 0。
    • 在你初始化自己的属性之前,你必须要调用父类的 init 方法。
    • 在你初始化完你的你自己的属性之前,你可以在你的初始化器里调用其他的方法。
    • 所有的初始化器都默认是失败的,「失败」指的是它们会返回 nil,在继续之前下一次的操作之前,你必须要先去确认初始化是否成功。
    • 你需要用 self 来返回。
// 为什么要这么初始化
- (instancetype)initWithName:(NSString*)name {
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}

Categories 种类

种类是类扩展的名称的集合

协议

空特性

  • Xcode的Assistant → Counterparts 可以将oc转为swift

  • nullable和nonnull

  • NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 来保证元素非空

    • 这样就不用导出写nonnull了
    • 可以额外设置 - (nullable instancetype)initWithName:(NSString*)name;
  • null_resettable 用于设置默认值