进阶内容

引入方式

OC中有四种引入方式,#include#import@import@class

#include来自C语言,用于包含一个头文件。

#import 大部分功能和 #include 一样,但是它解决了#include重复定义的问题

@import 是 iOS 7 之后增加的语法。这种方式叫模块导入,当前仅适用于内置系统框架的引入。该特性在Xcode5 以后默认开启,无需显式去写。当编译器看到模块导入时,它将加载框架的独立预编译版本。它还会自动处理与模块的链接,这意味着不再需要在Xcode中手动添加框架。

@class主要是用于声明一个类,告诉编译器它后面的名字是一个类的名字。仅仅只是声明一个类,所以在后面的实现文件里面是需要去 #import这个类的。一般来说,在 interface 中用@class引用一个类,它会把这个类作为一个类型来使用,而在实现文件中,还是需要 #import 。使用它的好处是提升编译效率,同时还能解决循环导入的问题。

枚举

OC基于C语言,所以它也支持C的枚举类型。

enum Color{
    Blue,
    Red,
    Green
}

如上,编译器会用一个整数来保存枚举的值,Blue是0,Red是1,以此类推。至于这个整数的具体类型,是由编译器决定的。以上的枚举值较少,编译器会用一个char来表示。

这样直接定义枚举类型,声明变量的时候会比较麻烦:

enum Color color = Blue;

因此通常借助typedef关键字定义枚举

// 可以在定义完枚举之后加一行
typedef enum Color Color;

//也可以直接在定义枚举时加上
typedef enum Color{
    Blue,
    Red,
    Green
} Color;

C语言的枚举存在一个问题,就是不能显式的定义枚举值的底层类型。得益于C++11的特性,OC有一种新的语法来在定义枚举时显式指定枚举值的底层类型。

enum Color : NSInteger{
    Blue,
    Red,
    Green
};

此外,OC还提供了两个宏帮助我们更简便的定义枚举

typedef NS_ENUM(NSInteger, MyColor){
    Blue,
    Red,
    Green
};

typedef NS_OPTIONS(NSInteger, Color){
    Blue = 1 << 0,
    Red = 1 << 1,
    Green = 1 << 2
};

Block

Block用来保存某一段代码, 可以在需要的时候取出来调用。它其实就相当于匿名函数或者闭包。

其基本格式如下:

返回值类型 (^block变量名)(形参列表) = ^(形参列表) {
    代码
};

简单示例:

// 无参无返回值
void (^myBlock)() = ^{ 
    NSLog(@"Hello"); 
};

// 调用
myBlock();

// 有参有返回值
int (^myAdd)(int, int) = ^(int num1, int num2){ 
    return num1 + num2; 
};
NSLog(@"%i", myAdd(10,8));

结合typedef一起使用

// 利用 typedef 给 block 起别名
typedef int (^calculteBlock)(int , int);

int main(int argc, const char * argv[]) {
    calculateBlock sumBlock = ^(int v1, int v2){
        return v1 + v2;
    };

    int res = sumBlock(10, 20);
    NSLog(@"res = %i", res);
    return 0;
}

Block通常会使用外部的其他变量,当执行Block时,为了确保其使用的外部变量始终存在,Block会捕获这些变量。对于基本类型,捕获意味着拷贝变量值到Block内部的局部变量中保存。但是对于指针类型外部变量,Block则会使用强引用。这意味着Block对象用到的那些对象会被一直保留,直到Block被释放,其引用的外部变量引用计数才会减1。

小结:

  • 在Block中可以访问外部的变量,但是默认情况下,不能在Block中修改外部的局部变量值(非引用类型)

  • 想在Block中修改外部局部变量的值,必须在外部局部变量前面加上__block修饰。例如__block int a = 10;

  • 在类中使用时,为了防止循环引用,应使用弱指针

    __weak typeof(self) weakSelf = self;
    
    self.myBlock = ^(void){
        [weakSelf doSomething];
    };
    

异常处理

OC 对异常处理提供四个编译器指令:@try@catch@throw 以及@finally

OC 虽然像其他语言一样提供了try...catch...捕获异常机制,但通常并不使用。Apple认为,异常属于代码错误,应该在编码阶段就解决,而不是在运行阶段处理异常。当需要抛出异常时,往往是直接让程序crash。

@try {  
     // 正常代码...
}@catch (NSException *exception) {  
     // 捕获异常...
}@finally {  
     // 最终执行...
}

属性修饰符

OC有3类属性修饰符:多线程相关读写相关内存管理相关

@property (nonatomic, readwrite, strong) NSString *name;

多线程相关修饰符:

  • nonatomic 非原子的
  • atomic原子的。默认类型

读写相关修饰符:

  • readwrite 可读写。默认类型
  • readonly 只读

readwrite意味着系统实现了settergetter方法。readonly意味着只实现getter方法

内存管理相关修饰符:

  • strong 强引用
  • weak 弱引用(避免循环强引用出现内存泄露问题)
  • copy 复制

对象间通信

  • 基于协议的委托
  • Block
  • 通知中心
  • Target-action

通知中心

每一个应用程序都有一个通知中心(NSNotificationCenter)实例,专门负责协助不同对象之间的消息通信。任何一个对象都可以向通知中心发布通知(NSNotification),其他感兴趣的对象(Observer)可以申请在某个特定通知发布时(或在某个特定的对象发布时)收到这个通知。

- (void)myMethod:(NSNotification *)notification {
    NSLog(@"userInfo =%@",notification.userInfo);
}
- (void)dealloc {
    //移除指定的通知
    // [[NSNotificationCenter defaultCenter] removeObserver:self name:@"test" object:self];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

//注册通知:
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(myMethod:) name:@"test" object:nil];

//发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil userInfo:@{@"key":@"value"}];

一个完整的通知通常包含3个属性:

  • (NSString *)name 通知的名称
  • (id)object 通知发布者
  • (NSDictionary *)userInfo 一些需要传递的参数(发布者传递给接收者)

注册通知监听器

//observer:监听器,即接收者
//aSelector:回调方法,方法参数必须包含通知对象
//aName:通知的名称。如果为nil,忽略名称,监听器能收到任意名称的通知
//anObject:发布者。如果为anObject和aName都为nil,监听器收到所有的通知
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;

//name:通知名称
//obj:发布者
//queue:决定了block在哪个操作队列中执行,如果传nil,默认在当前操作队列中同步执行
//block:收到通知时,回调此block
- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;

注销通知监听器

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;

发通知

//发布一个notification通知
- (void)postNotification:(NSNotification *)notification;

//发布一个名称为aName的通知,anObject为此通知的发布者
- (void)postNotificationName:(NSString *)aName object:(id)anObject;

//发布一个名称为aName的通知,aUserInfo为需要传递的参数信息
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;

初始化NSNotification对象的方法:

+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject;
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;

Target-action

每隔两秒回调对象方法

#import "TSLogger.h"

@implementation TSLogger

-(void)update:(NSTimer *)t{
  NSLog(@"update...");
}

@end
#import <Foundation/Foundation.h>
#import "TSLogger.h"

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    TSLogger *logger=[[TSLogger alloc]init];

    __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(update:) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop]run];
  }
  return 0;
}

Runtime

即运行时。OC就是一种动态编程语言,在运行时有一些机制,其中主要就是消息机制。OC的函数属于动态调用,在编译的时候并不能决定真正调用哪个函数,只有在运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。

Runtime实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。

反射

方法调用的本质,就是让对象发送消息,对象根据方法编号SEL去映射表查找对应的方法实现。因此我们可以通过运行时机制去动态调用方法,这就类似于Java语言中的反射。可用于私有方法的调用。

@interface Person : NSObject

- (void)test:(NSInteger)param;
- (void)run;

@end

每个类都有一个对应的Class,OC有三种方式获取Class对象:

  1. 通过Class clazz = NSClassFromString(@"Person")
  2. Class clazz = [Person class]
  3. 通过调用对象的class方法获取,Class clazz = [p class]

有了Class对象,就通过调用它的allocinit方法来实例化该类。

Class clazz = [Person class]
id p = [[clazz alloc]init];

// 注意完整的方法签名,有冒号的要包含冒号
SEL s1 = @selector(test:);
// 判断实例是否实现此方法
if([p respondsToSelector:@selector(s1)]){
    // 调用方法
    [p performSelector:s1 withObject:@1];
}

这里是一些相关的API:

// SEL和字符串的转换
NSString *NSStringFromSelector(SEL aSelector);
SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串的转换
NSString *NSStringFromClass(Class aClass);
Class NSClassFromString(NSString *aClassName);
// Protocol和字符串的转换
NSString *NSStringFromProtocol(Protocol *proto);
Protocol *NSProtocolFromString(NSString *namestr);

/// 常用判断方法

// 当前对象是否这个类或其子类的实例
- (BOOL)isKindOfClass:(Class)aClass;
// 当前对象是否是这个类的实例
- (BOOL)isMemberOfClass:(Class)aClass;
// 当前对象是否遵守指定的协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 当前对象是否实现此方法
- (BOOL)respondsToSelector:(SEL)aSelector;

使用Runtime机制

// 注意导入头文件
#import <objc/message.h>

// 创建person对象
// Person *p = [[Person alloc] init];
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
p = objc_msgSend(p, sel_registerName("init"));

// 调用对象方法,本质就是让对象发送消息
objc_msgSend(p, @selector(test:),20);
objc_msgSend(p, @selector(run));

// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 本质让类对象发送消息
objc_msgSend([Person class], @selector(run));

方法调用流程:

  1. 通过isa去对应的类中查找
  2. 注册方法编号
  3. 根据方法编号去查找对应方法
  4. 找到是最终函数地址,根据地址去方法区调用对应函数

注意,通过Runtime机制,还可以动态的给类添加方法和属性。


公众号“编程之路从0到1”

20190301102949549

Copyright © Arcticfox 2021 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:20:20

results matching ""

    No results matching ""