OC特性

2020-12-15  本文已影响0人  Coder_JdHo

有哪些属性关键字

属性可以拥有的特质分为四类:
原子性---atomic、nonatomic (atomic:系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象,不会出现错乱数据。不过atomic可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能)

读/写权限---readwrite(读写)、readonly (只读)

内存管理---assign、strong、 weak、copy、unsafe_unretained (unsafe_unretained跟weak相似,但当对象销毁时,会依然指向之前的内存空间(造成野指针)。使用场景:当你明确对象的生命周期的时候,可以使用unsafe_unretained替代weak,可以稍微提高一些性能,虽然这点性能微乎其微。)
是否可为空 nonnull, nullable, null_resettable

属性默认的关键字是什么

对象: atomic,readwrite,strong
基本数据类型:atomic,readwrite,assign

weak的原理

系统会将weak的属性记录在一张hash表里,当该属性的引用计数为0的时候,则将nil值赋值给该属性。

__weak和__block

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

__block的作用是可以Block内部修改外边的变量的值。
__block是将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。它其实提升了变量的作用域。
__block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。

NSMutableArray能否用copy修饰

不能。NSMutableArray用copy属性修饰调用set方法后会变成NSArray。

原因: 不管是集合类对象(NSArray,NSDictionary,NSSet...),还是非集合类对象(NSString),接收到copy或者mutableCopy消息时,都需遵循以下准则:

copy关键字什么时候用

用途:
1. NSString、NSArray、NSDictionary 等经常使用copy关键字。
说明:
因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;(如果不用copy修饰时,当str(NSString) = mStr(NSMutableString),而mStr改变时,str会跟着改变)

2. block也经常使用 copy 关键字。
说明:
block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。

如何让自己的类用copy修饰符

  1. 需声明该类遵从 NSCopying 协议
  2. 实现 NSCopying 协议的方法:- (id)copyWithZone:(NSZone *)zone;

@synthesize和@dynamic

@synthesize是合成实例变量,它作用是为属性添加一个实例变量名,或者说别名。同时会为该属性生成 setter/getter 方法。
@dynamic 声明属性是用来告知系统不要自动生成setter和getter方法

非ARC下的retain、copy、和assign的setter方法

- (void)setRetainStr:(NSString *)retainStr
{
    if (_retainStr != retainStr) {
        [_retainStr release];
        _retainStr = [retainStr retain];
    }
}

- (void)setCopStr:(NSString *)copStr
{
    if (_copStr != copStr) {
        [_copStr release];
        _copStr = [copStr copy];
    }
}

- (void)setAssignStr:(NSString *)assignStr
{
    _assignStr = assignStr;
}

@property 的本质是什么

@property = 实例变量 + getter + setter

+load和+initialize

load和initialize的共同点
如果父类和子类都被调用,父类的调用一定在子类之前

+load方法要点
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要再调用[super load],否则父类的load函数会多次执行。

  1. 当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
  2. 类中的load方法执行顺序要优先于类别(Category)
  3. 当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
  4. 当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致

注意:
load调用时机比较早,当load调用时,其他类可能还没加载完成,运行环境不安全.
load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法.

+initialize方法要点
initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要显式的调用父类的initialize,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。

  1. 父类的initialize方法会比子类先执行
  2. 当子类不实现initialize方法,会把父类的实现继承过来调用一遍。在此之前,父类的方法会被优先调用一次
  3. 当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

注意:
在initialize方法收到调用时,运行环境基本健全。
initialize内部也使用了锁,所以是线程安全的。但同时要避免阻塞线程,不要再使用锁

使用场景:
load:如runtime交换两个方法的实现
initialize:如对一些static变量初始化(它不能在load中执行)

一般用分类好还是继承好

一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。分类还能精简原类的代码,使类结构更清晰,此外还能给系统的类添加分类

Category(类别)、 Extension(扩展)和继承的区别

  1. 分类是运行时决议,扩展是编译时决议
  2. 分类有名字,扩展没有名字
  3. 分类包含声明和实现,扩展只有实现
  4. 可为系统的类添加分类,但不能添加扩展
  5. 分类不可以添加成员变量,扩展可以
  6. 分类和扩展都能添加属性,但分类的属性要配合动态属性关联才能使用;(原因是分类没有属性列表,在分类中用@property声明属性,既无法生成成员变量也无法生成setter/getter)

OC逆向传值的几种方法

1.代理
2.block
3.通知
4.KVO
5.单例(如通过一个manager或数据单例类)

  1. extern (在上一个控制器中声明一个熟悉为外部已定义的属性)

通知的完整过程

a. app注册远程推送通知并向APNS请求device token
b. app接收到APNS返回的token
c. app发送token给服务器
d. 服务器将要推送的内容和对应token发送给APNS (另:如果是使用第三方推送如JPush,则是服务器调用JPush的API,JPush发送给APNS)
e. APNS发出推送到手机

1.应用程序注册APNs推送消息。
说明:
a.只有注册过的应用才有可能接收到消息,程序中通常通过UIApplication的registerUserNotificationSettings:方法注册,iOS8中通知注册的方法发生了改变,如果是iOS7及之前版本的iOS请参考其他代码。
b.注册之前有两个前提条件必须准备好:开发配置文件(provisioning profile,也就是.mobileprovision后缀的文件)的App ID不能使用通配ID必须使用指定APP ID并且生成配置文件中选择Push Notifications服务,一般的开发配置文件无法完成注册;应用程序的Bundle Identifier必须和生成配置文件使用的APP ID完全一致。

2.iOS从APNs接收device token,在应用程序获取device token。
说明:
a.在UIApplication的-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken代理方法中获取令牌,此方法发生在注册之后。
b.如果无法正确获得device token可以在UIApplication的-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error代理方法中查看详细错误信息,此方法发生在获取device token失败之后。
c.必须真机调试,模拟器无法获取device token。

3.iOS应用将device token发送给应用程序提供商,告诉服务器端当前设备允许接收消息。
说明:
a.device token的生成算法只有Apple掌握,为了确保算法发生变化后仍然能够正常接收服务器端发送的通知,每次应用程序启动都重新获得device token(注意:device token的获取不会造成性能问题,苹果官方已经做过优化)。
b.通常可以创建一个网络连接发送给应用程序提供商的服务器端, 在这个过程中最好将上一次获得的device token存储起来,避免重复发送,一旦发现device token发生了变化最好将原有的device token一块发送给服务器端,服务器端删除原有令牌存储新令牌避免服务器端发送无效消息。

4.应用程序提供商在服务器端根据前面发送过来的device token组织信息发送给APNs。
说明:
a.发送时指定device token和消息内容,并且完全按照苹果官方的消息格式组织消息内容,通常情况下可以借助其他第三方消息推送框架来完成。

5.APNs根据消息中的device token查找已注册的设备推送消息。
说明:
a.正常情况下可以根据device token将消息成功推送到客户端设备中,但是也不排除用户卸载程序的情况,此时推送消息失败,APNs会将这个错误消息通知服务器端以避免资源浪费(服务器端此时可以根据错误删除已经存储的device token,下次不再发送)。
上一篇下一篇

猜你喜欢

热点阅读