0%

通过一个简单的打印实验就可以发现:

  • +initialize方法和+load调用时机
  • +initialize在父类,子类,类别之间的关系
  • +load在父类,子类,类别之间的关系

如下表格:

+load +initialize
调用时机 被添加到 runtime 时(类被加载时) 第一次调用该类的类方法或实例方法前调用,可能永远不调用
调用顺序 父类->子类->分类(多个分类间,则按加载顺序) 父类->分类(多个分类时,是最后一个的被执行)or子类
是否需要显式调用父类实现
自身没实现是否调用父类的实现
分类中实现产生的影响 类和分类都执行 覆盖类中的方法,只执行分类的实现

另:Swizzling should always be done in +load.

initialize

文档说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. Superclasses receive this message before their subclasses.

The runtime sends the initialize message to classes in a thread-safe manner. That is, initialize is run by the first thread to send a message to a class, and any other thread that tries to send a message to that class will block until initialize completes.

The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementationor if subclasses explicitly call [super initialize]. If you want to protect yourself from being run multiple times, you can structure your implementation along these lines:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}

Because initialize is called in a blocking manner, it’s important to limit method implementations to the minimum amount of work necessary possible. Specifically, any code that takes locks that might be required by other classes in their initialize methods is liable to lead to deadlocks. Therefore, you should not rely on initialize for complex initialization, and should instead limit it to straightforward, class local initialization.

Special Considerations
initialize is invoked only once per class. If you want to perform independent initialization for the class and for categories of the class, you should implement load methods.

有几个点值得说一下:

1、initialize方法是线程安全的。当第一个线程触发initialize执行后,其他线程发送的消息都会被阻塞直到initialize执行完成。

阅读全文 »

使用SDWebImage加载图片,当列表有很多页,图片很多时,可能会导致内存暴涨:
内存暴涨

产生内存暴涨的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//从磁盘获取图片,并缓存到内存.
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}

return diskImage;
}

//解压图片
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.config.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
return image;
} else {
return nil;
}
}

关闭缓存图片到内存的功能:
[[SDImageCache sharedImageCache].config setShouldCacheImagesInMemory:NO];

效果如下:
内存正常
此时虽然内存不再异常,但滑动过程中会明显看到图片填充时的闪动,用户体验想当不好.

比较好的办法:

1
2
3
4
5
6
- (void)initSDWebImage
{
[[SDImageCache sharedImageCache].config setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
[[SDImageCache sharedImageCache] setMaxMemoryCost:20 * 1024 * 1024];
}

设置一个内存缓存最大值,需要注意的是系统并不会精确的使用该值进行限定.这样设置之后,内存基本在70-90M之间浮动.不会再像之前飙到3-400M还停不下来.
如果设置为10 1024 1024,那么内存基本在30-50M之间.并且用户体验还行.

使用reduce模拟实现map和filter

首先看一下系统的map和filter功能:

1
2
3
4
5
6
7
8
9
10
11
let fbArr = [1, 1, 2, 3, 5, 8, 13, 21]

let mmp = fbArr.map { (item) -> Int in
item + item
}
print(mmp)

let cmp = fbArr.filter { (item) -> Bool in
item > 5
}
print(cmp)

打印如下:

1
2
[2, 2, 4, 6, 10, 16, 26, 42]
[8, 13, 21]

使用reduce模拟实现map和filter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extension Array {
func map2<T>(_ transform: (Element) -> T) -> [T] {
//初始值为空数组, 数组+数组
return reduce([], { (result, item) -> [T] in
return result + [transform(item)]
})
//简化一下
return reduce([], {
$0 + [transform($1)]
})
//再简化一下
return reduce([]) { $0 + [transform($1)] }
}

func filter2(_ filter: (Element) -> Bool) -> [Element] {
return reduce([], { (result, item) -> [Element] in
return filter(item) ? result + [item] : result
})
//简化一下
return reduce([]) { filter($1) ? $0 + [$1] : $0 }
}
}

说实话,Swift对闭包的简写有时候感觉有点过了,初次看到这样的写法reduce([]) { filter($1) ? $0 + [$1] : $0 },有时还得缓缓才知道是啥意思.

使用:

1
2
3
4
5
6
7
8
9
let ammp = fbArr.map2 { (item) -> Int in
item + item
}
print(ammp)

let bm = fbArr.filter2 { (item) -> Bool in
item > 10
}
print(bm)
阅读全文 »

category用结构体category_t表示(在objc-runtime-new.h中可以找到此定义):

1
2
3
4
5
6
7
8
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};

而类的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

typedef struct objc_class *Class;

从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。

MyClass.h

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface MyClass : NSObject

- (void)printName;

@end

@interface MyClass(MyAddition)

@property(nonatomic, copy) NSString *name;

- (void)printName;

@end

MyClass.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation MyClass

- (void)printName
{
NSLog(@"%@", @"MyClass");
}

@end

@implementation MyClass(MyAddition)

- (void)printName
{
NSLog(@"%@", @"MyAddition");
}

@end

转换为.cpp文件:clang -rewrite-objc MyClass.m

阅读全文 »

响应者对象

响应者对象(Responder Object)指的是有响应和处理事件能力的对象。A responder object is any instance of the UIResponder class, and common subclasses include UIView, UIViewController, and UIApplication. UIResponder是所有响应者对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的AppDelegate、UIApplication、 UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。

响应者链

响应者链就是由一系列的响应者对象构成的一个层次结构。UIResponder对象能够接收触摸事件(其子类当然也能够)。每一个UIResponder对象都有一个指针nextResponder,然后这些链接在一起的对象就组成了响应者链。

系统是如何找到第一响应者的

当用户点击了某个视图.系统是如何找到用户点击的视图呢?

答案就是对视图进行hitTest.

官方文档:

UIKit uses view-based hit testing to determine where touch events occur. Specifically, UIKit compares the touch location to the bounds of view objects in the view hierarchy. The hitTest:withEvent: method of UIView walks the view hierarchy, looking for the deepest subview that contains the specified touch. That view becomes the first responder for the touch event.

注意:If a touch location is outside of a view’s bounds, the hitTest:withEvent: method ignores that view and all of its subviews. As a result, when a view’s clipsToBounds property is NO, subviews outside of that view’s bounds are not returned even if they happen to contain the touch.

阅读全文 »

将要开始发生拖拽.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView

将要结束拖拽,velocity:结束时的速度;targetContentOffset:想要滚动到的指定位置.(使用该方法可以让UIScrollView停在我们想要的地方)
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset

拖拽结束,decelerate=YES:表明拖拽结束的时候,scrollView还有速度,将会减速滑动一段距离,最终停止时会回调scrollViewDidEndDecelerating方法;=NO:表明拖拽结束,scrollView也随即停止,此时不会再回调scrollViewDidEndDecelerating方法.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate

已经结束减速.表明滚动停止.只有在停止前有速度的时候才会被回调.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

已经滚动.scrollView处于滚动状态时,该方法会被频繁调用,因此该方法里面不应该有太复杂的处理.动画scrollToItemAtIndexPath:atScrollPosition:animated:;并不会使该代理方法调用.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView

called when setContentOffset/scrollRectVisible:animated: finishes. not called if not animating. 因为动画原因滚动结束.
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView

Block的声明、赋值与调用

Block变量的声明

和C语言的函数指针声明几乎一样,只是将”*”改为”^”.如下:

返回值类型 (^变量名称) (变量类型 变量1, 变量类型 变量2, ...)

例:int (^blkVar)(int a, int b);

Block语法

一个Block语法代表一个Block类型变量的值.它的写法和C语言函数的定义很像.仅有两点区别:

  • 没有函数名.
  • 在返回值前面加上”^”.

^ 返回值 (变量类型 变量1, 变量类型 变量2, ...) {... return xx;}.

例:^int (int a, int b){… return 3;}.

阅读全文 »

Block里的strongSelf

有时我们在看源码时,会发现作者会在block里第一行strong一下weakSelf.以下便是对这种写法的一个探究.

一般在使用block的时候,都会在block外,声明一个weakSelf,来确保block不去持有self.但是这也会引发一个问题,就是如果在block执行之前,self就已经销毁了,那么block里的[weakSelf method],method方法就不会被执行,如果有返回值的话,会返回nil.如果Block里有这样的代码:[arrM addObject:nil],是会崩溃的.所以,如何让block在执行时,self不销毁?

解决办法就是:在block代码块里第一行将weakSelf赋值给一个strong类型指针变量,通常是__strong typeof(weakSelf) strongSelf = weakSelf;.有了strongSelf这样一个强引用指针,以后的代码执行时,便不会出现self==nil的情况,且当block执行完后,局部变量strongSelf清除,self对象被释放.一切看起来似乎很完美.但是如果在
__strong typeof(weakSelf) strongSelf = weakSelf;
执行之前,self就已经销毁,那么strongSelf将等于nil.上面的崩溃还是会发生.所以必须判断一下strongSelf是否为nil.

综上完整的写法应该如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__weak typeof(self) weakSelf = self;
void (^success)(id data, NSString *statusInfo) = ^(NSArray *data, NSString *statusInfo) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf == nil) return;

NSLog(@"block回调了,strongSelf:%@", strongSelf);
NSString *sd = [strongSelf printSomething]; // 如果没有前面的判断,这里有可能会因为strongSelf==nil,而导致返回值为nil.从而后面的[mA addObject:sd];会崩溃.

NSMutableArray *mA = [[NSMutableArray alloc] initWithArray:data];
[mA addObject:@"dsfds"];
[mA addObject:sd];
NSLog(@"%@", mA);
NSLog(@"%@", statusInfo);
};

思考1:在Block里写__strong typeof(weakSelf) strongSelf = weakSelf;会不会导致self-Block引用循环?

答案是不会的,因为strongSelf是Block代码块里定义的一个局部变量,当Block执行完后,局部变量就会销毁,这样就不会有强引用的指针指向self对象.self与Block之间也就不会形成一个引用循环.

思考2:是不是Block里只能使用weakSelf,不能使用self?

这个并不是绝对的,使用weakSelf当然是不会有问题的.而如果self不持有Block,那么即使Block里使用self,也不会形成self-Block引用循环,这种情况下在Block里直接使用self也是没有问题的,只不过此时self对象的销毁将依赖于Block的销毁,换句话说就是self对象的生命周期可能会被延长.

阅读全文 »

block截获self情况.

略.

block截获成员变量时,避免循环引用的几种基本操作.

下面这段代码如果不做任何处理,在block属性里直接使用成员变量,100%导致循环引用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
场景:

typedef void(^MyBlk)(void);

@interface SecondViewController ()
{
Animal *_myAnimal;
}

@property (nonatomic, copy) MyBlk blk;

@end

_myAnimal = [[Animal alloc] init];
self.blk = ^{
NSLog(@"%@", _myAnimal); //因为实例变量的访问等价于self->_myAnimal,也等价于(*self)._myAnimal;所以这里会导致block强引用self.
};

那么如何解决呢?有如下几种操作:

  1. 使用__weak typeof(成员变量) weak成员变量 = 成员变量;

    1
    2
    3
    4
    5
    _myAnimal = [[Animal alloc] init];
    __weak typeof(_myAnimal) weakMyAnimal = _myAnimal;
    self.blk = ^{
    NSLog(@"%@", weakMyAnimal);
    };
  2. 使用局部变量,这样block就不再持有self.于是循环引用便不能形成了.

    1
    2
    3
    4
    5
    _myAnimal = [[Animal alloc] init];
    Animal *tMyAnimal = _myAnimal;
    self.blk = ^{
    NSLog(@"%@", tMyAnimal);
    };
  3. 对方法2进行改进,增加__block修饰符.

    1
    2
    3
    4
    5
    6
    _myAnimal = [[Animal alloc] init];
    __block Animal *tMyAnimal = _myAnimal;
    self.blk = ^{
    NSLog(@"%@", tMyAnimal);
    tMyAnimal = nil;
    };
  4. 使用__weak typeof(self) weakSelf = self;

    1
    2
    3
    4
    5
    6
    7
    8
    _myAnimal = [[Animal alloc] init];
    __weak typeof(self) weakSelf = self;
    self.blk = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) return ; //必须加上判断,否则一旦self在这之前被释放销毁了,那么后面的->操作将崩溃.
    NSLog(@"%@", strongSelf -> _myAnimal); //一旦block在执行中,对象被销毁了,很容易造成对NULL指针进行解引用导致崩溃.所以前面的判断是必须的.
    NSLog(@"%@", (*strongSelf)._myAnimal);
    };

那么这几种办法中哪种才是最佳实践呢?从写法上来看,第一种和第二种以及改进型的第三种要比第四种方便很多.那么第一种方法和第二种方法哪种更好呢?这里就需要知道什么是block以及为什么使用weak修饰符后就可以解除循环引用.

什么是block

带有自动变量(局部变量)值的匿名函数就是block.匿名函数就是不带有名称的函数.带有自动变量值在block中的表现就是”截获自动变量值”.

阅读全文 »