iOS tips 12

2016-06-20 | 阅读

禁用dylib钩子

Other Linker Flags添加设定 :

-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null  

这样打出来的包就不会被 dylib注入了,APP的Release版本应该设置该属性,提高安全性.但是DEBUG版本还是不要设置该属性了,Xcode上很多功能都是靠dylib注入实现的,如单元测试,InstrumentsDebug View Hierarchy等功能,设置该属性后,这些功能就不能正常使用了.

imageWithRenderingMode:

UIImage中有函数imageWithRenderingMode:,以返回一张以指定渲染模式 渲染的图片.枚举UIImageRenderingMode有三个值:

typedef NS_ENUM(NSInteger, UIImageRenderingMode) {
     UIImageRenderingModeAutomatic,          // Use the default rendering mode for the context where the image is used
     UIImageRenderingModeAlwaysOriginal,     // Always draw the original image, without treating it as a template
     UIImageRenderingModeAlwaysTemplate,     // Always draw the image as a template image, ignoring its color information
 } NS_ENUM_AVAILABLE_IOS(7_0);
  • UIImageRenderingModeAlwaysOriginal 就和字面的意思一样,这个模式告诉系统按照图片文件原来的样子渲染图片。
  • UIImageRenderingModeAlwaysTemplate 这是最有意思的模式。首先会扫描你的图片,然后从图片中所有不透明的像素创建一个模板。这同时也会忽略图片的所有颜色信息。你可以使用UIView子类的tintColor属性来给图片填充你选择的颜色。
  • UIImageRenderingModeAutomatic 这个模式由系统根据图片的使用环境来决定如何渲染图片。如果你的图片是用在比如UITabBar、UINavigationBar、UIToolbar 和UISegmentedControl这些地方,图片使用AlwaysTemplate渲染模式。图片用在其他的地方则会使用AlwaysOriginal渲染模式。

而所有图片默认是使用UIImageRenderingModeAutomatic的,这就遇到一个问题,对于UITabBarUINavigationBarUIToolbarUISegmentedControl这些地方 , 我们设置的图片可能并不是真正的图片效果,我们需要图片使用UIImageRenderingModeAlwaysOriginal原图,而不是乱加渲染.

Xcode上,可以在对图片直接设置RenderMode,在右边属性栏改变Render As属性.

NSCache:

NSCacheNSMutableDictionary相似,但是会在收到低内存通知时,自动删减缓存。

NSCache可以设置数量限制,通过countLimittotalCostLimit来限制cache的数量或者限制cost。当缓存的数量超过countLimit,或者cost之和超过totalCostLimit,NSCache会自动释放部分缓存。

NSCache是线程安全的,在多线程操作中,不需要对Cache加锁。NSCache的Key只是对对象的strong引用,对象不需要实现NSCopying协议,NSCache也不会像NSDictionary一样复制对象。

关于私有API

Published API(公开的API)(或者Documented API) 还有两类:私有API:Private API和未公开API:UnPublished API(或者Undocumented API)。

私有API是指放在PrivateFrameworks框架中的API,未公开的API是指虽然放在Frameworks框架中,但是却没有在苹果的官方文档中有使用说明、代码介绍等记录的API.

别人整理的iOS全部头文件信息, 在这里查看私有API.

获取UIView上的像素点

一般通过将UIView截图,制作出一个UIImage,然后再将UIImage转换为data类型,从data中获取固定位置的像素点信息:

UIGraphicsBeginImageContext(_gradientLayer.bounds.size);
[_gradientLayer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

_colorImageList = [NSMutableArray arrayWithCapacity:_height];

// First get the image into your data buffer
CGImageRef imageRef = [viewImage CGImage];
NSUInteger width = 2;
NSUInteger height = _height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                             bitsPerComponent, bytesPerRow, colorSpace,
                                             kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);

CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);

// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex ;
for (int i = 0 ; i < _height ; ++i)
{
    byteIndex = bytesPerRow * i +  bytesPerPixel;
    CGFloat alpha = ((CGFloat) rawData[byteIndex + 3] ) / 255.0f;
    CGFloat red   = ((CGFloat) rawData[byteIndex]     ) / alpha;
    CGFloat green = ((CGFloat) rawData[byteIndex + 1] ) / alpha;
    CGFloat blue  = ((CGFloat) rawData[byteIndex + 2] ) / alpha;
    
    UIColor *acolor = [UIColor colorWithRed:red / 255.0f green:green / 255.0f blue:blue / 255.0f alpha:alpha];
    
    [_colorList addObject:uiimage];
}
free(rawData);

CoreText的字体属性

const CFStringRef kCTCharacterShapeAttributeName;              
//字体形状属性  必须是CFNumberRef对象默认为0,非0则对应相应的字符形状定义,如1表示传统字符形状
const CFStringRef kCTFontAttributeName;                        
//字体属性   必须是CTFont对象
const CFStringRef kCTKernAttributeName;                        
//字符间隔属性 必须是CFNumberRef对象
const CFStringRef kCTLigatureAttributeName;                 
//设置是否使用连字属性,设置为0,表示不使用连字属性。标准的英文连字有FI,FL.默认值为1,既是使用标准连字。也就是当搜索到f时候,会把fl当成一个文字。必须是CFNumberRef 默认为1,可取0,1,2
const CFStringRef kCTForegroundColorAttributeName;             
//字体颜色属性  必须是CGColor对象,默认为black
const CFStringRef kCTForegroundColorFromContextAttributeName; 
 //上下文的字体颜色属性 必须为CFBooleanRef 默认为False,
const CFStringRef kCTParagraphStyleAttributeName;              
//段落样式属性 必须是CTParagraphStyle对象 默认为NIL
const CFStringRef kCTStrokeWidthAttributeName;              
//笔画线条宽度 必须是CFNumberRef对象,默为0.0f,标准为3.0f
const CFStringRef kCTStrokeColorAttributeName;              
//笔画的颜色属性 必须是CGColorRef 对象,默认为前景色
const CFStringRef kCTSuperscriptAttributeName;              
//设置字体的上下标属性 必须是CFNumberRef对象 默认为0,可为-1为下标,1为上标,需要字体支持才行。如排列组合的样式Cn1
const CFStringRef kCTUnderlineColorAttributeName;           
//字体下划线颜色属性 必须是CGColorRef对象,默认为前景色
const CFStringRef kCTUnderlineStyleAttributeName;           
//字体下划线样式属性 必须是CFNumberRef对象,默为kCTUnderlineStyleNone 可以通过CTUnderlineStypleModifiers 进行修改下划线风格
const CFStringRef kCTVerticalFormsAttributeName;
//文字的字形方向属性 必须是CFBooleanRef 默认为false,false表示水平方向,true表示竖直方向
const CFStringRef kCTGlyphInfoAttributeName;
//字体信息属性 必须是CTGlyphInfo对象
const CFStringRef kCTRunDelegateAttributeName
//CTRun 委托属性 必须是CTRunDelegate对象

取消延迟函数

GCD的任务是不能取消的,而通过performSelector:withObject:afterDelay:进行延迟的函数是可以取消的,通过cancelPreviousPerformRequestsWithTarget: selector:object:来取消.

cancelPreviousPerformRequestsWithTarget: selector:object:这个函数需要注意,首先只能取消当前run loop上的任务,且对于有参数的函数,参数必须通过isEqual判断相同.如果参数设置为nil,表示匹配调用performSelector:withObject:afterDelay:时传递参数为nil的任务.

也可以通过cancelPreviousPerformRequestsWithTarget:方法,取消所有target上的任务.

麻烦的NSTimer

在NSTImer中scheduledTimerWithTimeInterval 第一次执行会等待这个时间间隔.

NSTimer是一个很危险的东西,危险在于,它居然持有target,导致target和NSTimer肯定互相持有,导致无法释放.所以最好少用NSTimer,用任何其他的东西都比这个鬼NSTimer要靠谱多了.

好.随便写个简单的东西来安全的处理NSTimer:

// 写一个简单的类,来处理NSTimer.
@interface NSTimerTargetContainer : NSObject

// 持有target.
- (instancetype)initWithTarget:(NSObject *)target;

@property (nonatomic) Class targetClass;

@property (nonatomic,weak) NSObject *target;

@end

@implementation NSTimerTargetContainer

- (instancetype)initWithTarget:(NSObject *)target {
    NSParameterAssert(target != nil);
    if (self = [super init]) {
        _target = target;
        _targetClass = [target class];
    }
    return self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    // 防止崩溃.
    struct objc_method_description methodDescription = *method_getDescription(class_getInstanceMethod(_targetClass, selector));
    if (methodDescription.name == NULL) {
        // 如果不存在,说明设置的Target没有该selector,蹦就蹦吧.
        return [super methodSignatureForSelector:selector];
    }
    return [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([_target respondsToSelector:aSelector]) {
        // 如果target存在,则转发.
        return _target;
    }
    return nil;
}

@end

创建一个NSTimerTargetContainer的类,以封装可能会持有NSTimer的target,一般就是self,通过forwardingTargetForSelector来转发消息给target,作为targetNSTimer之间的桥梁,保证两者不会互相持有.

关于NSBundle的pathForResource方法

- (NSString *)pathForResource:(NSString *)name ofType:(NSString *)extension

需要注意的是,

对于name 有:

The name of the resource file. If you specify nil, the method returns the first resource file it finds with the specified extension. .

如果没有设置name,则会返回第一个对应上后缀名的文件.

对于 extension 有:

If you specify an empty string or nil, the extension is assumed not to exist and the file is the first file encountered that exactly matches name.

如果没有设置后缀名,会返回第一个匹配文件名的文件.