首页 文章 精选 留言 我的

精选列表

搜索[源码阅读],共10005篇文章
优秀的个人博客,低调大师

阅读YYModel

YYModel库中涉及到Runtime、CF API、信号和锁、位的操作。学习该库可以学习到使用Runtime获取类的信息,包括:类属性信息、类ivar信息、类方法、类型编码;使用runtime底层技术进行方法调用,也就是objc_msgSend方法的使用;dispatch_semaphore_t信号锁的使用;CF框架中CFMutableDictionaryRef/CFMutableDictionaryRef对象的操作;位的操作。 简单使用介绍 简单的转化 自定义属性和json字段的映射配置 黑名单和白名单配置 类型映射配置 预备知识 Type Encodings Property Type String 代码解析 类信息的获取 配置信息的获取 模型对象的转换 其它重要知识点 位操作 信号和锁 CF框架API YYModel使用介绍 首先会介绍下YYModel的使用,作为下面代码解析章节的铺垫,代码解析章节以使用方式作为入口点切入,研究框架整体的实现思路、步骤以及每个步骤使用的详细技术。 简单的转化 类属性的定义和json数据中的key是一致的,这种情况最为简单,不用配置映射关系,使用NSObject+YYModel的方法yy_modelWithDictionary获取yy_modelWithJSON即可以把数据反序列化为对象。 比如元素的JSON数据如下所示: { "id": 7975492, "title": "同学们,开学了,准备好早起了吗?\n", "source_text": "来自新浪微博", "share_count": 1, "praise_num": 999, "comment_num": 0, "is_praise": 0 } 定义的数据模型类如下: @interface IMYOriginalRecommendWeiboModel : IMYRecommendBaseModel <YYModel> @property (nonatomic, copy) NSString *source_text; @property (nonatomic, assign) NSInteger share_count; @property (nonatomic, assign) NSInteger praise_num; ///< 点赞数 @property (nonatomic, assign) BOOL is_praise; ///< 是否已点赞 @property (nonatomic, assign) NSInteger comment_num; ///< 评论数量 @end 转换后的结果如下:  自定义属性和json字段的映射配置 多数情况下,服务端的接口是为了多平台开发的,不同平台的属性定义标准、格式不一样,多数情况下我们需要把服务端的数据个数映射为平台适应的格式,这种情况需要用到配置自定义属性和json字段的映射,可以重写YYModel协议的方法modelCustomPropertyMapper返回一个配置。 定义的数据模型类如下: @interface IMYOriginalRecommendWeiboModel : IMYRecommendBaseModel <YYModel> @property (nonatomic, copy) NSString *sourceText; @property (nonatomic, assign) NSInteger shareCount; @property (nonatomic, assign) NSInteger praiseCount; ///< 点赞数 @property (nonatomic, assign) BOOL isPraise; ///< 是否已点赞 @property (nonatomic, assign) NSInteger commentCount; ///< 评论数量 @end 映射关系配置如下: @implementation IMYRecommendWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } @end 转换后的结果如下:  黑名单和白名单配置 黑名单配置如下: @implementation IMYRecommendBlackListWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } + (NSArray<NSString *> *)modelPropertyBlacklist { return @[@"sourceText", @"shareCount"]; } @end 反序列化的结果,配置在黑名单中的属性(sourceText和shareCount)不会被赋值  白名单配置如下: @implementation IMYRecommendWhiteListWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } + (NSArray<NSString *> *)modelPropertyWhitelist { return @[@"sourceText", @"shareCount"]; } @end 反序列化的结果,只有配置在白名单中的属性(sourceText和shareCount)才会被赋值  类型映射配置 类型映射配置用于属性是数组类型,需要配置该属性中元素的类类型,比如我们定义的数据模型如下,有个users指定是数组类型 @interface IMYRecommendExtendWeiboModel : IMYRecommendBaseModel <YYModel> @property (nonatomic, copy) NSString *sourceText; @property (nonatomic, assign) NSInteger shareCount; @property (nonatomic, assign) NSInteger praiseCount; ///< 点赞数 @property (nonatomic, assign) BOOL isPraise; ///< 是否已点赞 @property (nonatomic, assign) NSInteger commentCount; ///< 评论数量 @property (nonatomic, strong) NSArray<IMYUser *> *users; @end 类型映射配置如下: @implementation IMYRecommendBlackListWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } + (NSDictionary *)modelContainerPropertyGenericClass { return @{@"users" : [IMYUser class]}; } @end 反序列化的结果如下:  预备知识 首先需要了解的是苹果对于类型说明的的一些规范,包括了Type Encodings和Property Type String,YYModel代码中重要的一部分就是对这些信息进行建模处理。 Type Encodings Type Encodings 指的是属性、参数、变量的类型,比如基本数据类型有char、int、short、long、等;此外还有对象类型、类类型、数组类型、结构体类型、共用体类型、OC中的SEL类型等。其中Block 类型的比较特殊,该类型的的类型定义为:"@?",在YYModel中处理Block类型的代码如下: case '@': { if (len == 2 && *(type + 1) == '?') return YYEncodingTypeBlock | qualifier; else return YYEncodingTypeObject | qualifier; } Property Type String Property Type String 指的是property的内存属性、读写属性、原子属性、getter/getter属性、属性对应的ivar名称以及属性本身的Type Encoding,其中property的内存属性、读写属性、原子属性是只有属性名称,没有属性值,而property的getter/getter属性、属性对应的ivar名称以及属性本身的Type Encoding除了属性名称之外还会附加一个属性的值。查看property的属性名字和值可以使用property_getAttributes方法获取,返回的是一个const char *类型,如果需要对属性的名称和值进行详细的分析可以使用property_copyAttributeList获取到一个数组,数组的元素是objc_property_attribute_t这种结构体类型的,已经解析好了name和value,方便使用。 假设定义有如下的属性@property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; ,使用property_getAttributes方法获取到的属性对应的属性描述如下:Ti,R,GisIntReadOnlyGetter,intReadonlyGetter属性对应的objc_property_attribute_t类型为p,基本的规则解释如下: 类型 p.name == 'T',需要获取 p.value值,然后查询Type Encodings表格 对应的ivar名称 p.name == 'V',需要获取 p.value值,p.value值为ivar名称 读写属性 p.name == 'R' -> readonly 内存属性 p.name == 'C' -> copy; p.name == '&' -> retain; p.name == 'W' -> weak 原子属性 p.name == 'N' -> nonatomic getter/setter属性 p.name = 'G<name>' -> G后面的内容为自定义的getter方法名称,使用p.value获取;p.name = 'S<name>' -> S后面的内容为自定义的setter方法名称,使用p.value获取 以下是一段使用property_getAttributes方法获取属性名字和值得示例代码 定义一个测试的类包含有如下几个属性 @interface MyObj : NSObject @property (nonatomic, copy) MyBlock block; @property struct YorkshireTeaStruct structDefault; @property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic; @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; @end 使用如下的代码解析property的属性 char *propertyAttributes = (char *)property_getAttributes(property); NSString *propertyAttributesString = [NSString stringWithUTF8String:propertyAttributes]; NSLog(@"propertyAttributesString = %@", propertyAttributesString); 使用以上的代码对MyObj类的属性进行分析,打印的内容如下,可以对照Property Type String和Type Encodings查看详细的说明: propertyAttributesString = T@?,C,N,V_block propertyAttributesString = T{YorkshireTeaStruct=ic},V_structDefault propertyAttributesString = T@,R,&,N,V_idReadonlyRetainNonatomic propertyAttributesString = Ti,R,GisIntReadOnlyGetter,V_intReadonlyGetter 代码解析 类信息的获取 类信息的获取主要是在YYClassInfo类的方法_update中处理的,包括类的类对象、父类的类对象、父类的类信息、是否是元类、属性信息、方法信息、ivar信息。实际上方法信息、ivar信息这两个信息并没有真正的使用到,但是框架中有进行处理,为了完整性还是稍作叙述。主要使用到的还是propertyInfos属性中的内容,后面的配置信息获取和模型对象转换都需要使用到。 YYClassInfo类的定义主要如下 @interface YYClassInfo : NSObject @property (nonatomic, assign, readonly) Class cls; ///< class object @property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object @property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object @property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class @property (nonatomic, strong, readonly) NSString *name; ///< class name @property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties // ... 流程时序图如下所示:  获取类信息的操作主要都在_update方法中,主要步骤如下 class_copyMethodList 方法获取类中的方法信息,方法信息转换为YYClassMethodInfo对象,保存在methodInfos属性中 class_copyPropertyList 方法获取类中的属性信息,属性信息转换为YYClassPropertyInfo对象,保存在propertyInfos属性中 class_copyIvarList 方法获取类中的ivar信息,ivar信息转换为YYClassIvarInfo对象,保存在ivarInfos属性中 这里需要注意的是class_copyMethodList、class_copyPropertyList、class_copyIvarList涉及到内存的拷贝问题,使用完成之后需要使用C的方法free释放拷贝的内容,防止内存泄漏。上面有提到说方法信息、ivar信息这两个信息并没有真正的使用到,接下来会着重的介绍属性信息的获取中的一些细节。 - (void)_update { _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; // 获取类中的方法信息 unsigned int methodCount = 0; Method *methods = class_copyMethodList(cls, &methodCount); if (methods) { NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; for (unsigned int i = 0; i < methodCount; i++) { YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; if (info.name) methodInfos[info.name] = info; } free(methods); } // 获取类中的属性信息 unsigned int propertyCount = 0; objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); if (properties) { NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; _propertyInfos = propertyInfos; for (unsigned int i = 0; i < propertyCount; i++) { YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; if (info.name) propertyInfos[info.name] = info; } free(properties); } // 获取类中的ivar信息 unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList(cls, &ivarCount); if (ivars) { NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; _ivarInfos = ivarInfos; for (unsigned int i = 0; i < ivarCount; i++) { YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; if (info.name) ivarInfos[info.name] = info; } free(ivars); } if (!_ivarInfos) _ivarInfos = @{}; if (!_methodInfos) _methodInfos = @{}; if (!_propertyInfos) _propertyInfos = @{}; _needUpdate = NO; } 属性信息获取 属性信息主要包含属性名字、属性的Encoding 类型、属性所属的类、属性的getter/setter方法的选择子SEL。这些信息会保存在属性信息类YYClassPropertyInfo中,属性信息类YYClassPropertyInfo定义如下: @interface YYClassPropertyInfo : NSObject @property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct @property (nonatomic, strong, readonly) NSString *name; ///< property's name @property (nonatomic, assign, readonly) YYEncodingType type; ///< property's type @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value @property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name @property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil @property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil @property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull) @property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull) // ... 属性处理主要在initWithProperty方法中进行,这部分的内容在前面的预备知识中的Type Encodings和Property Type String有讲到了大部分,主要步骤如下: 获取property名字 读取property的属性名字和属性值,建立属性的Encoding模型 处理getter/setter - (instancetype)initWithProperty:(objc_property_t)property { if (!property) return nil; self = [super init]; _property = property; // 获取property名字 const char *name = property_getName(property); if (name) { _name = [NSString stringWithUTF8String:name]; } // 读取property的属性名字和属性值,建立属性的Encoding模型 YYEncodingType type = 0; unsigned int attrCount; objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); for (unsigned int i = 0; i < attrCount; i++) { switch (attrs[i].name[0]) { case 'T': { // Type encoding if (attrs[i].value) { _typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; type = YYEncodingGetType(attrs[i].value); if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) { NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding]; if (![scanner scanString:@"@\"" intoString:NULL]) continue; NSString *clsName = nil; if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) { if (clsName.length) _cls = objc_getClass(clsName.UTF8String); } NSMutableArray *protocols = nil; while ([scanner scanString:@"<" intoString:NULL]) { NSString* protocol = nil; if ([scanner scanUpToString:@">" intoString: &protocol]) { if (protocol.length) { if (!protocols) protocols = [NSMutableArray new]; [protocols addObject:protocol]; } } [scanner scanString:@">" intoString:NULL]; } _protocols = protocols; } } } break; case 'V': { // Instance variable if (attrs[i].value) { _ivarName = [NSString stringWithUTF8String:attrs[i].value]; } } break; case 'R': { type |= YYEncodingTypePropertyReadonly; } break; case 'C': { type |= YYEncodingTypePropertyCopy; } break; case '&': { type |= YYEncodingTypePropertyRetain; } break; case 'N': { type |= YYEncodingTypePropertyNonatomic; } break; case 'D': { type |= YYEncodingTypePropertyDynamic; } break; case 'W': { type |= YYEncodingTypePropertyWeak; } break; case 'G': { type |= YYEncodingTypePropertyCustomGetter; if (attrs[i].value) { _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } break; case 'S': { type |= YYEncodingTypePropertyCustomSetter; if (attrs[i].value) { _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } // break; commented for code coverage in next line default: break; } } if (attrs) { free(attrs); attrs = NULL; } _type = type; // 处理getter、setter方法 if (_name.length) { if (!_getter) { _getter = NSSelectorFromString(_name); } if (!_setter) { _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]); } } return self; } 配置信息的获取 配置信息的获取主要流程时序图如下:  主要有以下几个步骤: 黑名单配置获取 白名单配置获取 容器类型中元素类型配置获取 递归遍历当前类和父类的propertyInfos属性,获取所有property的元数据 处理自定义的属性对于json数据key的映射配置 对应的代码如下,关键的地方有添加了注释: - (instancetype)initWithClass:(Class)cls { YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; if (!classInfo) return nil; self = [super init]; // Get black list // 黑名单配置获取 NSSet *blacklist = nil; if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) { NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist]; if (properties) { blacklist = [NSSet setWithArray:properties]; } } // Get white list // 白名单配置获取 NSSet *whitelist = nil; if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) { NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist]; if (properties) { whitelist = [NSSet setWithArray:properties]; } } // Get container property's generic class // 容器类型中元素类型配置获取 NSDictionary *genericMapper = nil; if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) { genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass]; if (genericMapper) { NSMutableDictionary *tmp = [NSMutableDictionary new]; [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if (![key isKindOfClass:[NSString class]]) return; Class meta = object_getClass(obj); if (!meta) return; if (class_isMetaClass(meta)) { tmp[key] = obj; } else if ([obj isKindOfClass:[NSString class]]) { Class cls = NSClassFromString(obj); if (cls) { tmp[key] = cls; } } }]; genericMapper = tmp; } } // 递归遍历当前类和父类的propertyInfos属性,获取所有property的元数据 // property的元数据对应的私有类为_YYModelPropertyMeta,其实就是YYClassPropertyInfo类对象的封装 NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; YYClassInfo *curClassInfo = classInfo; while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy) for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) { if (!propertyInfo.name) continue; if (blacklist && [blacklist containsObject:propertyInfo.name]) continue; if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue; _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]]; if (!meta || !meta->_name) continue; if (!meta->_getter || !meta->_setter) continue; if (allPropertyMetas[meta->_name]) continue; allPropertyMetas[meta->_name] = meta; } curClassInfo = curClassInfo.superClassInfo; } if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy; // create mapper // 处理自定义的属性对于json数据key的映射配置 NSMutableDictionary *mapper = [NSMutableDictionary new]; NSMutableArray *keyPathPropertyMetas = [NSMutableArray new]; NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new]; if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper]; [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; if (!propertyMeta) return; [allPropertyMetas removeObjectForKey:propertyName]; if ([mappedToKey isKindOfClass:[NSString class]]) { if (mappedToKey.length == 0) return; propertyMeta->_mappedToKey = mappedToKey; NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."]; for (NSString *onePath in keyPath) { if (onePath.length == 0) { NSMutableArray *tmp = keyPath.mutableCopy; [tmp removeObject:@""]; keyPath = tmp; break; } } if (keyPath.count > 1) { propertyMeta->_mappedToKeyPath = keyPath; [keyPathPropertyMetas addObject:propertyMeta]; } // 处理有多个属性绑定到同一个json数据key的配置 propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } else if ([mappedToKey isKindOfClass:[NSArray class]]) { NSMutableArray *mappedToKeyArray = [NSMutableArray new]; for (NSString *oneKey in ((NSArray *)mappedToKey)) { if (![oneKey isKindOfClass:[NSString class]]) continue; if (oneKey.length == 0) continue; NSArray *keyPath = [oneKey componentsSeparatedByString:@"."]; if (keyPath.count > 1) { [mappedToKeyArray addObject:keyPath]; } else { [mappedToKeyArray addObject:oneKey]; } if (!propertyMeta->_mappedToKey) { propertyMeta->_mappedToKey = oneKey; propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil; } } if (!propertyMeta->_mappedToKey) return; propertyMeta->_mappedToKeyArray = mappedToKeyArray; [multiKeysPropertyMetas addObject:propertyMeta]; // 处理有多个属性绑定到同一个json数据key的配置 propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } }]; } // 处理默认的属性的映射配置,属性在json数据中的key就是属性的原始名称 [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { propertyMeta->_mappedToKey = name; propertyMeta->_next = mapper[name] ?: nil; mapper[name] = propertyMeta; }]; // 最终把json数据key和属性的映射关系保存起来 if (mapper.count) _mapper = mapper; if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas; if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas; _classInfo = classInfo; _keyMappedCount = _allPropertyMetas.count; _nsType = YYClassGetNSType(cls); _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]); _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]); _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]); _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]); return self; } 模型对象的转换 模型对象的转换的步骤时序图如下:  入口函数yy_modelSetWithDictionary的功能如下: 1、属性的映射配置的个数大于json数据元素的个数(if分支),优先处理json数据 1.1、遍历json数据的key 1.2、根据key从meta->_mapper配置中寻找映射的_YYModelPropertyMeta对象 1.3、从json数据中读取值,给属性设置值。 2、json数据元素的个数大于属性的映射配置的个数(else分支),优先处理属性属性 2.1、从modelMeta->_allPropertyMetas读取属性配置 2.2、从json数据中读取值,给属性设置值 对应的代码如下,关键的地方有添加了注释: - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { if (!dic || dic == (id)kCFNull) return NO; if (![dic isKindOfClass:[NSDictionary class]]) return NO; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; if (modelMeta->_keyMappedCount == 0) return NO; if (modelMeta->_hasCustomWillTransformFromDictionary) { dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic]; if (![dic isKindOfClass:[NSDictionary class]]) return NO; } ModelSetContext context = {0}; context.modelMeta = (__bridge void *)(modelMeta); context.model = (__bridge void *)(self); context.dictionary = (__bridge void *)(dic); // 这里做这个处理主要是为了优化性能,哪个少优先处理哪个,提高效率 // 1、属性的映射配置的个数大于json数据元素的个数(if分支),优先处理json数据 // 1.1、遍历json数据的key // 1.2、根据key从`meta->_mapper`配置中寻找映射的`_YYModelPropertyMeta`对象 // 1.3、从json数据中读取值,给属性设置值。 // * 因为`meta->_mapper`保存的是一对一的映射关系,所以另外需要额外的处理keypath映射和数组类型的key的映射 // 2、json数据元素的个数大于属性的映射配置的个数(else分支),优先处理属性属性 // 2.1、从`modelMeta->_allPropertyMetas`读取属性配置 // 2.2、从json数据中读取值,给属性设置值 if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { // 处理多个属性映射到同一个json数据的key CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); if (modelMeta->_keyPathPropertyMetas) { // 处理keypath,属性对应json数据key是多层类型的映射 CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_multiKeysPropertyMetas) { // 处理数组类型映射,属性对应json数据key是多个的映射 CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { // 处理属性多json数据的key的一对一映射,这种情况多个属性映射到同一个json数据的key处理方式和ModelSetWithDictionaryFunction方法的不一样,不过最终的结果是一样的 CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_hasCustomTransformFromDictionary) { return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic]; } return YES; } 以上代码中使用到的两个方法ModelSetWithDictionaryFunction、ModelSetWithPropertyMetaArrayFunction,这两个方法很简单,使用获取值方法YYValueForMultiKeys、YYValueForKeyPath,根据_YYModelPropertyMeta对象的配置从json数据中获取值,然后调用ModelSetValueForProperty给对象的属性设置值。 static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)]; __unsafe_unretained id model = (__bridge id)(context->model); while (propertyMeta) { if (propertyMeta->_setter) { ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta); } propertyMeta = propertyMeta->_next; }; } static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) { ModelSetContext *context = _context; __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary); __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta); if (!propertyMeta->_setter) return; id value = nil; if (propertyMeta->_mappedToKeyArray) { value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray); } else if (propertyMeta->_mappedToKeyPath) { value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath); } else { value = [dictionary objectForKey:propertyMeta->_mappedToKey]; } if (value) { __unsafe_unretained id model = (__bridge id)(context->model); ModelSetValueForProperty(model, value, propertyMeta); } } 获取值方法YYValueForMultiKeys、YYValueForKeyPath也比较简单 YYValueForMultiKeys深度遍历json数据取值 YYValueForKeyPath广度遍历json数据取值 static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) { id value = nil; for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) { value = dic[keyPaths[i]]; if (i + 1 < max) { if ([value isKindOfClass:[NSDictionary class]]) { dic = value; } else { return nil; } } } return value; } static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) { id value = nil; for (NSString *key in multiKeys) { if ([key isKindOfClass:[NSString class]]) { value = dic[key]; if (value) break; } else { value = YYValueForKeyPath(dic, (NSArray *)key); if (value) break; } } return value; } 到了最关键的设置属性值的方法ModelSetValueForProperty,使用底层的C语言方法objc_msgSend给属性赋值,该方法区分类型处理值 数值类型 NSFoundation类型,包含了NSArray、NSString、NSDictionary、NSData等类型以及对应的可变类型(如果存在) 其余特殊类型,包含了SEL、block、Struct、Union、C指针、字符串指针等 需要注意: 1、可变类型需要特殊处理,需要使用mutableCopy复制一个可变对象赋值给属性,否则使用可变类型的特有api比如addObject就会出现崩溃; 2、json数据取出的值和property定义的值类型可能不一样,如果是数值类型,需要把数值转换为对应类型,否则编译不过;如果是NSFoundation类型,需要把Json数据转换为property定义的类型,否则类型会有问题,方法objc_msgSend可以设置成功,比如property中定义的是属性status_desc是NSString类型,方法objc_msgSend方法的参数传递的是一个NSDate类型,使用status_desc的方法比如length就会崩溃,因为类型变为NSDate了,在NSDate中找不到length方法 方法的部分如下: static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta) { if (meta->_isCNumber) { NSNumber *num = YYNSNumberCreateFromID(value); ModelSetNumberToProperty(model, num, meta); if (num) [num class]; // hold the number } else if (meta->_nsType) { if (value == (id)kCFNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else { switch (meta->_nsType) { case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { if ([value isKindOfClass:[NSString class]]) { if (meta->_nsType == YYEncodingTypeNSString) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy); } } else if ([value isKindOfClass:[NSNumber class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSNumber *)value).stringValue : ((NSNumber *)value).stringValue.mutableCopy); } else if ([value isKindOfClass:[NSData class]]) { NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string); } else if ([value isKindOfClass:[NSURL class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSURL *)value).absoluteString : ((NSURL *)value).absoluteString.mutableCopy); } else if ([value isKindOfClass:[NSAttributedString class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSAttributedString *)value).string : ((NSAttributedString *)value).string.mutableCopy); } } break; // ... 省略很多 default: break; } } } else { BOOL isNull = (value == (id)kCFNull); switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { if (isNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value); } else if ([value isKindOfClass:[NSDictionary class]]) { NSObject *one = nil; if (meta->_getter) { one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); } if (one) { [one yy_modelSetWithDictionary:value]; } else { Class cls = meta->_cls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:value]; if (!cls) cls = meta->_genericCls; // for xcode code coverage } one = [cls new]; [one yy_modelSetWithDictionary:value]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one); } } } break; case YYEncodingTypeClass: { if (isNull) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL); } else { Class cls = nil; if ([value isKindOfClass:[NSString class]]) { cls = NSClassFromString(value); if (cls) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls); } } else { cls = object_getClass(value); if (cls) { if (class_isMetaClass(cls)) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value); } } } } } break; case YYEncodingTypeSEL: { if (isNull) { ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL); } else if ([value isKindOfClass:[NSString class]]) { SEL sel = NSSelectorFromString(value); if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel); } } break; case YYEncodingTypeBlock: { if (isNull) { ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL); } else if ([value isKindOfClass:YYNSBlockClass()]) { ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value); } } break; case YYEncodingTypeStruct: case YYEncodingTypeUnion: case YYEncodingTypeCArray: { if ([value isKindOfClass:[NSValue class]]) { const char *valueType = ((NSValue *)value).objCType; const char *metaType = meta->_info.typeEncoding.UTF8String; if (valueType && metaType && strcmp(valueType, metaType) == 0) { [model setValue:value forKey:meta->_name]; } } } break; case YYEncodingTypePointer: case YYEncodingTypeCString: { if (isNull) { ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL); } else if ([value isKindOfClass:[NSValue class]]) { NSValue *nsValue = value; if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) { ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue); } } } // break; commented for code coverage in next line default: break; } } } 其它重要知识点 位操作 YYModel框架中定义了一个枚举YYEncodingType,这个类型的值可以保存property的值类型(Type Encoding);property的读写、原子、内存等属性(Property Encoding);方法的类型(Method Encoding)(暂时没用到)。在获取类型方法YYEncodingGetType获取类型的时候使用位活操作符"|"组合多个Encoding,在设置属性值的方法比如ModelSetNumberToProperty,使用位与操作符“&”判断是否是特定的类型。使用这种类型有两个好处:1、节省空间;2、位操作效率比较高,比如乘除2,使用左移或者右移的效率会比乘除法高,不过实际编码中为了可读性还是最好使用乘除法,另外现代的编译器会帮我们做很多优化,在绝大多数的场景中,我们可以不用太关注这些细枝末节,真正需要优化的时候才考虑做这类代码上的优化。 信号和锁 YYModel中使用的是信号锁dispatch_semaphore_t,为什么使用这种类型的锁呢,可以从作者的博客中找到答案不再安全的 OSSpinLock ,这种锁的效率还是非常高的,又可以避免OSSpinLock锁产生的问题。这个场景中dispatch_semaphore_t是一个读写互斥锁,CFDictionaryGetValue是读操作,CFDictionarySetValue是写操作,这两者同一时间只能一个进入,其余的需要等待。 + (instancetype)metaWithClass:(Class)cls { if (!cls) return nil; static CFMutableDictionaryRef cache; static dispatch_once_t onceToken; static dispatch_semaphore_t lock; dispatch_once(&onceToken, ^{ cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); dispatch_semaphore_signal(lock); if (!meta || meta->_classInfo.needUpdate) { meta = [[_YYModelMeta alloc] initWithClass:cls]; if (meta) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta)); dispatch_semaphore_signal(lock); } } return meta; } CF框架API yy_modelSetWithDictionary方法中使用到CF框架中容器遍历的API有CFDictionaryApplyFunction和CFArrayApplyFunction,一般滴使用CF框架的API性能会高于NSFoundation框架的API,因为NSFoundation框架的API是基于CF框架的API,此时NSFoundation框架相当于一个中间者,绕了一步。更多的关于CFArray的知识可以参考Exposing NSMutableArray这篇文章的介绍。

优秀的个人博客,低调大师

书籍——待阅读

《Java并发编程的艺术》 《戒了吧,拖延证》 《洗脑术,怎样有逻辑的说服他人》 《重新定义公司:谷歌是如何运营的》 《乌合之众:大众心理研究》 《像TED一样演讲:打造世界顶级演讲的9个秘诀》 《工业4.0大革命》 《Google 软件测试之道》 《超限战(十五周年纪念版)》 《麦肯锡学院:麦肯锡方法》 《六顶思考帽》 《水平思考法》 《第五项修炼》 《经营战略史》 《麦肯锡问题分析与解决技巧》 《风险管理:理解、评估和实施有效的风险管理》 《六西格玛设计实战》 《TRIZ进阶及实战 大道至简的发明法》 《新产品开发管理体系DOE工具应用指南》 《新产品开发管理体系IPDFSS》 《新产品开发管理体系QFD工具应用指南》 《知识管理战略制胜》 《数据化决策:大数据时代 《财富》500强都在用的量化决策法》 《精益设计(设计团队如何改善用户体验) .》 《精益数据分析》 《学习观察:通过价值流图创造价值、消除浪费》 《六西格玛管理》 《领导术》 《OFF学:会玩才会成功》 《策略思维》 《设计模式:可复用面向对象软件的基础》 《架构之美:软件架构的艺术》作者李伟 《设计模式:可复用面向对象软件的基础》 《微服务架构与实践》 《持续交付:发布可靠软件的系统方法》 《用户体验要素》 《从创思维与技法》 《大规模敏捷开发实践》 《软件产品线工程》 奋斗之光 失控 影响力 你远比想象中的伟大 别让情绪失控害了你 拖延症 跟任何人都聊得来 脆弱的力量 成大事者者不纠结 情商 以奋斗者为本 卓越成效的管理者 高效能人士的七个习惯 失去的胜利 一路向前 不懂带人,你就自己干到死 互联网+从IT到DT 要事第一 大数据时代 掌控 创业维艰 从0到1 财报就像一本故事书 你只是看起来很努力 定位 工业4.0 传习录 阿米巴经营 人才盘点 薪酬设计实战 人才管理 黑天鹅 思考,快与慢 本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/bonelee/p/6755202.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

SOA 初步阅读理解

SOA的基本概念 SOA是英文词语"Service Oriented Architecture"的缩写,中文翻译"面向服务的架构",其中"面向服务的架构"SOA有定义:SOA是包含运行环境、编程模型、架构风格和相关方法论等在内的一整套新的分布式软件系统构造方法和环境,涵盖服务的整个生命周期:建模-开发-整合-部署-运行-管理。SOA是分布式软件系统构造方法和环境的新发展阶段。 在SOA架构风格中,服务是最核心的抽象手段,业务被划分(组件化)为一系列粗粒度的业务服务和业务流程。业务服务相对独立、自包含、可重用,由一个或者多个分布的系统所实现,而业务流程由服务组装而来。一个"服务"定义了一个与业务功能或业务数据相关的接口,以及约束这个接口的契约,如服务质量要求、业务规则、安全性要求、法律法规的遵循、关键业绩指标(Key Performance Indicator,KPI)等。接口和契约采用中立、基于标准的方式进行定义,它独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在不同系统中的服务可以以一种统一的和通用的方式进行交互、相互理解。除了这种不依赖于特定技术的中立特性,通过服务注册库(Service Registry)加上企业服务总线(Enterprise Service Bus)来支持动态查询、定位、路由和中介(Mediation)的能力,使得服务之间的交互是动态的,位置是透明的。技术和位置的透明性,使得服务的请求者和提供者之间高度解耦。这种松耦合系统的好处有两点:一点是它适应变化的灵活性;另一点是当某个服务的内部结构和实现逐渐发生改变时,不影响其他服务。而紧耦合则是指应用程序的不同组件之间的接口与其功能和结构是紧密相连的,因而当发生变化时,某一部分的调整会随着各种紧耦合的关系引起其他部分甚至整个应用程序的更改,这样的系统架构就很脆弱了。 SOA架构带来的另一个重要观点是业务驱动IT,即IT和业务更加紧密地对齐。以粗粒度的业务服务为基础来对业务建模,会产生更加简洁的业务和系统视图;以服务为基础来实现的IT系统更灵活、更易于重用、更好(也更快)地应对变化;以服务为基础,通过显式地定义、描述、实现和管理业务层次的粗粒度服务(包括业务流程),提供了业务模型和相关IT实现之间更好的"可追溯性",减小了它们之间的差距,使得业务的变化更容易传递到IT。 因此,可以将SOA的主要优点概括为:IT能够更好更快地提供业务价值(Business Centric)、快速应变能力(Flexibility)、重用(Reusability)。 从演变的历程来看,SOA在很多年前就被提出来了,现在SOA的再现和流行是若干因素的结合。一方面是多年的软件工程发展和实践所积累的经验、方法和各种设计/架构模式,包括OO/CBD/MDD/MDA、EAI和中间件;另一方面是互联网的多年发展带来前所未有的分布式系统的交互能力和标准化基础。与此同时,企业越来越重视业务模型本身的组件化,以支持高度灵活的业务战略。但是现有的企业软件架构不够灵活,难以适应日益复杂的企业整合,难以满足随需应变商务的需要,因此与业务对齐、以业务的敏捷应变能力为首要目标、松散耦合、支持重用的SOA架构方法得到青睐。 基于我们同客户打交道的经验,有必要在这里澄清大家经常混淆的几个基本问题。 第一,SOA是架构风格,是方法;而不是具体架构具体实现技术(如Web Service)、具体架构元素(如企业服务总线,Enterprise Service Bus,ESB)。 经常有人认为只要用了Web Service,就是SOA了。这是不对的,Web Service只是实现服务的一种具体技术表现形式。同样,认为搞SOA,就是买点软件,建个ESB,这也是不对的,ESB只是SOA架构风格中的一部分。首先,ESB是一种从实践中总结出来的架构风格元素,即BUS(总线模式);其次,ESB的主要功能是负责连通性和服务中介(Service Mediation),解耦服务的请求者和服务的提供者。 第二,SOA的首要目标是IT与业务对齐,支持业务的快速变化;其次是IT架构的灵活性和IT资产的重用。 业务对敏捷性的需要,是SOA最大的驱动力。一方面是业务在这方面的要求越来越高;另一方面是今天的IT很不灵活,很难适应业务快速变化的需求,不仅仅是因为IT架构不灵活,更重要的是业务模型中的元素和IT系统的元素之间存在很大的差距。这种不对齐,导致业务人员和IT人员之间的沟通不够有效,业务的变化需要花费很大的代价传递到IT系统。很难想像,业务人员对一个Java对象,一个EJB或者一个JSP页面感兴趣,这离业务世界太远了。这种业务和IT的对齐,需要在IT系统中实现更高阶的抽象元素,就是业务模型中的元素(服务、流程、业绩管理),并且满足业务需要的水平整合(将人、信息、应用和流程端到端地动态整合起来)。这样一个以服务为中心的、端到端整合的环境,首先使得业务变化可以在业务元素这个层面上沟通,更容易、更准确地从业务传递到IT。其次,这种变化被隔离在需要变化的局部,而不扩散到系统的其他部分。这就需要整个IT架构本身是松散耦合的,一个服务的变化(功能、数据、过程、技术环境等)不影响其他服务。最后,我们希望这些反映业务元素的服务,是相对稳定、可以重用的,这对快速适应变化、减少成本是非常重要的。 第三,在工程上,SOA的重点是服务建模和基于SOA的设计原则进行架构决策和设计。 经常碰到客户提出这样的问题:SOA挺好,为什么好?怎么做才是SOA的方法?跟过去的方法,比如OO/CBD有什么不同?有时候一个J2EE服务器就好了,为什么那么复杂? 从建模和设计的角度来说,SOA更多地侧重在业务层次上,也就是通过服务建模将业务组件化为服务模型,它是业务架构的底层,是技术架构的顶层,承上启下,是灵活的业务模型和IT之间的桥梁,保证二者之间的"可追溯性"。从这里往下,是基于已有的方法,比如OO/CBD来进行的。从架构的层次上,SOA更多地侧重于如何将企业范围内多个分布的系统(包括已有系统/遗留系统)连接起来(ESB,Adapter/Connector),如何将它们的功能、数据转化为服务,如何通过服务中介机制(ESB,Service Registry)保证服务之间以松散耦合的方式交互,如何组装(集成)服务为流程,如何管理服务和流程等。从这往下,对于实现服务的一个具体应用,它的架构、设计和实现是可以基于已有的实践和方法的,比如J2EE或.NET。 有些时候,由于业务需求比较简单,所有这些东西都在一个J2EE的应用服务器上,有些要素不是那么突出,不过随着系统规模的扩大,要解决的业务问题更复杂、范围更大的时候,SOA的各种架构要素就会变得越来越重要。 在下面的小节里就概括地讨论一下SOA的若干个重要方面,包括:面向服务的计算环境,编程模型,架构风格,工程方法,以及相关的技术。 本文转自灵动生活博客园博客,原文链接:http://www.cnblogs.com/ywqu/archive/2008/01/07/1029611.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Spark 官网阅读笔记

1.spark读取本地文件系统: 则该文件也必须可以在工作节点上的相同路径上访问。所以需要将文件复制到所有work 节点或使用网络安装的共享文件系统。 2.group by key 没有reduceBykey, aggregateBykey高效,(后者是同时分区排序) 3.accumulator 累加器的使用 4.broadcast variables 广播变量的使用 5:DataFrame就是DataSet的一行Dataset[Row] 6.用于隐式转换,如将RDD转换为DataFrames . 需要 import spark.implicits._ 7.全局临时视图与系统保存的数据库绑定global_temp,我们必须使用前面加上限定名称来引用它,例如SELECT * FROM global_temp.view1。(加关键字global_temp) 8.新建session测试spark.newSession().sql("select * from global_temp.people") 9.通过提供一个样例类可以将DataFrames转换为Dataset。映射将通过名称 10:类型推断 peopleDF.createOrReplaceTempView("people") val tdf = spark.sql("select name,age from people were age>=13 and age <= 19) tdf.map(t=> "name: " +t(0)).show() tdf.map(t=> "name: " +t.getAs[String]("name')).show() 11. persist table: 写出时没有指定路径参数(默认存储为管理表,路径为hive-site.xml,指定的位置),表被删除时,数据,路径都会被删掉。指定参数则不会。 For file-based data source, e.g. text, parquet, json, etc. you can specify a custom table path via the path option, e.g. df.write.option("path", "/some/path").saveAsTable("t"). When the table is dropped, the custom table path will not be removed and the table data is still there. If no custom table path is specified, Spark will write data to a default table path under the warehouse directory. When the table is dropped, the default table path will be removed too. 12. the automatic type inference can be configured ,配置是否启用类型推断,确定分区字段。by spark.sql.sources.partitionColumnTypeInference.enabled, which is default to true. When type inference is disabled, string type will be used for the partitioning columns. 13.设置全局的参数实现类型合并: spark.sql.parquet.mergeSchema 14:读取HIVE:前提需要pom文件中有spark-hive 依赖。 val spark = SparkSession 。builder() 。appName (“Spark Hive示例” ) 。config (“spark.sql.warehouse.dir” , warehouseLocation ) 。enableHiveSupport () 。getOrCreate () import spark.implicits._ import spark.sql 15:使用广播表: import org.apache.spark.sql.functions.broadcast broadcast(spark.table("src")).join(spark.table("records"), "key").show() 16:pyArrow: 要在执行这些调用时使用箭头,用户需要首先将Spark配置'spark.sql.execution.arrow.enabled'设置为'true'。这是默认禁用的。 pyspark 使用自定义python相关。 配置spark-env.sh 内容:添加如下 export PYSPARK_PYTHON= /home/lio/anaconda3/env/DL/bin/python3.5 即可 ipython notebook with spark http://blog.cloudera.com/blog/2014/08/how-to-use-ipython-notebook-with-apache-spark/ import numpy as np import pandas as pd # Enable Arrow-based columnar data transfers spark.conf.set("spark.sql.execution.arrow.enabled", "true") # Generate a Pandas DataFrame pdf = pd.DataFrame(np.random.rand(100, 3)) # Create a Spark DataFrame from a Pandas DataFrame using Arrow df = spark.createDataFrame(pdf) # Convert the Spark DataFrame back to a Pandas DataFrame using Arrow result_pdf = df.select("*").toPandas() pip install PyArrow - spark-streaming 17: 要仅停止StreamingContext,请将可选参数stop()调用stopSparkContext为false: * @param stopSparkContext If true, stops the associated SparkContext. The underlying SparkContext will be stopped regardless of whether this StreamingContext has been started. */ def stop( stopSparkContext: Boolean = conf.getBoolean("spark.streaming.stopSparkContextByDefault", true) ): Unit = synchronized { stop(stopSparkContext, false) } 18:DStream由连续的RDD系列表示,这是Spark对不可变的分布式数据集的抽象.DStream中的每个RDD都包含来自特定时间间隔的数据. 19:不在driver端创建数据库连接, 不使用foreachRDD操作创建连接,最好使用foreachPartition 和connectionPool dstream.foreachRDD { rdd => val connection = createNewConnection() // executed at the driver rdd.foreach { record => connection.send(record) // executed at the worker } } 这是不正确的,因为这要求将连接对象序列化并从Driver端发送到worker端。这种连接对象很难在机器间转移。此错误可能表现为序列化错误(连接对象不可序列化),初始化错误(连接对象需要在worker初始化)等。正确的解决方案是在worker上创建连接对象。 但是,这可能会导致另一个常见错误 - 为每条记录创建一个新的连接。例如, dstream.foreachRDD { rdd => rdd.foreach { record => val connection = createNewConnection() connection.send(record) connection.close() } } 通常,创建连接对象会带来时间和资源开销。因此,创建和销毁每个记录的连接对象可能会导致不必要的高开销,并且可能会显着降低系统的整体吞吐量。更好的解决方案是使用 rdd.foreachPartition- 创建单个连接对象并使用该连接发送RDD分区中的所有记录。 dstream.foreachRDD { rdd => rdd.foreachPartition { partitionOfRecords => val connection = createNewConnection() partitionOfRecords.foreach(record => connection.send(record)) connection.close() } } 这会缓解许多记录中的连接创建开销。 最后,通过跨多个RDD /批次重用连接对象,可以进一步优化这一点。可以维护一个静态的连接对象池,因为多个批处理的RDD被推送到外部系统,所以可以重复使用,从而进一步降低了开销。 dstream.foreachRDD { rdd => rdd.foreachPartition { partitionOfRecords => // ConnectionPool is a static, lazily initialized pool of connections val connection = ConnectionPool.getConnection() partitionOfRecords.foreach(record => connection.send(record)) ConnectionPool.returnConnection(connection) // return to the pool for future reuse } } 请注意,池中的连接应按需延迟创建,如果暂时不使用,则超时。这实现了向外部系统发送数据的最高效率。 其他要记住的要点: 输出操作会延迟执行DStream,就像RDD由RDD操作懒洋洋地执行一样。具体来说,DStream输出操作中的RDD操作会强制处理接收到的数据。因此,如果您的应用程序没有任何输出操作,或者dstream.foreachRDD()没有任何RDD操作的输出操作,则不会执行任何操作。系统只会收到数据并丢弃它。 默认情况下,输出操作一次执行一次。并且它们按照它们在应用程序中定义的顺序执行。 20: val words: DStream[String] = ... words.foreachRDD { rdd => // Get the singleton instance of SparkSession val spark = SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate() 从整个在使用的streamingContext的sparkContext中获取构建sparkSession的配置文件。 import spark.implicits._ // Convert RDD[String] to DataFrame val wordsDataFrame = rdd.toDF("word") // Create a temporary view wordsDataFrame.createOrReplaceTempView("words") // Do word count on DataFrame using SQL and print it val wordCountsDataFrame = spark.sql("select word, count(*) as total from words group by word") wordCountsDataFrame.show() } 21:可以异步的使用sql的查询不同进程中的streamingContext实例。请确保streamingContext设置记住足够多的数据,否则,streamingContext没有感知到任何 查询会删除之前的数据。{您还可以在来自不同线程的流式数据上定义的表上运行SQL查询(即与正在运行的StreamingContext异步)。只要确保您将StreamingContext设置为记住足够数量的流数据,以便查询可以运行。否则,不知道任何异步SQL查询的StreamingContext将在查询完成之前删除旧流数据。例如,如果您想查询最后一批,但查询可能需要5分钟才能运行,请调用streamingContext.remember(Minutes(5))(以Scala或其他语言的等效语言)}如果您想查询最后一批,但查询可能需要5分钟才能运行,请调用streamingContext.remember(Minutes(5)) 22:容错: 对于通过网络接收数据的输入流(例如Kafka,Flume,套接字等),默认持久性级别设置为将数据复制到两个节点以实现容错。 DStream的默认持久性级别使数据在内存中序列化 23:为了减少数据恢复时的lineage过于长所以需要设立checkpoint切断这个链条。 24:配置checkpoint, 失败自启动。 预写日志:spark.streaming.receiver.writeAheadLog.enable来true 24:设置背压,:设置最大接收速率 - 如果群集资源不够大,流式传输应用程序无法像接收数据一样快速处理数据,则可以通过以记录/秒为单位设置最大速率限制来限制接收器速率。查看接收器的配置参数 spark.streaming.receiver.maxRate和spark.streaming.kafka.maxRatePerPartition Direct Kafka方法。在Spark 1.5中,我们引入了一项名为backpressure的功能,无需设置此速率限制,因为Spark Streaming会自动计算出速率限制并在处理条件发生变化时动态调整速率限制。这个背压可以通过设置来启用配置参数 spark.streaming.backpressure.enabled来true。 25:sturctedStreaming:new data in the data stream === new rows append to a unabounded table. 26:写出模式: 完整:整个更新后的结果表将被写入外部存储器。由存储连接器决定如何处理整个表格的写入 追加:只有在结果表中追加的新行才会写入外部存储器。这仅适用于预期不会更改结果表中现有行的查sinks 更新:- 只有自上次触发以来在结果表中更新的行才会写入外部存储器(自Spark 2.1.1起可用)。请注意,这与完整模式不同之处在于,此模式仅输出自上次触发后更改的行。如果查询不包含聚合,则它将模式。 27:容错语义: 提供端到端的一次语义是结构化数据流设计背后的关键目标之一。为此,我们设计了结构化流式源,接收器和执行引擎,以可靠地跟踪处理的确切进程,以便通过重新启动和/或重新处理来处理任何类型的故障。假定每个流源都有偏移量(类似于Kafka偏移量或Kinesis序列号)来跟踪流中的读取位置。该引擎使用checkpoint和WAL来记录每个触发器中正在处理的数据的偏移范围。流式接收器被设计为处理重新处理的幂等。通过使用可重放源和幂等,结构化数据流可以确保端到端完全一次的语义 任何失败 28:类型推断和分区 对于即席查询,您可以通过设置spark.sql.streaming.schemaInference为true启用。 可以/data/year=2016/在/data/year=2015/存在时添加,但更改分区列(即通过创建目录/data/date=2016-04-17/)是无效的。 29:水位线: import spark.implicits._ val words = ... // streaming DataFrame of schema { timestamp: Timestamp, word: String } // Group the data by window and word and compute the count of each group val windowedCounts = words .withWatermark("timestamp", "10 minutes") .groupBy( window($"timestamp", "10 minutes", "5 minutes"), $"word") .count() 在这个例子中,我们正在定义查询的水印“timestamp”列的值,并且还定义了“10分钟”作为允许数据有多迟的阈值。 30:join操作: 流数据和静态数据集之间的join val staticDf = spark.read. ... val streamingDf = spark.readStream. ... streamingDf.join(staticDf, "type") // inner equi-join with a static DF streamingDf.join(staticDf, "type", "right_join") // right outer join with a static DF 流数据和流数据之间的join: 为了在流-流join的时候可以进行状态清理 水位线限制:事件处理逻辑可延时发生的最大时间 事件时间延迟:事件处理逻辑中的事件限制 import org.apache.spark.sql.functions.expr val impressions = spark.readStream. ... val clicks = spark.readStream. ... // Apply watermarks on event-time columns val impressionsWithWatermark = impressions.withWatermark("impressionTime", "2 hours") val clicksWithWatermark = clicks.withWatermark("clickTime", "3 hours") // Join with event-time constraints impressionsWithWatermark.join( clicksWithWatermark, expr(""" clickAdId = impressionAdId AND clickTime >= impressionTime AND clickTime <= impressionTime + interval 1 hour """) ) 2小时”的水印延迟保证引擎不会丢失延迟少于2小时的任何数据。但延迟超过2小时的数据可能会或可能不会得到处理。 31:虽然水印+事件时间约束对于内连接是可选的,但对于左外连接和右外连接,它们必须指定。这是因为为了在外连接中生成NULL结果,引擎必须知道输入行将来何时不会与任何事物匹配。因此,必须指定水印+事件时间约束以生成正确的结果。因此,具有外连接的查询与前面的广告示例非常相似,不同之处在于会有一个附加参数指定它为外连接。 impressionsWithWatermark.join( clicksWithWatermark, expr(""" clickAdId = impressionAdId AND clickTime >= impressionTime AND clickTime <= impressionTime + interval 1 hour """), joinType = "leftOuter" // can be "inner", "leftOuter", "rightOuter" ) 32:外部结果的生成 关于如何生成外部结果,需要注意几个重要特征。 外部NULL结果将生成延迟,这取决于指定的水印延迟和时间范围条件。这是因为引擎必须等待很长时间才能确保没有匹配,并且将来不会再有匹配。 在当前的微批处理引擎中,水印在微批处理结束时被提前,而下一个微批处理使用更新后的水印来清理状态并输出外部结果。由于我们仅在有新数据要处理时触发微量批处理,因此如果流中没有接收到新数据,则可能会延迟生成外部结果。 简而言之,如果连接的两个输入流中的任何一个在一段时间内都没有收到数据,则外部(两种情况下,左侧或右侧)输出可能会延迟。 支持流式查询中的连接矩阵

优秀的个人博客,低调大师

阅读笔记-Hadoop入门实践

ASF:阿里软件开发平台服务框架SIP:服务集成平台BEA:应用基础结构软件公司分布式计算的应用场景:日志分析、索引建立Threshold:是一款IOS平台的应用。随时随地帮您监控行动数据使用奘况 无论是使用无限数据方案或是有限数据方案, Threshold 可以帮助您随时掌握最新数据使用状态, 提供客制化警示设定, 还可以设立一个您专属的行动数据区域, 以确保您不会被额外收费。 对于日志内容使用任务分解的多线程处理模式来分析统计,在比较简单的情况下,使用Memcache作为计数器,结合MySQL就能完成访问控制以及统计的工作,随着日志量的增加,未来可能不能满足要求。早期的多线程多任务分解日志模式,其实是分布式计算的一个单机版。将单机的工作进行分拆,变成协同工作的集群,其实就是分布式计算框架设计所涉及的。BEA和VMWare合作采用虚拟机来构建集群,就是希望使得计算机硬件能够类似于应用程序中资源池的资源,使用者无需关心资源分配情况,从而最大化了硬件资源的使用价值。分布式计算亦是如此,任务的分配和汇总都由分布式框架的Master来抉择,使用者需要提供待分析内容给分布式计算系统作为输入,等待结果。Hadoop是Apache开源组织的一个分布式计算开源框架,在很多大型网站上都已经得到了应用,比如亚马逊、Facebook和Yahoo等等。平台日志分析位常用,特别是集成平台的日志量会很大,非常适合分布式计算的适用场景。wwh学习:what? why? how? what:Hadoop框架中最核心的设计:MapReduce和HDFS。MapReduce:任务的分解与结果的汇总。HDFS:是Hadoop分布式文件系统(Hadoop Distributed File System)的缩写,为分布式计算存储提供了底层支持。MapReduce:Map&&Reduce,此处Map不适Map容器,作“展开”来讲,就是将一个任务分解为多个任务,Reduce就是将分解后多任务处理的结果汇总起来,得出最后的分析结果。类似于多线程,多任务。任务之间的关系可以分为两种:不相关的任务,可并行执行;人物之间有相互依赖,先后顺序不能够颠倒,这类任务是无法并行处理的。一般情况下,在Map前还可能会对输入的数据有Split(分割)的过程,保证任务并行效率,在Map之后,还会有Shuffer(混合)的过程,对于提高Reduce的效率以及减少数据传输的压力有很大的帮助。 HDFS:是分布式计算的存储基石。Hadoop的分布式文件系统和其他分布式文件系统有很多的类似的特质。分布式文件系统基本的几个特点。1、对于整个集群有单一的命名空间。2、数据一致性。适合一次写入多次读取的模型,客户端在文件没有被成功创建之前无法看到文件存在。3、文件会被分割成多个文件块,每个文件块被分配存储到数据节点上,而且根据配置会由赋值文件块来保证数据的安全性。 HDFS三个重要角色:NameNode、DataNode和Client。NameNode:可以看作是分布式文件系统中的管理者,主要负责管理文件系统的命名空间、集群配置信息和存储块的赋值等。NameNode会将文件系统的Meta-data存储在内存中,这些信息主要包括了文件信息、每一个文件对应的文件块的信息和每一个文件块在DataNode的信息等。DataNode:文件存储的基本单元,它将Block存储在本地文件系统中,保存了Block的Meta-data,同时周期性地将所有存在的Block信息发送给NameNode。Client:需要获取分布式文件系统文件的应用程序。三者关系样例说明:文件写入:1、Client向NameNode发起文件写入的请求。2、NameNode根据文件大小和文件块配置情况,返回给Client它所管理部分DataNode的信息。3、Client将文件划分为多个Block,根据DataNode的地址信息,按顺序写入到每一个DataNode块中。文件读取:1、Client向NameNode发起文件读取的请求。2、NameNode返回文件存储的DataNode的信息。3、Client读取文件信息。文件Bock复制:1、NameNode发现部分文件的Block不符合最小复制数或者部分DataNode失效。2、通知DataNode相互复制Block。3、DataNode开始直接相互复制。 HDFS的几个设计特点:1、Block的放置:默认不配置。一个Block会有三个备份,一份放在NameNode指定的DataNode,另一份放在与指定DataNode非同一个Rack上的DataNode,最后一份放在与指定DataNode同一Rack上的DataNode上。备份无非就是为了数据安全,考虑同一Rack的失败情况以及不同Rack之间数据拷贝性能问题就采用这种配置方式。2、心跳检测DataNode的健康状况,如果发现问题就采取数据备份的方式来保证数据的安全性。3、数据复制(场景为DataNode失败,需要平衡DataNode的存储利用率和需要平衡DataNode数据交互压力等情况):简述,使用HDFS的balancer命令,可以配置一个Threshold来平衡每一个DataNode磁盘利用率。例如设置了Threshold为10%,那么执行balancer命令的时候,首先统计所有DataNode的磁盘利用率平均值,然后判断如果某一个DataNode的磁盘利用率超过这个平均值Threshold以上,那么将会把这个DataNode的block转移到磁盘利用率低的DataNode,这对于新节点的加入来说十分有用。4、数据校验:采用CRC32作数据校验。在文件Block写入的时候除了写入数据还会写入校验信息,在读取的时候需要校验后在读入。5、NameNode是单点:如果失败的话,任务处理信息将会记录在本地文件系统和远端的文件系统中。6、数据管道性的写入:当客户端要写入文件到DataNode上,首先客户端读取一个Block然后写入到第一个DataNode上,然后由第一个DataNode传递到备份的DataNode上,一直到所有需要写入这个Block的DataNode都成功写入,客户端才会继续开始写下一个Block。7、安全模式:在分布式文件系统启动的时候,开始的时候会有安全模式,当分布式文件系统处于安全模式的情况下,文件系统中的内容不允许修改也不允许删除,直到安全模式结束。安全模式主要是为了系统启动的时候检查各个DataNode上数据块的有效性,同时根据策略必要的复制或删除部分数据块。运行期通过命令也可以进入安全模式。生产中,系统启动时修改和删除文件也会有安全模式不允许修改的出错提示,需要等待。 MapReduce&&HDFS来看Hadoop结构:在Hadoop的系统中,会有一台Master,主要负责NameNode的工作以及JobTracker的工作。JobTracker的主要职责就是启动、跟踪和调度各个Slave的任务执行。还会有多台Slave,每一台Slave通常具有DataNode的功能并负责TaskTracker的工作。TaskTracker根据应用要求来结合本地数据执行Map任务以及Reduce任务。Moving Computation is Cheaper than Moving Data:分布式计算最重要的一个设计点。就是在分布式处理中,移动数据的代价总是高于转移计算机的代价。简单来说就是分而治之的工作,需要将数据也分而存储,本地任务处理本地数据然会归总,这样才会保证分布式计算的高效性。 why:Hadoop的有点&适用场景:1、可扩展:不论是存储的可扩展性还是计算的可扩展性都是Hadoop的设计根本。2、经济:框架可以运行在任何普通的PC上。3、可靠:分布式文件系统的备份恢复机制以及MapReduce的任务监控保证了分布式处理的可靠性。4、高效:分布式文件系统的高效数据交互实现以及MapReduce结合Local Data处理的模式,为高效处理海量的信息作了基础准备。使用场景:海量数据的分析。海量数据被分割于多个节点,然后每一个节点并行计算,将得出的结果归并到输出。同时第一阶段的输出又可以作为下一阶段计算的输入,因此可以想象到一个树状结构的分布式计算图,在不同阶段都由不同产品,同时并行和串行结合的计算也可以很好地在分布式集群的资源下得以高效的处理。 Hadoop可以单机跑,也可以配置集群跑;集群配置运行的过程:环境:机器在多而不在精,JDK必须时1.5以上的,各个机器的机器名务必不同,机器名对于MapReduce有很大的影响。部署:对于Hadoop的集群来说,可以分成两大类角色:Master和Slave,前者主要配置NameNode和JobTracker的角色,负责总管分布式数据存储以及任务的执行。一般情况下,Master和Slave不会部署在同一台机器上。 实施步骤:1、在所有的机器上都建立相同的目录,也就可以建立相同的用户,以该用户的home路径来做Hadoop的安装路径。例如在所有机器上都建立了/home/Hado2、下载Hadoop,先解压到Master上。此时Hadoop的安装路径就是/home/Hado/版本。3、解压后进入conf目录,主要修改以下文件:Hadoop-env.sh, Hadoop-site.xml, masters, slavers。Hadoop的基础配置文件时Hadoop-default.xml,看Hadoop的代码可以知道,默认建立一个Job的时候会建立Job的Config,Config首先读入Hadoop-defaulg.xml的配置,然后再读入Hadoop-site.xml的配置(这个文件初始的时候配置为空),hadoop-site.xml中主要配置你需要覆盖的Hadoop-default.xml的系统级配置,以及你需要再你的MapReduce过程中使用的自定义配置(具体一些的使用例如final等参考文档)。以下是一个简单的hadoop-site.xml的配置:<?xml version="1.0"?><?xml-stylesheet type="text/xsl" href="configuration.xsl"?> fs.default.name dfs://10.2.224.46:54310/mapred.job.tracker hdfs://10.2.224.46:54311dfs.replication 1Hadoop.tmp.dir /home/Hado/Hadoop/tmp/mapred.child.java.opts -Xmx512mdfs.block.size 5120000The default block size for new files.Hadoop-env.sh文件只需要修改一个参数: The java implementation to use.Required. export JAVA_HOME=/usr/ali/jdk1.5.0_10(jdk版本)配置你的Java路径,记住一定要1.5版本以上,免得莫名其妙出现问题Masters中配置Masters的ip或者机器名,如果是机器名那么需要在/etc/hosts中有所设置。Slaves中配置的是Slaves的ip或者机器名,同样如果是机器名需要在etc/hosts中有所设置。范例如下:Masters: 10.2.224.46Slaves: 10.2.224.40 10.2.224.39 10.2.224.38 10.2.224.37 10.2.224.36 ...4、建立Masters到每一台Slave的SSH受信证书。由于Master将会通过SSH启动所有Slave的Hadoop,所以需要建立单向或者双向证书保证命令执行时不需要再输入密码。在Master和所有的Slave机器上执行:ssh-keygen -t rsa。执行此命令的时候,看到提示只需要回车。然后就会在/root/.ssh/下面产生id_rsa.pub的证书文件,通过scp将Master机器上的这个文件拷贝到Slave上(记得修改名称),例如:scp root@masterIP:/root/.ssh/id_rsa.pub /root/.ssh/46_rsa.pub,然后执行cat /root/.ssh/46_rsa.pub >>/root/.ssh/authorized_keys,建立authorized_keys文件即可,可以打开这个文件看看,也就是rsa的公钥作为key,user@IP作为value。此时可以试验一下,从master ssh到slave已经不需要密码了。由slave反向建立也是同样。为什么要反向呢?其实如果一直都是Master启动和关闭的话那就没有必要建立反向,只是如果想在Slave也可以关闭Hadoop就需要建立反向。5、将Master上的Hadoop通过scp拷贝到每一个Slave相同的目录下,根据每一个Slave的Java_HOME的不同修改器Hadoop-env.sh.6、修改Master上/etc/profile;新增一下内容:(具体的内容根据你的安装路径修改,这步只是为了方便使用)export HADOOP_HOME=/home/Hado/Hadoop_.. export PATH=$PATH:$HADOOP_HOME/bin修改完之后,执行source /etc/profile来使其生效。7、在Master上执行Hadoop namenode -format,这是第一需要做的初始化,可以看作格式化,以后除了上面提及的删除Master上的hadoop.tmp.dir目录,否则是不需要再次执行的。8、然后执行Master上的start-all.sh,这个命令可以直接执行,因为在6中已经添加到了path路径,这个命令是启动hdfs和mapreduce两部分,当然你也可以分开单独启动hdfs和mapreduce,分别是bin目录下的start-dfs.sh和start-mapred.sh.9、检查Master的logs目录,看看Namenode日志以及JobTracker日志是否正常启动。10、检查Slave的logs目录看看Datanode日志以及TaskTracker日志是否正常。11、如果需要关闭,那么就直接执行stop-all.sh即可。 感谢:岑文初!!!

优秀的个人博客,低调大师

构建之法阅读笔记1

软件=程序+软件工程 跟单纯的编写代码去实现自己想要看到的功能不同,软件工程是一个带有工程色彩的概念,不仅是为了满足个人的简单兴趣,而是有着明确的实用目标和更庞大、完善可靠的系统结构,而且也跟更多的人发生了联系(包括程序员和用户)。从工程的角度来看,写一个满足用户需求的软件:需要从用户需求的角度出发仔细分析,结合现有技术水平和人力物力,提出并逐步完善某个idea直至产品完成。工程问题更明显的特征是讲求实用和产品的工作效能(成本、稳定性...),我们面临的大多数问题都由实际的工程问题转化而来并逐步形成专业领域的各种技术和知识。 程序是基础,也是软件大厦的基石,但是只有程序并不是一个完整的软件。需要软件工程的支撑。由于在暑假已经整体的读过一遍了,现在再次翻看有种焕然领悟的感觉,之前只是枯燥的把它读完,现在再看它 的目录结构,整个软件的架构呈现在我面前。 个人感受: 过去的我只是看到一道题有一点思路就去写,不去考虑具体的规范和流程。 看到书上写的之后,大概懂得了拿到一个问题,基本的解决思路。 先构建一个程序的基本框架,分块去写,再分别验证。

优秀的个人博客,低调大师

《View Programming Guide for iOS》阅读笔记

文档地址: 《View Programming Guide for iOS》 View and Window Architecture 视图绘制周期 UIView 类使用了请求式绘制模型来展示内容。当一个视图第一次出现在屏幕上时,系统要求它绘制自己的内容。系统截取视图内容的一个快照,并且将这个快照用于视图的可视化呈现。如果视图内容永远不改变,那么这个视图的绘图代码可能永远都不会再次调用。这个快照的图片在大部分涉及到该视图的操作中被重复使用。如果改变了视图内容,则需要通知系统视图发生了改变。之后视图会重复绘制过程并且为新的绘制结果截取一个快照。 当视图内容发生改变时,不需要直接重绘这些改变。相反,通过调用函数 setNeedsDisplay 或者 setNeedsDisplayInRect: 来使当前视图无效。这些函数会告诉系统视图的内容发生改变并且需要在下次时机到来时重绘。系统会一直等到当前的 run loop 结束后,才会开始任何绘制操作。这个延迟,给了你一个机会去废止多个视图,从当前视图层级中添加或者删除视图,隐藏视图,重设视图大小,和重定位视图。所有的这些改变稍后会再同一时间呈现。 备注:改变视图的几何结构并不会让系统自动重绘视图内容。视图的 contentMode 属性决定了视图几何结构的改变该如何解析。大部分的 content modes 只是在视图的边界中拉伸或者重定位已经存在的视图快照而不需要重新创建一个快照。 当绘制视图内容的时刻到来时,真正的绘制过程会根据视图和它的配置而有所不同。系统视图通常是实现自己的私有绘图函数来重绘内容。这些一样的系统视图通常会暴露一些接口,以便能用来配置视图实际的外观。对于自定义的 UIView 的子类,典型的应该重写视图的 drawRect: 函数,使用它来绘制视图内容。当然也存在一些其他的方法去提供视图的内容,比如直接设置内容下的图层。但是重写 drawRect: 函数是使用最多的技术。 UIKit 框架的坐标原点位于左上角,x 轴向右延伸,y 轴向下延伸。而 Core Graphics 和 OpenGL ES 的坐标系统原点则在左下角,y 轴向上延伸,x 轴向右延伸。 UIView 属性中的 frame和 center 是相对于父视图的坐标系统的。而 bounds属性相对于自身的坐标系统,故 bounds 默认的 point 位置是(0,0),大小与 frame 相同 改变视图的 transform 属性时,所有变形都是相对于视图中心点也就是 center 属性的。 在视图的 drawRect: 方法中,可以使用仿射变换来定位和确定需要绘制的元素。相比于在视图的某个地点固定一个对象的位置,相对于一个固定点(通常是(0,0))来创建每个对象是更为简单的。在绘制之前使用 transform 就能做到这点。在这种情况下,如果视图中的对象位置发生改变,只需要修改这个 transform 即可,这比在新的位置重新创建对象要快速并且花销更小。可以使用 CGContextGetCTM 函数来检索图形上下文的仿射变换矩阵,在绘制过程中也可以使用 Core Graphics 的相关函数来设置 CTM。 CTM(current transformation matrix) 是任何时候都被使用的仿射变换,当操作的是整个视图时,CTM 就是视图的 transform 属性。在 drawRect: 方法中, CTM 与当前活动的图形上下文有关 当一个视图的 transform 属性不是 identity transform 时,这个视图的 frame 属性就是未定义并且必须被忽视的。此时,你必须使用视图的 bounds 和 center 属性来获得视图的大小和位置。该视图的任何子视图的 frame 矩形依然是有效的,因为它们是基于父视图的 bounds 属性的。 一个点并不一定对应着屏幕上的一个像素 对于显式定义了 drawRect: 方法的视图来说,UIKit 负责调用这个方法。这个方法中的实现应该尽可能快地重绘视图的指定区域并且不应该做别的任何事情。不要在这里做额外的布局,也不要改变应用的数据模型。这个方法的唯一目的就是更新视图的可视内容。 自定义视图需要重写的事件处理函数有touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent: 如果使用了手势识别来处理事件,则不需要重写这些函数。如果视图不包含任何子视图或者它的尺寸不发生改变,也不需要重写 layoutSubviews 函数。最后,当视图内容在运行时发生改变,同时使用了 UIKit 或者 Core Graphics 来绘制图形,则需要重写drawRect:函数。 Windows 每个 iOS 应用程序至少包含一个窗口。窗口通常座位一个或者多个视图的空白容器。同时,应用程序也不通过展示新的窗口来改变内容。如果想要这么做,改变窗口最前面的视图来完成。 当创建窗口时,应该总是将窗口的大小设置为充满屏幕的边界。不应该为了容纳状态栏或者其他元素而减去窗口大小。无论何时,状态栏总是浮在窗口的上面的。所以应该是放入到窗口中的视图来缩减大小去适应状态栏。如果是使用视图控制器,则视图控制器应该自动处理视图大小。 窗口有等级概念,每个 UIWindow 对象都有一个可配置的 windowLevel 属性。通常不需要改变应用程序的窗口等级。新的窗口在创建时,会自动指派为正常窗口等级。高窗口等级是出现在应用程序内容之上的必要信息,比如系统状态栏和 alert 消息。虽然可以手动将窗口设置为这样的等级,但当使用到特殊接口时,通常系统会做好这些事情。举例来说,当显示隐藏状态栏,或者显示一个 alert 视图时,系统会自动创建必要的窗口去显示这些内容。 当应用程序进入到后台时,窗口改变通知并不会被传递。因为当程序进入后台时尽管窗口不在屏幕上显示了,但在应用程序环境中,窗口依然被认为是可见的。 retina 屏的 iOS 设备可以外接显示设备。 Views 使用编程方法来创建视图时,视图创建代码一般放在视图控制器的 loadView 函数中。无论是使用编程或者 nib 文件来创建视图,都可以在 viewDidLoad 函数中添加视图的配置代码。 父视图会自动 retain 子视图,所以当添加了一个子视图后,release 子视图的操作是安全的。事实上,推荐这么做,因为它能防止应用程序保持太多的视图而导致的内存泄露。记住,如果从父视图中移除了子视图后,还想继续使用子视图,必须对子视图做 retain 操作。removeFromSuperview 函数会在子视图从父视图中移除后,自动释放子视图。如果没有在下一个时间循环周期前做 retain 操作,这个视图将会被释放。 UIView的 window 属性代表当前正在显示的视图所在的窗口。对于当前在屏幕上显示的视图来说,窗口对象就是它们所在视图层次的根视图。 如果隐藏的视图是 first responder,这个视图不会自动的取消自己 first responder 的状态。以 first responder 为目标的事件依然会被传递到这个隐藏的视图。为了防止这种情况发生,应该在隐藏视图时,强制使其取消 first responder 状态。 当包含了旋转因子的视图做矩形转换时,看如下的图: uiview_convert_rotated.jpg 如果一个视图的 transform 属性不是 identity transform,那么它的 frame 和 autoresizing 行为结果都是未定义的。 视图在初始化过程之前调用 UIView 类方法 layerClass,并且使用返回的类来创建 layer 对象。此外,视图总是会指定自己本身作为 layer 对象的代理。在这点上,视图拥有着图层,并且视图和图层之前的关系必须不能改变。也就是说,不能指定相同的视图作为另一个图层对象的代理。改变这种所属关系或者代理关系,都可能会导致视图绘制的错误,并且应用程序存在潜在的 crash 问题。 通过创建 UIView 的子类,重写 layerClass 类函数可以改变创建图层时的默认的 CALayer 类。 自定义的 layer 不接收事件,也不参与到 responder chain 中,但是它们绘制自身,并且根据 Core Animation 的规则,在他们的父视图或者父图层中响应大小变化。 CGRectGetMidX(),CGRectGetMidY() 两个函数可以分别得到一个 frame 的中心点的 x 坐标和 y 坐标。 CALayer 的属性 position 就是中心点,和 UIView 的属性 center 效果相同。 自定义的视图类,如果是通过代码来创建,则需要重写 initWithFrame: 初始化函数。而若是从 nib 文件中加载,则需要重写 initWithCoder: 函数。注意:nib 加载的视图并不会调用 initWithFrame: 函数。 视图默认的行为是一次只响应一个 touch。如果用户按下了第二个手指,系统会忽视这个 touch 事件并且不会将它报告给视图。如果希望在视图的事件处理函数中跟踪多手指手势,需要设置视图的 multipleTouchEnabled 属性为 YES 来使多点触摸事件生效。 转自:WebFrogs 的博客

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册