├── CategoryInspector.zip ├── README.md ├── Run Loop Observer 图解 ├── 1.png ├── RunLoopAction │ ├── RunLoopAction.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcuserdata │ │ │ │ └── vedon.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── vedon.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ ├── RunLoopAction.xcscheme │ │ │ └── xcschememanagement.plist │ └── RunLoopAction │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Demo.png │ │ ├── Info.plist │ │ ├── ViewController.h │ │ ├── ViewController.m │ │ └── main.m ├── Screen Shot 2015-09-22 at 11.28.41 PM.png ├── runloop.key └── runloop.md ├── UITableViewOpt ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.png ├── 17.png ├── 18.png ├── 19.png ├── 2.png ├── 20.png ├── 21.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png └── UITableView_Opt.md ├── UITableViewOpt2 ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png ├── TableView_DrawRect.zip └── UITableViewOpt2.md ├── 初识XCode 打包脚本 └── build.sh ├── 图片加载 ├── Screen Shot 2015-06-26 at 2.42.23 PM.png ├── Screen Shot 2015-06-30 at 10.56.26 AM.png └── iOS 图片加载.md └── 调试 ├── SymbolicBreakPoint.md ├── SymbolicBreakpoint1-300x69.png └── SymbolicBreakpoint2-300x120.png /CategoryInspector.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/CategoryInspector.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS-tech 2 | 3 | 欢迎大家交流: 4 | QQ:403264272 5 | email:vedon.fu@gmail.com , 403264272@qq.com 6 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/Run Loop Observer 图解/1.png -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4E7728DB1D50851E00FE1D79 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E7728DA1D50851E00FE1D79 /* main.m */; }; 11 | 4E7728DE1D50851E00FE1D79 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E7728DD1D50851E00FE1D79 /* AppDelegate.m */; }; 12 | 4E7728E11D50851E00FE1D79 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E7728E01D50851E00FE1D79 /* ViewController.m */; }; 13 | 4E7728E41D50851E00FE1D79 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4E7728E21D50851E00FE1D79 /* Main.storyboard */; }; 14 | 4E7728E61D50851E00FE1D79 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4E7728E51D50851E00FE1D79 /* Assets.xcassets */; }; 15 | 4E7728E91D50851E00FE1D79 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4E7728E71D50851E00FE1D79 /* LaunchScreen.storyboard */; }; 16 | 4E7728F11D509ABB00FE1D79 /* Demo.png in Resources */ = {isa = PBXBuildFile; fileRef = 4E7728F01D509ABB00FE1D79 /* Demo.png */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 4E7728D61D50851E00FE1D79 /* RunLoopAction.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RunLoopAction.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 4E7728DA1D50851E00FE1D79 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 22 | 4E7728DC1D50851E00FE1D79 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 23 | 4E7728DD1D50851E00FE1D79 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 24 | 4E7728DF1D50851E00FE1D79 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 25 | 4E7728E01D50851E00FE1D79 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 26 | 4E7728E31D50851E00FE1D79 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 27 | 4E7728E51D50851E00FE1D79 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | 4E7728E81D50851E00FE1D79 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | 4E7728EA1D50851E00FE1D79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 4E7728F01D509ABB00FE1D79 /* Demo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Demo.png; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 4E7728D31D50851E00FE1D79 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 4E7728CD1D50851E00FE1D79 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 4E7728D81D50851E00FE1D79 /* RunLoopAction */, 48 | 4E7728D71D50851E00FE1D79 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 4E7728D71D50851E00FE1D79 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 4E7728D61D50851E00FE1D79 /* RunLoopAction.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 4E7728D81D50851E00FE1D79 /* RunLoopAction */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 4E7728F01D509ABB00FE1D79 /* Demo.png */, 64 | 4E7728DC1D50851E00FE1D79 /* AppDelegate.h */, 65 | 4E7728DD1D50851E00FE1D79 /* AppDelegate.m */, 66 | 4E7728DF1D50851E00FE1D79 /* ViewController.h */, 67 | 4E7728E01D50851E00FE1D79 /* ViewController.m */, 68 | 4E7728E21D50851E00FE1D79 /* Main.storyboard */, 69 | 4E7728E51D50851E00FE1D79 /* Assets.xcassets */, 70 | 4E7728E71D50851E00FE1D79 /* LaunchScreen.storyboard */, 71 | 4E7728EA1D50851E00FE1D79 /* Info.plist */, 72 | 4E7728D91D50851E00FE1D79 /* Supporting Files */, 73 | ); 74 | path = RunLoopAction; 75 | sourceTree = ""; 76 | }; 77 | 4E7728D91D50851E00FE1D79 /* Supporting Files */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 4E7728DA1D50851E00FE1D79 /* main.m */, 81 | ); 82 | name = "Supporting Files"; 83 | sourceTree = ""; 84 | }; 85 | /* End PBXGroup section */ 86 | 87 | /* Begin PBXNativeTarget section */ 88 | 4E7728D51D50851E00FE1D79 /* RunLoopAction */ = { 89 | isa = PBXNativeTarget; 90 | buildConfigurationList = 4E7728ED1D50851E00FE1D79 /* Build configuration list for PBXNativeTarget "RunLoopAction" */; 91 | buildPhases = ( 92 | 4E7728D21D50851E00FE1D79 /* Sources */, 93 | 4E7728D31D50851E00FE1D79 /* Frameworks */, 94 | 4E7728D41D50851E00FE1D79 /* Resources */, 95 | ); 96 | buildRules = ( 97 | ); 98 | dependencies = ( 99 | ); 100 | name = RunLoopAction; 101 | productName = RunLoopAction; 102 | productReference = 4E7728D61D50851E00FE1D79 /* RunLoopAction.app */; 103 | productType = "com.apple.product-type.application"; 104 | }; 105 | /* End PBXNativeTarget section */ 106 | 107 | /* Begin PBXProject section */ 108 | 4E7728CE1D50851E00FE1D79 /* Project object */ = { 109 | isa = PBXProject; 110 | attributes = { 111 | LastUpgradeCheck = 0730; 112 | ORGANIZATIONNAME = vedon; 113 | TargetAttributes = { 114 | 4E7728D51D50851E00FE1D79 = { 115 | CreatedOnToolsVersion = 7.3.1; 116 | DevelopmentTeam = 8QH5EM85GC; 117 | }; 118 | }; 119 | }; 120 | buildConfigurationList = 4E7728D11D50851E00FE1D79 /* Build configuration list for PBXProject "RunLoopAction" */; 121 | compatibilityVersion = "Xcode 3.2"; 122 | developmentRegion = English; 123 | hasScannedForEncodings = 0; 124 | knownRegions = ( 125 | en, 126 | Base, 127 | ); 128 | mainGroup = 4E7728CD1D50851E00FE1D79; 129 | productRefGroup = 4E7728D71D50851E00FE1D79 /* Products */; 130 | projectDirPath = ""; 131 | projectRoot = ""; 132 | targets = ( 133 | 4E7728D51D50851E00FE1D79 /* RunLoopAction */, 134 | ); 135 | }; 136 | /* End PBXProject section */ 137 | 138 | /* Begin PBXResourcesBuildPhase section */ 139 | 4E7728D41D50851E00FE1D79 /* Resources */ = { 140 | isa = PBXResourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 4E7728F11D509ABB00FE1D79 /* Demo.png in Resources */, 144 | 4E7728E91D50851E00FE1D79 /* LaunchScreen.storyboard in Resources */, 145 | 4E7728E61D50851E00FE1D79 /* Assets.xcassets in Resources */, 146 | 4E7728E41D50851E00FE1D79 /* Main.storyboard in Resources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXResourcesBuildPhase section */ 151 | 152 | /* Begin PBXSourcesBuildPhase section */ 153 | 4E7728D21D50851E00FE1D79 /* Sources */ = { 154 | isa = PBXSourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 4E7728E11D50851E00FE1D79 /* ViewController.m in Sources */, 158 | 4E7728DE1D50851E00FE1D79 /* AppDelegate.m in Sources */, 159 | 4E7728DB1D50851E00FE1D79 /* main.m in Sources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXSourcesBuildPhase section */ 164 | 165 | /* Begin PBXVariantGroup section */ 166 | 4E7728E21D50851E00FE1D79 /* Main.storyboard */ = { 167 | isa = PBXVariantGroup; 168 | children = ( 169 | 4E7728E31D50851E00FE1D79 /* Base */, 170 | ); 171 | name = Main.storyboard; 172 | sourceTree = ""; 173 | }; 174 | 4E7728E71D50851E00FE1D79 /* LaunchScreen.storyboard */ = { 175 | isa = PBXVariantGroup; 176 | children = ( 177 | 4E7728E81D50851E00FE1D79 /* Base */, 178 | ); 179 | name = LaunchScreen.storyboard; 180 | sourceTree = ""; 181 | }; 182 | /* End PBXVariantGroup section */ 183 | 184 | /* Begin XCBuildConfiguration section */ 185 | 4E7728EB1D50851E00FE1D79 /* Debug */ = { 186 | isa = XCBuildConfiguration; 187 | buildSettings = { 188 | ALWAYS_SEARCH_USER_PATHS = NO; 189 | CLANG_ANALYZER_NONNULL = YES; 190 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 191 | CLANG_CXX_LIBRARY = "libc++"; 192 | CLANG_ENABLE_MODULES = YES; 193 | CLANG_ENABLE_OBJC_ARC = YES; 194 | CLANG_WARN_BOOL_CONVERSION = YES; 195 | CLANG_WARN_CONSTANT_CONVERSION = YES; 196 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 197 | CLANG_WARN_EMPTY_BODY = YES; 198 | CLANG_WARN_ENUM_CONVERSION = YES; 199 | CLANG_WARN_INT_CONVERSION = YES; 200 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 201 | CLANG_WARN_UNREACHABLE_CODE = YES; 202 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 203 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 204 | COPY_PHASE_STRIP = NO; 205 | DEBUG_INFORMATION_FORMAT = dwarf; 206 | ENABLE_STRICT_OBJC_MSGSEND = YES; 207 | ENABLE_TESTABILITY = YES; 208 | GCC_C_LANGUAGE_STANDARD = gnu99; 209 | GCC_DYNAMIC_NO_PIC = NO; 210 | GCC_NO_COMMON_BLOCKS = YES; 211 | GCC_OPTIMIZATION_LEVEL = 0; 212 | GCC_PREPROCESSOR_DEFINITIONS = ( 213 | "DEBUG=1", 214 | "$(inherited)", 215 | ); 216 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 217 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 218 | GCC_WARN_UNDECLARED_SELECTOR = YES; 219 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 220 | GCC_WARN_UNUSED_FUNCTION = YES; 221 | GCC_WARN_UNUSED_VARIABLE = YES; 222 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 223 | MTL_ENABLE_DEBUG_INFO = YES; 224 | ONLY_ACTIVE_ARCH = YES; 225 | SDKROOT = iphoneos; 226 | TARGETED_DEVICE_FAMILY = "1,2"; 227 | }; 228 | name = Debug; 229 | }; 230 | 4E7728EC1D50851E00FE1D79 /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_ANALYZER_NONNULL = YES; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 236 | CLANG_CXX_LIBRARY = "libc++"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_EMPTY_BODY = YES; 243 | CLANG_WARN_ENUM_CONVERSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_UNREACHABLE_CODE = YES; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 251 | ENABLE_NS_ASSERTIONS = NO; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | GCC_C_LANGUAGE_STANDARD = gnu99; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 257 | GCC_WARN_UNDECLARED_SELECTOR = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 259 | GCC_WARN_UNUSED_FUNCTION = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 262 | MTL_ENABLE_DEBUG_INFO = NO; 263 | SDKROOT = iphoneos; 264 | TARGETED_DEVICE_FAMILY = "1,2"; 265 | VALIDATE_PRODUCT = YES; 266 | }; 267 | name = Release; 268 | }; 269 | 4E7728EE1D50851E00FE1D79 /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 273 | CODE_SIGN_IDENTITY = "iPhone Developer"; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 275 | INFOPLIST_FILE = RunLoopAction/Info.plist; 276 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 277 | PRODUCT_BUNDLE_IDENTIFIER = vedon.RunLoopAction; 278 | PRODUCT_NAME = "$(TARGET_NAME)"; 279 | PROVISIONING_PROFILE = ""; 280 | }; 281 | name = Debug; 282 | }; 283 | 4E7728EF1D50851E00FE1D79 /* Release */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 287 | CODE_SIGN_IDENTITY = "iPhone Developer"; 288 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 289 | INFOPLIST_FILE = RunLoopAction/Info.plist; 290 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 291 | PRODUCT_BUNDLE_IDENTIFIER = vedon.RunLoopAction; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | PROVISIONING_PROFILE = ""; 294 | }; 295 | name = Release; 296 | }; 297 | /* End XCBuildConfiguration section */ 298 | 299 | /* Begin XCConfigurationList section */ 300 | 4E7728D11D50851E00FE1D79 /* Build configuration list for PBXProject "RunLoopAction" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | 4E7728EB1D50851E00FE1D79 /* Debug */, 304 | 4E7728EC1D50851E00FE1D79 /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | 4E7728ED1D50851E00FE1D79 /* Build configuration list for PBXNativeTarget "RunLoopAction" */ = { 310 | isa = XCConfigurationList; 311 | buildConfigurations = ( 312 | 4E7728EE1D50851E00FE1D79 /* Debug */, 313 | 4E7728EF1D50851E00FE1D79 /* Release */, 314 | ); 315 | defaultConfigurationIsVisible = 0; 316 | }; 317 | /* End XCConfigurationList section */ 318 | }; 319 | rootObject = 4E7728CE1D50851E00FE1D79 /* Project object */; 320 | } 321 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction.xcodeproj/project.xcworkspace/xcuserdata/vedon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/Run Loop Observer 图解/RunLoopAction/RunLoopAction.xcodeproj/project.xcworkspace/xcuserdata/vedon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction.xcodeproj/xcuserdata/vedon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | 20 | 26 | 27 | 29 | 31 | 32 | 33 | 34 | 35 | 43 | 44 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction.xcodeproj/xcuserdata/vedon.xcuserdatad/xcschemes/RunLoopAction.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction.xcodeproj/xcuserdata/vedon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RunLoopAction.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4E7728D51D50851E00FE1D79 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // RunLoopAction 4 | // 5 | // Created by vedon on 2/8/2016. 6 | // Copyright © 2016 vedon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // RunLoopAction 4 | // 5 | // Created by vedon on 2/8/2016. 6 | // Copyright © 2016 vedon. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | 27 | 28 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/Demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/Run Loop Observer 图解/RunLoopAction/RunLoopAction/Demo.png -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // RunLoopAction 4 | // 5 | // Created by vedon on 2/8/2016. 6 | // Copyright © 2016 vedon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // RunLoopAction 4 | // 5 | // Created by vedon on 2/8/2016. 6 | // Copyright © 2016 vedon. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | @property (strong,nonatomic) dispatch_queue_t uiQueue; 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | 20 | /** 21 | * Observer 22 | */ 23 | [self createRunLoopObserverWithObserverType:kCFRunLoopEntry]; 24 | [self createRunLoopObserverWithObserverType:kCFRunLoopBeforeTimers]; 25 | [self createRunLoopObserverWithObserverType:kCFRunLoopBeforeSources]; 26 | [self createRunLoopObserverWithObserverType:kCFRunLoopBeforeWaiting]; 27 | [self createRunLoopObserverWithObserverType:kCFRunLoopAfterWaiting]; 28 | 29 | 30 | /** 31 | * Runloop block 32 | * 33 | */ 34 | CFRunLoopRef mainRunloop = CFRunLoopGetMain(); 35 | CFRunLoopPerformBlock(mainRunloop, kCFRunLoopCommonModes, ^{ 36 | 37 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ CFRunLoopPerformBlock"); 38 | 39 | }); 40 | 41 | /** 42 | * Source 0 event 43 | * 44 | */ 45 | [self performSelector:@selector(source0Event) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; 46 | 47 | 48 | /** 49 | * Source 1 event 50 | */ 51 | [self addButtonToMainView]; 52 | 53 | 54 | 55 | //Exec order : FIFO 56 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 57 | NSLog(@"__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ GCD dispatch_after"); 58 | }); 59 | 60 | 61 | dispatch_async(dispatch_get_main_queue(), ^{ 62 | NSLog(@"__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ GCD dispatch_async"); 63 | }); 64 | 65 | 66 | /** 67 | * Timer 68 | */ 69 | [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(timerAction) userInfo:nil repeats:NO]; 70 | 71 | 72 | /** 73 | * Dispatch_once will be executed before the runloop run 74 | */ 75 | static dispatch_once_t onceToken; 76 | dispatch_once(&onceToken, ^{ 77 | NSLog(@"dispatch_once"); 78 | }); 79 | 80 | 81 | 82 | 83 | //********************************************************** 84 | NSLog(@"addImageViewToMainView"); 85 | self.uiQueue = dispatch_queue_create("com.uiQueue", NULL); 86 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 87 | [self addImageViewToMainView]; 88 | }); 89 | 90 | 91 | 92 | 93 | 94 | // Do any additional setup after loading the view, typically from a nib. 95 | } 96 | 97 | - (void)didReceiveMemoryWarning { 98 | [super didReceiveMemoryWarning]; 99 | // Dispose of any resources that can be recreated. 100 | } 101 | 102 | - (void)source0Event 103 | { 104 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ Source0"); 105 | } 106 | 107 | - (void)timerAction 108 | { 109 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ NSTimer"); 110 | } 111 | 112 | 113 | - (void)addButtonToMainView 114 | { 115 | UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 80, 50)]; 116 | [button addTarget:self action:@selector(source1Event) forControlEvents:UIControlEventTouchUpInside]; 117 | button.backgroundColor = [UIColor lightGrayColor]; 118 | 119 | [self.view addSubview:button]; 120 | 121 | } 122 | 123 | - (void)source1Event 124 | { 125 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ Source1"); 126 | } 127 | 128 | - (void)createRunLoopObserverWithObserverType:(CFOptionFlags)flag 129 | { 130 | CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 131 | CFStringRef runLoopMode = kCFRunLoopDefaultMode; 132 | CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler 133 | (kCFAllocatorDefault, flag, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _activity) { 134 | 135 | switch (_activity) { 136 | case kCFRunLoopEntry: 137 | { 138 | NSLog(@"即将进入Loop"); 139 | } 140 | break; 141 | case kCFRunLoopBeforeTimers: 142 | { 143 | NSLog(@"即将处理 Timer"); 144 | break; 145 | } 146 | case kCFRunLoopBeforeSources: 147 | NSLog(@"即将处理 Source"); 148 | break; 149 | case kCFRunLoopBeforeWaiting: 150 | NSLog(@"即将进入休眠"); 151 | ; 152 | break; 153 | case kCFRunLoopAfterWaiting: 154 | NSLog(@"刚从休眠中唤醒"); 155 | break; 156 | case kCFRunLoopExit: 157 | NSLog(@"即将退出Loop"); 158 | break; 159 | default: 160 | break; 161 | } 162 | }); 163 | 164 | CFRunLoopAddObserver(runLoop, observer, runLoopMode); 165 | } 166 | 167 | 168 | - (void)addImageViewToMainView 169 | { 170 | UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; 171 | dispatch_async(self.uiQueue, ^{ 172 | imageView1.image = [UIImage imageNamed:@"Demo"]; 173 | }); 174 | 175 | 176 | [self.view addSubview:imageView1]; 177 | } 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/RunLoopAction/RunLoopAction/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RunLoopAction 4 | // 5 | // Created by vedon on 2/8/2016. 6 | // Copyright © 2016 vedon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Run Loop Observer 图解/Screen Shot 2015-09-22 at 11.28.41 PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/Run Loop Observer 图解/Screen Shot 2015-09-22 at 11.28.41 PM.png -------------------------------------------------------------------------------- /Run Loop Observer 图解/runloop.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/Run Loop Observer 图解/runloop.key -------------------------------------------------------------------------------- /Run Loop Observer 图解/runloop.md: -------------------------------------------------------------------------------- 1 | ##RunLoopRun 源码解析 2 | 3 | 了解一下runloop ,对于实际的开发上是大有裨益的。[源码](https://github.com/vedon/CF/blob/master/CFRunLoop.c)在github 上都有,大家可以自行查阅。 4 | 5 | Runloop 的主要工作流程,简单来说就是下图。 6 | ![](./Screen Shot 2015-09-22 at 11.28.41 PM.png) 7 | 8 | 从日常开发里面,断点的堆栈里面看得最多的就是下面函数的了,它们是什么? 9 | 10 | 11 | ``` 12 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(); 13 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(); 14 | static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(); 15 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(); 16 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(); 17 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(); 18 | 19 | ``` 20 | 21 | *** 22 | 23 | ####首先看第一个函数,它让外部观察Runloop 的运行状态成为可能,你可以注册一个observer ,通过它观察runloop 的行为。 24 | 25 | ``` 26 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__( 27 | CFRunLoopObserverCallBack func, 28 | CFRunLoopObserverRef observer, 29 | CFRunLoopActivity activity, 30 | void *info); 31 | 32 | ``` 33 | 34 | CoreAnimation就有这么一个观察者,下面的堆栈是从Instruments 里面看到的。当一个UIView 的DrawRect被调用时,这个UIView 就被标记为待处理,并被提交到一个全局的容器去。当Oberver监听的事件到来时,回调执行函数中会遍历所有待处理的UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。可以打一个符号断点,就可以看到整个完整的堆栈信息了。 35 | 36 | ``` 37 | layout_and_display_if_needed 38 | ``` 39 | ####这里插一个题外话:CoreAnimation 不是只负责动画吗? 40 | 41 | Core animation 除了负责动画的创建和执行,还有绘制功能。它是QuzrtzCore 的一部分。QuartzCore 是什么? 42 | 43 | QuartzCore (与openGl 交互) 负责图形处理和视频图像处理的能力。除此之外,CoreGraphic 也是基于它的绘制引擎实现的。实际上,真正负责绘制的都是QuartzCore。一般我们重写的DrawRect 方法,是创建了数据模型(位图数据),QuzrtzCore 把数据模型显示到屏幕上。 44 | 45 | [Core Graphic & Core Animation](http://stackoverflow.com/questions/9248530/confusion-regarding-quartz2d-core-graphics-core-animation-core-images) 46 | 47 | [Core Graphics Framework Reference](https://developer.apple.com/library/ios/documentation/CoreGraphics/Reference/CoreGraphics_Framework/index.html) 48 | 49 | [Core Animation Reference](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/CoreAnimation_framework/) 50 | 51 | [Quartz Core Framework Reference](https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/QuartzCoreRefCollection/) 52 | 53 | 54 | ![](./1.png) 55 | 56 | ``` 57 | CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) () 58 | CA::Transaction::commit() () 59 | CA::Context::commit_transaction(CA::Transaction*) () 60 | CA::Layer::layout_and_display_if_needed(); 61 | CA::Layer::layout_if_needed(); 62 | 63 | 这里会触发UIView 的layoutSubViews 或者是 layer 的layoutSubLayers 64 | 65 | CA::Layer::display_if_needed(); 66 | 67 | 这里会触发UIView 的drawRect 或者是 layer 的display 68 | ``` 69 | 70 | ####看到这样的堆栈,大家会想到什么呢? 71 | 72 | 首先在iOS 上几乎所有的东西都是通过Core Animation 绘制出来的。哎哟,是不是所有与UI相关的更新都会经过CA::Layer XXXXX ,这样的话,在Debug 模式下 hook 住其中一个函数,然后做一些判断UI 是否在主线程的判断。这样至少在开发过程中能发现一些问题。(Am i right ? FixMe) 73 | 74 | 除此之外,autoreleasePool 也是通过观察者的方式来实现的。 75 | 76 | ``` 77 | 1,即将进入runloop 的时候,push 一个pool,此时观察者的优先级是最高的,保证在其他回调前创建好pool. 78 | 79 | 2,准备进入睡眠的时候 pop 一个pool。此时观察者的优先级是最低的,保证在所有回调结束之后执行。 80 | 81 | ``` 82 | 83 | **ps: 可以下载源码编译一下,打断点,你会有求知若渴的感觉!** 84 | 85 | *** 86 | 87 | ####CFRunLoopPerformBlock() 添加一个block 到指定runloop中。 88 | 89 | ``` 90 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__( 91 | void (^block)(void)); 92 | 93 | ``` 94 | 95 | *** 96 | 97 | ####GCD中dispatch到main queue的block会被dispatch到main loop执行。GCD 会创建多个没有runloop的线程,当任务执行完的时候,把上下文切换到主线程,继续执行?(FixMe) 98 | 99 | ``` 100 | static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__( 101 | void *msg); 102 | ``` 103 | *** 104 | 105 | ####NSObject PerformSelector:AfterDelay: ,NSTimer 会通过这个函数回调。 106 | 107 | ``` 108 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__( 109 | CFRunLoopTimerCallBack func, 110 | CFRunLoopTimerRef timer, 111 | void *info); 112 | ``` 113 | 114 | *** 115 | 116 | ####Source0 处理App 内部事件,e.g. 以下的方法就回创建一个Source 0 事件。 117 | 118 | ``` 119 | - (void)performSelector:(SEL)aSelector 120 | onThread:(NSThread *)thr 121 | withObject:(id)arg 122 | waitUntilDone:(BOOL)wait 123 | modes:(NSArray *)array; 124 | ``` 125 | 126 | ``` 127 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__( 128 | void (*perform)(void *), 129 | void *info); 130 | ``` 131 | 132 | *** 133 | ####Source1 处理内核事件。 e.g. NSMach port(通过内核和其他线程通信,接收、分发系统事件) 134 | 135 | ``` 136 | static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__( 137 | void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info), 138 | mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply, 139 | void (*perform)(void *), 140 | void *info); 141 | ``` 142 | 143 | 144 | 除了__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ 观察者回调函数外,其他5 个函数,我们来看一下它们的执行顺序。?[直接看结果](#result) 145 | 146 | ``` 147 | // 148 | // ViewController.m 149 | // RunLoopAction 150 | // 151 | // Created by vedon on 2/8/2016. 152 | // Copyright © 2016 vedon. All rights reserved. 153 | // 154 | 155 | #import "ViewController.h" 156 | 157 | @interface ViewController () 158 | 159 | @end 160 | 161 | @implementation ViewController 162 | 163 | - (void)viewDidLoad { 164 | [super viewDidLoad]; 165 | 166 | /** 167 | * Observer 168 | */ 169 | [self createRunLoopObserverWithObserverType:kCFRunLoopEntry]; 170 | [self createRunLoopObserverWithObserverType:kCFRunLoopBeforeTimers]; 171 | [self createRunLoopObserverWithObserverType:kCFRunLoopBeforeSources]; 172 | [self createRunLoopObserverWithObserverType:kCFRunLoopBeforeWaiting]; 173 | [self createRunLoopObserverWithObserverType:kCFRunLoopAfterWaiting]; 174 | 175 | 176 | /** 177 | * Runloop block 178 | * 179 | */ 180 | CFRunLoopRef mainRunloop = CFRunLoopGetMain(); 181 | CFRunLoopPerformBlock(mainRunloop, kCFRunLoopCommonModes, ^{ 182 | 183 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ CFRunLoopPerformBlock"); 184 | 185 | }); 186 | 187 | /** 188 | * Source 0 event 189 | * 190 | */ 191 | [self performSelector:@selector(source0Event) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; 192 | 193 | 194 | /** 195 | * Source 1 event 196 | */ 197 | [self addButtonToMainView]; 198 | 199 | 200 | 201 | //Exec order : FIFO 202 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 203 | NSLog(@"__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ GCD dispatch_after"); 204 | }); 205 | 206 | 207 | dispatch_async(dispatch_get_main_queue(), ^{ 208 | NSLog(@"__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ GCD dispatch_async"); 209 | }); 210 | 211 | 212 | /** 213 | * Timer 214 | */ 215 | [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(timerAction) userInfo:nil repeats:NO]; 216 | 217 | 218 | /** 219 | * Dispatch_once will be executed before the runloop run 220 | */ 221 | static dispatch_once_t onceToken; 222 | dispatch_once(&onceToken, ^{ 223 | NSLog(@"dispatch_once"); 224 | }); 225 | // Do any additional setup after loading the view, typically from a nib. 226 | } 227 | 228 | - (void)didReceiveMemoryWarning { 229 | [super didReceiveMemoryWarning]; 230 | // Dispose of any resources that can be recreated. 231 | } 232 | 233 | - (void)source0Event 234 | { 235 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ Source0"); 236 | } 237 | 238 | - (void)timerAction 239 | { 240 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ NSTimer"); 241 | } 242 | 243 | - (void)addButtonToMainView 244 | { 245 | UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 80, 50)]; 246 | [button addTarget:self action:@selector(source1Event) forControlEvents:UIControlEventTouchUpInside]; 247 | button.backgroundColor = [UIColor lightGrayColor]; 248 | 249 | [self.view addSubview:button]; 250 | 251 | } 252 | 253 | - (void)source1Event 254 | { 255 | NSLog(@"__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ Source1"); 256 | } 257 | 258 | 259 | - (void)createRunLoopObserverWithObserverType:(CFOptionFlags)flag 260 | { 261 | CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 262 | CFStringRef runLoopMode = kCFRunLoopDefaultMode; 263 | CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler 264 | (kCFAllocatorDefault, flag, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _activity) { 265 | 266 | switch (_activity) { 267 | case kCFRunLoopEntry: 268 | { 269 | NSLog(@"即将进入Loop"); 270 | } 271 | break; 272 | case kCFRunLoopBeforeTimers: 273 | { 274 | NSLog(@"即将处理 Timer"); 275 | break; 276 | } 277 | case kCFRunLoopBeforeSources: 278 | NSLog(@"即将处理 Source"); 279 | break; 280 | case kCFRunLoopBeforeWaiting: 281 | NSLog(@"即将进入休眠"); 282 | ; 283 | break; 284 | case kCFRunLoopAfterWaiting: 285 | NSLog(@"刚从休眠中唤醒"); 286 | break; 287 | case kCFRunLoopExit: 288 | NSLog(@"即将退出Loop"); 289 | break; 290 | default: 291 | break; 292 | } 293 | }); 294 | CFRunLoopAddObserver(runLoop, observer, runLoopMode); 295 | } 296 | 297 | @end 298 | 299 | ``` 300 | 301 | 运行结果 302 | 303 | ``` 304 | 2016-08-02 16:36:21.129 RunLoopAction[4232:342167] dispatch_once 305 | 2016-08-02 16:36:21.140 RunLoopAction[4232:342167] 即将进入Loop 306 | 2016-08-02 16:36:21.141 RunLoopAction[4232:342167] 即将处理 Timer 307 | 2016-08-02 16:36:21.141 RunLoopAction[4232:342167] 即将处理 Source 308 | 2016-08-02 16:36:21.142 RunLoopAction[4232:342167] __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ CFRunLoopPerformBlock 309 | 2016-08-02 16:36:21.142 RunLoopAction[4232:342167] __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ Source0 310 | 2016-08-02 16:36:21.143 RunLoopAction[4232:342167] __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ NSTimer 311 | 2016-08-02 16:36:21.143 RunLoopAction[4232:342167] 即将处理 Timer 312 | 2016-08-02 16:36:21.144 RunLoopAction[4232:342167] 即将处理 Source 313 | 2016-08-02 16:36:21.144 RunLoopAction[4232:342167] __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ GCD dispatch_after 314 | 2016-08-02 16:36:21.145 RunLoopAction[4232:342167] __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ GCD dispatch_async 315 | 2016-08-02 16:36:21.146 RunLoopAction[4232:342167] 即将处理 Timer 316 | 2016-08-02 16:36:21.146 RunLoopAction[4232:342167] 即将处理 Source 317 | 2016-08-02 16:36:21.147 RunLoopAction[4232:342167] 即将处理 Timer 318 | 2016-08-02 16:36:21.147 RunLoopAction[4232:342167] 即将处理 Source 319 | 2016-08-02 16:36:21.148 RunLoopAction[4232:342167] 即将处理 Timer 320 | 2016-08-02 16:36:21.148 RunLoopAction[4232:342167] 即将处理 Source 321 | 2016-08-02 16:36:21.148 RunLoopAction[4232:342167] 即将处理 Timer 322 | 2016-08-02 16:36:21.149 RunLoopAction[4232:342167] 即将处理 Source 323 | 2016-08-02 16:36:21.149 RunLoopAction[4232:342167] 即将进入休眠 324 | ``` 325 | 326 | 从运行log 可以看出,dispatch_once 在runloop 还没有进入的时候已经执行了。接着执行的顺序就是: 327 | 328 | ``` 329 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ 330 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ 331 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ 332 | 333 | 334 | __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ 335 | ``` 336 | 执行顺序和源码表述的一样。 337 | 338 | ``` 339 | __CFRunLoopDoBlocks 340 | __CFRunLoopDoSources0 341 | __CFRunLoopDoTimers 342 | 343 | 344 | __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ 345 | ``` 346 | 347 | 348 | ``` 349 | static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { 350 | 351 | //1.判断一下RunLoop 是否停止了,停止则启动runloop. 352 | 353 | //2.启动一个source 来检测runloop 是否超时,超时则唤醒runloop. 354 | 355 | //标志是否有需要在GCD 执行的操作 356 | Boolean didDispatchPortLastTime = true; 357 | int32_t retVal = 0; 358 | do { 359 | //3.通知 Observers: RunLoop 即将触发 Timer 回调 360 | if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 361 | 362 | //4.通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。 363 | if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); 364 | 365 | //执行哪些加入到当前runloop的block. e.g :CFRunLoopPerformBlock(<#CFRunLoopRef rl#>, <#CFTypeRef mode#>, <#^(void)block#>) 366 | __CFRunLoopDoBlocks(rl, rlm); 367 | 368 | 369 | //5.RunLoop 触发 Source0 (非port) 回调。 370 | Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); 371 | if (sourceHandledThisLoop) { 372 | //6.执行哪些加入到当前runloop的block. 373 | __CFRunLoopDoBlocks(rl, rlm); 374 | } 375 | 376 | Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); 377 | 378 | //7.判断当前的port 不为空,而且没有触发过dispatchPort 里面的事件,如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。 379 | 380 | if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { 381 | msg = (mach_msg_header_t *)msg_buffer; 382 | if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { 383 | goto handle_msg; 384 | } 385 | } 386 | didDispatchPortLastTime = false; 387 | 388 | //8.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。 389 | if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); 390 | 391 | 392 | /* 393 | 9.调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒 394 | 1)一个基于 port 的Source 的事件。 395 | 2)一个 Timer 到时间了 396 | 3) RunLoop 自身的超时时间到了 397 | 4)被其他什么调用者手动唤醒 398 | */ 399 | __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); 400 | 401 | 402 | //10. 通知 Observers: RunLoop 的线程刚刚被唤醒了。 403 | __CFRunLoopUnsetSleeping(rl); 404 | if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); 405 | 406 | handle_msg:; 407 | 408 | //11.处理消息 409 | __CFRunLoopSetIgnoreWakeUps(rl); 410 | if (MACH_PORT_NULL == livePort) { 411 | CFRUNLOOP_WAKEUP_FOR_NOTHING(); 412 | // handle nothing 413 | } else if (livePort == rl->_wakeUpPort) { 414 | CFRUNLOOP_WAKEUP_FOR_WAKEUP(); 415 | // do nothing on Mac OS 416 | } 417 | else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { 418 | 419 | //如果一个 Timer 到时间了,触发这个Timer的回调。 420 | CFRUNLOOP_WAKEUP_FOR_TIMER(); 421 | if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { 422 | __CFArmNextTimerInMode(rlm, rl); 423 | } 424 | } 425 | else if (livePort == dispatchPort) { 426 | 427 | // 如果有dispatch到main_queue的block,执行block。 428 | CFRUNLOOP_WAKEUP_FOR_DISPATCH(); 429 | __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 430 | _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL); 431 | sourceHandledThisLoop = true; 432 | didDispatchPortLastTime = true; 433 | } else { 434 | CFRUNLOOP_WAKEUP_FOR_SOURCE(); 435 | 436 | //如果一个 Source1发出事件了,处理这个事件 437 | CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); 438 | if (rls) { 439 | sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; 440 | } 441 | 442 | } 443 | //12.执行哪些加入到当前runloop的block. 444 | __CFRunLoopDoBlocks(rl, rlm); 445 | if (sourceHandledThisLoop && stopAfterHandle) { 446 | 447 | //进入loop时参数说处理完事件就返回 448 | retVal = kCFRunLoopRunHandledSource; 449 | } else if (timeout_context->termTSR < mach_absolute_time()) { 450 | 451 | //Runloop 超时le 452 | retVal = kCFRunLoopRunTimedOut; 453 | } else if (__CFRunLoopIsStopped(rl)) { 454 | //被外部干掉了 455 | retVal = kCFRunLoopRunStopped; 456 | } else if (rlm->_stopped) { 457 | //被外部干掉了 458 | retVal = kCFRunLoopRunStopped; 459 | } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { 460 | 461 | // source/timer/observer一个都没有了 462 | retVal = kCFRunLoopRunFinished; 463 | } 464 | 465 | } while (0 == retVal); 466 | 467 | if (timeout_timer) { 468 | dispatch_source_cancel(timeout_timer); 469 | dispatch_release(timeout_timer); 470 | } else { 471 | free(timeout_context); 472 | } 473 | 474 | //13.通知观察者,runloop 要退出了。 475 | return retVal; 476 | } 477 | ``` 478 | 479 | 480 | ##Example 481 | 482 | 简单从一个点击时间开始分析。 483 | 484 | > *系统注册了一个基于port 的source ,回调函数为__IOHIDEventSystemClientQueueCallback。通过测试,无论你点击屏幕,甚至是你晃动手机,都是触发这个回调。 485 | 486 | > *经查资料知道这首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。 487 | 488 | > *_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。 489 | 490 | 下面是一些点击button 的调用log 491 | 点击一个button 492 | 493 | ``` 494 | __CFRunLoopDoObservers (刚从休眠中唤醒) 495 | __CFRunLoopDoSource1 496 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ 497 | __IOHIDEventSystemClientQueueCallback 498 | __CFRunLoopDoBlocks 499 | __CFRunLoopDoObservers (即将处理 Timer) 500 | __CFRunLoopDoObservers (即将处理 Source) 501 | __CFRunLoopDoBlocks 502 | __CFRunLoopDoSource0 (__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__) 503 | _UIApplicationHandleEventQueue 504 | __CFRunLoopDoBlocks 505 | __CFRunLoopDoBlocks 506 | ``` 507 | 508 | 接着说说GCD 509 | 实际上 RunLoop 底层也会用到 GCD 的东西,比如 RunLoop 是用 dispatch_source_t 实现的 Timer。但同时 GCD 提供的某些接口也用到了 RunLoop, 例如 dispatch_async()。 510 | 511 | 调用的log: 512 | 513 | ``` 514 | __CFRunLoopDoObservers (即将处理 Timer) 515 | __CFRunLoopDoObservers (即将处理 Source) 516 | __CFRunLoopDoBlocks 517 | __CFRunLoopDoObservers (即将进入休眠) 518 | __CFRunLoopDoObservers (刚从休眠中唤醒) 519 | __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ 520 | ``` 521 | 522 | 当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。 523 | -------------------------------------------------------------------------------- /UITableViewOpt/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/1.png -------------------------------------------------------------------------------- /UITableViewOpt/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/10.png -------------------------------------------------------------------------------- /UITableViewOpt/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/11.png -------------------------------------------------------------------------------- /UITableViewOpt/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/12.png -------------------------------------------------------------------------------- /UITableViewOpt/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/13.png -------------------------------------------------------------------------------- /UITableViewOpt/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/14.png -------------------------------------------------------------------------------- /UITableViewOpt/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/15.png -------------------------------------------------------------------------------- /UITableViewOpt/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/16.png -------------------------------------------------------------------------------- /UITableViewOpt/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/17.png -------------------------------------------------------------------------------- /UITableViewOpt/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/18.png -------------------------------------------------------------------------------- /UITableViewOpt/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/19.png -------------------------------------------------------------------------------- /UITableViewOpt/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/2.png -------------------------------------------------------------------------------- /UITableViewOpt/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/20.png -------------------------------------------------------------------------------- /UITableViewOpt/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/21.png -------------------------------------------------------------------------------- /UITableViewOpt/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/3.png -------------------------------------------------------------------------------- /UITableViewOpt/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/4.png -------------------------------------------------------------------------------- /UITableViewOpt/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/5.png -------------------------------------------------------------------------------- /UITableViewOpt/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/6.png -------------------------------------------------------------------------------- /UITableViewOpt/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/7.png -------------------------------------------------------------------------------- /UITableViewOpt/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/8.png -------------------------------------------------------------------------------- /UITableViewOpt/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt/9.png -------------------------------------------------------------------------------- /UITableViewOpt/UITableView_Opt.md: -------------------------------------------------------------------------------- 1 | #UITableview Tip 2 | 3 | 4 | 5 | ## 1) delegate 和 dataSource 回调时机 6 | 7 | 在做自媒体人卡片的时候,发现在 iOS 7 和 iOS 8 之后的UITableView 的事件回调机制很不一样,于是踩坑了! 8 | 关心的主要回调事件主要有: 9 | ![回调事件](./2.png) 10 | 11 | ####这几个函数调用的时机主要有: 12 | >1) 调用addSubView方法将UITableView 加入到父视图。 13 | > 14 | >2)TableView 调用 reloadData 方法。 15 | > 16 | >3)滚动TableView。 17 | 18 | 19 | ####在 iOS 7 上,把TableView 加入到父视图实行的方法时序如下: 20 | 21 | ![iOS7 logic](./1.png) 22 | 23 | 24 | 可以发现,系统会在获取每行的cell前,前置拿到所有行的高度。这样,系统才能计算出滚动轴的总高度。而之后在获取cell对象时,并不会重新调用获取该行高度的方法。 25 | 26 | ####在 iOS 8 上,把TableView 加入到父视图实行的方法时序如下: 27 | 28 | ![iOS7 logic](./3.png) 29 | 30 | 31 | 在iOS 7.0上,可以发现,系统会在获取每行的cell前,前置拿到所有行的高度。之后,在获取cell对象时,并不会重新调用获取该行高度的方法。而在iOS 8.0上,在获取cell对象后,会重新调用获取该行高度的方法。这样,就意味着可以在真正产生出cell对象之后,再提供高度。 32 | 33 | **这里也是我猜坑的地方,在iOS 7 计算高度的地方,由于在 7 上前置拿到所有cell 高度之后,后面不会再调用heightForRowAtIndexPath 这个方法,导致高度计算出错,解决的办法就是在返回高度的地方强制SetNeedLayout才能获取对的高度!** 34 | 35 | 36 | 在这里,聪明的杀破狼队员们应该发现:什么,在iOS 8 之后,调用cellForRowAtIndexPath 的地方会再调用一次heightForRowAtIndexPath ! 每次都计算高度,很浪费呀。 37 | 38 | 左边是iOS 8 ,右边是iOS 7 39 | ![iOS7 logic](./4.png) 40 | 41 | 正是因为这个区别,有时候我们发现同样的代码,在 iPhone5 (iOS 7 系统) 比 iPhone 5s (iOS 8 系统)还流畅!WTF 42 | 43 | 解决的办法是:**Cache it !** 别忘了,横屏和竖屏的高度是不一样的,要分开缓存!不然,测试兄弟又提Bugs 了。 44 | 45 | ***插个tip:不要cellForRowAtIndexPath:方法中绑定数据,因为在此时cell还没有显示。可以使用UITableView的delegate中的tableView:willDisplayCell:forRowAtIndexPath:方法。*** 46 | 47 | ####heightForRowAtIndexPath 调用次数,在iOS 7 也受estimatedHeightForRowAtIndexPath 函数的影响。 48 | ![iOS7 logic](./5.png) 49 | 50 | **有图有真相!** 51 | 52 | 当使用estimatedHeightForRowAtIndexPath 的时候,系统只会调用heightForRowAtIndexPath。 53 | ![iOS7 logic](./6.png) 54 | 55 | 当没有使用estimatedHeightForRowAtIndexPath 的时候,系统只会调用heightForRowAtIndexPath。 56 | 57 | ![iOS7 logic](./7.png) 58 | 59 | **通过结果可以看到,加载时会先通过 estimatedHeightForRowAtIndexPath 处理全部数据,此时只需要返回一个粗略的高度,待到Cell加载时才去调用原有的真实高度的回调方法,且只会处理屏幕范围内的行,这样当数据非常多时,会显著的提升加载的性能** 60 | 61 | ##2)让高度计算不再成为负担 62 | 63 | 64 | 基于这些TableView 的特点,在设置cell 的时候,是否可以提前异步计算每一个cell 的高度,然后缓存起来呢? 65 | 这不是废话吗?肯定可以啦! 66 | 67 | 68 | 有三种类型的cell ,如下图 69 | 70 | ![iOS7 logic](./8.png) 71 | 72 | 假设注册了三种类型的cell,第一,第二种cell 的高度是固定的。第三种cell 的高度根据底部label 的高度变化而变化。聪明的你一定会想到,根据数据的类型,返回对应的高度就可以啦。对于第三种类型,计算文字的高度再返回。So far so good! 73 | 74 | 75 | 代码大概长这个样子: 76 | 77 | ![iOS7 logic](./9.png) 78 | 79 | 80 | 过几天,UI 觉得不好看,改一下UI 把。 81 | 82 | ![iOS7 logic](./10.png) 83 | 84 | 一次两次的改动,修改是很快的。当业务越来越复杂的时候,cell 的类型越来越多的时候,一些layout 的代码也变得很难看。我的解决办法是:通过为每一种cell 配置一个专门用于保存配置信息的类。LayoutoutAttribute.通过layoutAttribute ,可以知道类型1 的cell ,它主要就是图片的上下左右边距的调整,通过设置layoutAttribute ,返回对应UIEdgeInsets 。这样就可以了,不用再修改layoutSubView 里面的代码。多爽! 85 | 86 | ![iOS7 logic](./11.png) 87 | 88 | 89 | 结合Cell的数据,生成对应的LayoutAttribute 就可以确定对应的Cell的 高度!简单一句就是:TableView的数据有了,高度也就有了。这里可以异步的计算高度,具体怎么做,你懂的啦! 90 | 91 | 通过使用estimatedHeightForRowAtIndexPath 和 高度预计算,可以大大提升TableView 的滚动效率!还觉得不够!继续往下看! 92 | 93 | ##3) 图片加载时机优化 94 | 95 | 按照用户的操作习惯,当用户快速滑动列表的时候,在没有滚动到目标位置之前,tableView 中的图片是不需要下载的。通过观察ScrollView的回调可以轻松做到这一点。先来看看ScrollView的一些回调和特性。 96 | 97 | ![iOS7 logic](./12.png) 98 | 99 | 按照一般的操作习惯,回调函数时序会有三种: 100 | >1) 用户滑动列表一次 101 | > 102 | >![iOS7 logic](./13.png) 103 | > 104 | >2)用户两次次滑动列表 105 | > 106 | >![iOS7 logic](./14.png) 107 | > 108 | >3)用户两次滑动列表,第二次停止加速滑动(就是用手指把scrollView 停住) 109 | >![iOS7 logic](./15.png) 110 | > 111 | 112 | 通过观察第二和第三种情况,发现新的 dragging 如果有加速度,那么 willBeginDecelerating 会再一次被调用,然后才是 didEndDecelerating;如果没有加速度,虽然 willBeginDecelerating 不会被调用,但前一次留下的 didEndDecelerating 会被调用,所以连续快速滚动一个 scroll view 时,delegate 方法被调用的顺序就是2和3 两种情况交替出现。 113 | 114 | **刚开始拖动的时候,dragging 为 YES,decelerating 为 NO;decelerate 过程中,dragging 和 decelerating 都为 YES;decelerate 未结束时开始下一次拖动,dragging 和 decelerating 依然都为 YES。所以无法简单通过 table view 的 dragging 和 decelerating 判断是在用户拖动还是减速过程。** 115 | 116 | **通过添加一个变量如 userDragging,在 willBeginDragging 中设为 YES,didEndDragging 中设为 NO。加载图片的时机可以是userDragging 为YES 和 tableView的decelerating 属性为YES.** 117 | 118 | 遇到一些大图下载的需求,可以通过这种方法来提升TableView 的性能。 119 | 120 | ![iOS7 logic](./16.png) 121 | 122 | ##4) 数据内存优化 123 | 124 | 仿效于CoreData 属性,当一些没有用到的数据,不需要加载到内存。这里可以使用数据虚拟化,简单来说就是。 125 | ![iOS7 logic](./17.png) 126 | 127 | 在内存中,不全是真的数据类型,item placeholder是一个自定义的数据类型,item 是真实的数据类型。展示的数据就相当于一个窗口,会上下浮动。不再窗口内的数据,可以直接释放掉,节省内存,在窗口内的数据则从本地DB 中读取。 128 | 129 | **在这里聪明的你,应该会想到:提前异步加载窗口内的数据,当cell 要使用的时候,直接可以在内存获取。**这里可以结合ScrollView 的特性实现数据预加载。 130 | 131 | ![iOS7 logic](./18.png) 132 | 133 | targetContentOffset 是tableView 减速到停止的地方。通过判断当前数据的位置,可以实现数据预加载! 134 | 135 | ##5) 对CPU 和 GPU 都应该公平点! 136 | 优化UITableView 的流畅性,不单单是time profile 看手机CPU 的利用率就可以的。别忘了,它还有个兄弟:GPU。什么情况下会触发GPU,让它工作呢?先来看看两者的主要工作。 137 | 138 | ###CPU 139 | > * 对象的创建与销毁 140 | > * 布局计算(frame,bound ,etc) 141 | > * 文本计算 ([NSAttributedString boundingRectWithSize:options:context:] 计算文字高度) 142 | > * 文本渲染 (UITextView ,UILabel, CoreText) 143 | > * 图片的解码 (这个不用说啦,大家都懂) 144 | > * 图像的绘制 (DrawRect) 145 | 146 | 147 | ###GPU 148 | > * 纹理的渲染 (所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture) 149 | > * 视图的混合 (多层次的view 或者layer 叠加) 150 | > * 图形的生成(圆角、阴影、遮罩。最典型的图片圆角问题!) 151 | 152 | 针对自媒体人卡片的情况,我们来分析一下。卡片类型中有一种比较复杂的情况是这样的。 153 | ![iOS7 logic](./19.png) 154 | 155 | 在这里可以看出一个严重的问题:会存在多层View的叠加。看看上面提到的,多层view 的叠加,如果处理不好,处了会增加CPU 的负担。还会增加GPU 的负担!Holy shit,目前项目中还没有处理这种情况。 156 | 157 | 通过Instrument观察到,CPU 的利用率不高,但是GPU的利用率就飙升! 我们要对CPU 和 GPU 公平一点,把一些可以分担给CPU 的工作都分出去。 158 | 处理方法:把叠加的Views ,绘制成一张图片!利用CPU 来绘制图像。 159 | 160 | 这里有个关键点:你需要清楚地知道哪部分渲染需要使用GPU,哪部分可以使用CPU,以此保持平衡。 161 | 162 | 163 | ##6)关于像素的问题 164 | 之前同事国星发现了一个UILabel 的问题,当UILabel的坐标存在很小的小数时,Label 的字体会模糊。例如:CGRectMake(21.0023, 60, 200, 50),这样就会出现字体模糊。 165 | 166 | 深挖一下, 其实就是iOS 的sub pixel anti aliasing. AKA ,像素的抗锯齿。什么情况下会出现这种不必要的子像素抗锯齿操作呢?最常发生的情况是通过代码计算而变成浮点值的视图坐标,已经踩过坑!或者是一些不正确的图片资源,这些图片的大小不是对齐到屏幕的物理像素上的(例如,你有一张在Retina显示屏上的大小为60*61的图片,而不是60*60的)。 167 | 168 | 像素模糊问题,靠人眼是很难看出区别的。不是每个人都像我一样,有像素眼。这个时候,可以用Instruments 来检测。非常简单,只需要在Core Animation里面开启Color misaligned Images就可以了。 169 | 170 | ![iOS7 logic](./21.png) 171 | 172 | ##然后我们看个例子: 173 | 174 | 175 | ![iOS7 logic](./20.png) 176 | 177 | 黄色部分就是图片没有对齐的问题,品红色就是像素没对齐。 178 | 图片中的设备是iPhone 5c ,坐标为21和21.5 没有像素对齐的问题,因为0.5 就是一个像素嘛。当座位改为21.2,这个时候就悲剧了,屏幕为了可以显示这个坐标的字体,就发生了sub pixel antialiased. 严重的,就会出现字体模糊的问题。 179 | 180 | 再来看看下面的图片,第一张图片的大小是200 * 200 pixels ,也就是程序里面的CGSizeMake(100, 100)。图片的大小刚好可以对齐imageView的大小。另外一张图,90 * 90 pixels ,也就是程序里面的CGSizeMake(45, 45).当imageView 设为CGSizeMake(90, 90),就会有图片对齐的问题。 181 | 182 | 以上两种问题都会消耗GPU!影响帧率! 183 | 184 | 以上两种问题都会消耗GPU!影响帧率! 185 | 186 | 以上两种问题都会消耗GPU!影响帧率! 187 | 188 | ###解决的办法 189 | 像素对齐的问题,只需要简单的使用ceilf, floorf和CGRectIntegral方法来对坐标做四舍五入处理。 190 | 191 | 图片对齐的问题,就需要重绘图片了。 192 | 193 | ```objc 194 | - (UIImage *)cropEqualScaleImageToSize:(CGSize)size { 195 | CGFloat scale = [UIScreen mainScreen].scale; 196 | 197 | UIGraphicsBeginImageContextWithOptions(size, NO, scale); 198 | 199 | CGSize aspectFitSize = CGSizeZero; 200 | if (self.size.width != 0 && self.size.height != 0) { 201 | CGFloat rateWidth = size.width / self.size.width; 202 | CGFloat rateHeight = size.height / self.size.height; 203 | 204 | CGFloat rate = MIN(rateHeight, rateWidth); 205 | aspectFitSize = CGSizeMake(self.size.width * rate, self.size.height * rate); 206 | } 207 | 208 | [self drawInRect:CGRectMake(0, 0, aspectFitSize.width, aspectFitSize.height)]; 209 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 210 | UIGraphicsEndImageContext(); 211 | 212 | return image; 213 | } 214 | ``` 215 | 216 | 关于字体的相关链接: 217 | 218 | [字体渲染背后不得不说的故事](http://www.jianshu.com/p/8414b96549e3) 219 | 220 | [Sub pixels antialiased](https://bjango.com/articles/subpixeltext/) 221 | 222 | -------------------------------------------------------------------------------- /UITableViewOpt2/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/1.png -------------------------------------------------------------------------------- /UITableViewOpt2/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/2.png -------------------------------------------------------------------------------- /UITableViewOpt2/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/3.png -------------------------------------------------------------------------------- /UITableViewOpt2/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/4.png -------------------------------------------------------------------------------- /UITableViewOpt2/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/5.png -------------------------------------------------------------------------------- /UITableViewOpt2/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/6.png -------------------------------------------------------------------------------- /UITableViewOpt2/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/7.png -------------------------------------------------------------------------------- /UITableViewOpt2/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/8.png -------------------------------------------------------------------------------- /UITableViewOpt2/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/9.png -------------------------------------------------------------------------------- /UITableViewOpt2/TableView_DrawRect.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/UITableViewOpt2/TableView_DrawRect.zip -------------------------------------------------------------------------------- /UITableViewOpt2/UITableViewOpt2.md: -------------------------------------------------------------------------------- 1 | #UITableview Tip 2 | 3 | ##1)CPU消耗的时间低,不代表tableView 滚动的帧率就会高 4 | 5 | NSAttributedString 对帧率的影响,一开始认为它对帧率的影响应该是很小的,但是经过测试发现->Holy shit, 原来它是这么屌的。 6 | 7 | Demo 中的代码主要差别就是这几行代码。 8 | 9 | ![](./1.png) 10 | 11 | ![](./2.png) 12 | 13 | 14 | 使用NSAttributedString 来对label 的字体设值,帧率会下降到30帧左右。而通过label 的Text设值。帧率会保持在58左右。来看看CPU Usage,看看可以查出有什么函数特别耗时。 15 | 16 | ![](./3.png) 17 | 18 | 再来对比一下label 的setText 19 | 20 | ![](./4.png) 21 | 22 | 从图片中可以看到setText 的耗时比Attribute Text的耗时更长,但是帧率却赢它几条街。 23 | 24 | 从CPU 利用率的曲线上看,可以发现问题。 25 | 26 | ![](./5.png) 27 | 28 | CPU 总的利用率太高了。NSAttributeString 在CPU 上进行渲染了。把整个CPU 的利用率提上去了,因此影响了滚动的帧率。定位到问题了,那么解决办法是? 29 | 30 | #->Google it 31 | 32 | 对于文字和图片的渲染,用得最多的就是UIImageView 和 UILabel ,替换的方案可以用CALayer 和CATextlayer. 33 | 34 | 好处: 35 | 36 | > * 快到没朋友 37 | > * 图片和文字的渲染可以在16ms 内完成。 38 | > 39 | 40 | 缺点: 41 | 42 | > * 一些复杂的自定义的文字很难配置 43 | > * 使用起来比较啰嗦,需要配置很多属性。 44 | > 45 | 46 | QuartzCore 配合CoreText 可以满足大部分需求了,至少我的已经满足了。LOL,下面列出最常用到的几个类。 47 | 48 | ###QuartzCore 49 | > * CALayer 50 | > * CATextLayer 51 | > * CAGradientLayer (设计最喜欢说,这里加个蒙板吧,要渐变的哦,ps, 你知道会影响效率吗,你用Instrument 的Core animation看看?). 52 | 53 | ###CoreText 54 | > * NSAttributeString 55 | > * NSMutableAttributeString 56 | 57 | 58 | 有了他们,UI 的需求应该能满足了。说了这么久,来看一下他们的对比吧。👇 59 | 60 | ![](./6.png) 61 | 62 | 从图里面可以看到,UILabel 搭配 NSAttributeString ,在我的使用场景下,它们不应该在一起。勉强没幸福。CATextLayer 和NSAttributeString 更搭!在快速滚动下,帧率还可以保持在58 帧左右。实在是屌! 63 | 64 | 可以通过简单的改一下UILabel 的layer 的类(重写类方法 layerClass,UILabel 默认的layers是_UILabelLayer,它是CALayer的子类),以利用CATextLayer 的高性能绘制。 65 | 66 | ``` 67 | - (id)initWithFrame:(CGRect)frame 68 | { 69 | if (self = [super initWithFrame:frame]) { 70 | [self setUp]; 71 | } 72 | return self; 73 | } 74 | 75 | + (Class)layerClass 76 | { 77 | return [CATextLayer class]; 78 | } 79 | 80 | - (CATextLayer *)textLayer 81 | { 82 | return (CATextLayer *)self.layer; 83 | } 84 | 85 | - (void)setUp 86 | { 87 | [self textLayer].alignmentMode = kCAAlignmentJustified; 88 | [self textLayer].wrapped = YES; 89 | [self.layer display]; 90 | } 91 | 92 | - (void)setText:(NSString *)text 93 | { 94 | super.text = text; 95 | [self textLayer].string = text; 96 | } 97 | 98 | - (void)setAttributedText:(NSAttributedString *)attributedText 99 | { 100 | super.attributedText = attributedText; 101 | [self textLayer].string = attributedText; 102 | } 103 | 104 | - (void)setTextColor:(UIColor *)textColor 105 | { 106 | super.textColor = textColor; 107 | [self textLayer].foregroundColor = textColor.CGColor; 108 | } 109 | 110 | - (void)setFont:(UIFont *)font 111 | { 112 | super.font = font; 113 | CFStringRef fontName = (__bridge CFStringRef)font.fontName; 114 | CGFontRef fontRef = CGFontCreateWithFontName(fontName); 115 | [self textLayer].font = fontRef; 116 | [self textLayer].fontSize = font.pointSize; 117 | 118 | CGFontRelease(fontRef); 119 | } 120 | ``` 121 | 122 | 使用CATextLayer 的UILabel 和纯CATextLayer 的效率对比如下: 123 | ![](./9.png) 124 | 125 | 可以看到纯CATextLayer 的渲染是比CATextLayer 的UILabel快的。 126 | 127 | 128 | 129 | ##2)使用 Dispatch_once 为那些经常要创建的对象服务。 130 | 131 | > * UIFont ,Screen scale , UIColor ,NSMutableParagraphStyle.etc .尽量使用dispatch_once 来初始化,然后保存起来重复使用。YYAsyncLayer 就是这么干的。 132 | > ![](./7.png) 133 | > * 使用strptime 而不是 NSDateFomatter 。为什么!因为它快呀 134 | 135 | ``` 136 | //#include 137 | 138 | time_t t; 139 | struct tm tm; 140 | strptime([iso8601String cStringUsingEncoding:NSUTF8StringEncoding], "%Y-%m-%dT%H:%M:%S%z", &tm); 141 | tm.tm_isdst = -1; 142 | t = mktime(&tm); 143 | [NSDate dateWithTimeIntervalSince1970:t + [[NSTimeZone localTimeZone] secondsFromGMT]]; 144 | ``` 145 | > * 使用NSDictionary 里面的Key ,一般用[NSString stringWithFormat ...]。大多数情况下是没有效率的问题的,但是如果用在循环里面,那就会有效率的问题。使用test2 的方法,效率基本上是stringWithFormat 的3倍。 146 | > ![](./8.png) 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /初识XCode 打包脚本/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ############################# 4 | # 5 | # 修改脚本参数 6 | # 7 | ############################# 8 | 9 | project_path= ./ #项目文件的路径,将编译脚本放在项目目录中可以使用相对路径 10 | adhoc_macro_setting='${inherited} ADHOC=1' #adhoc模式下的宏定义 11 | adhoc_profile="GSUser_AdHoc" #adhoc模式下使用的provision文件 12 | development_macro_setting='${inherited} DEVELOPMENT=1' #develoment模式下的宏定义 13 | develoment_profile="GSUser-Dev-Profile" #development模式下使用的provision文件 14 | appstore_macro_setting='${inherited} APP_STORE=1' #appstore模式下的宏定义 15 | appstore_profile="GSUser-Pro-Profile" #appstore模式下使用的provision文件 16 | 17 | app_name="GasStation" #应用名字 18 | scheme="GasStation" #工程文件中应用的scheme名字(一般和target名字相同) 19 | workspace="GasStation.xcworkspace" #工程文件的名字 20 | configuration 21 | configFileName 22 | logFile=GSUserBuild.log 23 | ############################ 24 | 25 | 26 | 27 | ############################# 28 | # 29 | # 根据证书名获取证书的UDID 30 | # 31 | ############################# 32 | get_provisioning_id() 33 | { 34 | provisionpath="$HOME/Library/MobileDevice/Provisioning Profiles" 35 | provisions=$( ls "$provisionpath" ) 36 | provisioningid="" 37 | for prv in $provisions 38 | do 39 | result=$(security cms -D -i "$provisionpath/$prv" | grep -i 'Name' -A 2 | grep -i "$1") 40 | 41 | if [ "$result" != "" ] 42 | then 43 | provisioningid=${prv%%.*} 44 | echo "Found Provisioning Profile For $1 : "$prv 45 | break 46 | fi 47 | done 48 | 49 | if [ "$provisioningid" == "" ] 50 | then 51 | errormsg="$errormsg\n fail to find $1" 52 | echo "error NO Provisioning Profile For $1 was found~" 53 | 54 | fi 55 | } 56 | 57 | 58 | 59 | read -p "请输入打包类型(AdHoc或Develop或Productioin)" mode 60 | 61 | if [ $mode = "a" ] ; then 62 | # 内测发布模式 63 | echo "内测发布模式" 64 | macro_setting="$adhoc_macro_setting" 65 | profile="$adhoc_profile" 66 | configuration="AdHoc" 67 | configFileName="Config_GSUser_AdHoc.xcconfig" 68 | 69 | elif [ $mode = "d" ] ; then 70 | # 开发模式 71 | echo "开发模式" 72 | macro_setting="$development_macro_setting" 73 | profile="$develoment_profile" 74 | configuration="Debug" 75 | configFileName="Config_GSUser_Debug.xcconfig" 76 | 77 | 78 | elif [ $mode = "p" ] ; then 79 | #APP STORE模式 80 | read -p "你是否确认已正确地修改了APP的版本号与Build Number?(y/n)" confirm 81 | if [ $confirm != "y" ] ; then 82 | echo "请正确修改后重试" 83 | exit 1 84 | fi 85 | 86 | macro_setting="$appstore_macro_setting" 87 | profile="$appstore_profile" 88 | configuration="Release" 89 | configFileName="Config_GSUser_Release.xcconfig" 90 | 91 | else 92 | echo "模式无法识别!" 93 | exit 1 94 | 95 | fi 96 | 97 | # 删除log 文件 98 | rm -f build.log 99 | echo "remove build.log file" 100 | 101 | rm -f fir.log 102 | echo "remove fir.log file" 103 | 104 | cd "$project_path" 105 | 106 | #删除之前的打包文件 107 | export_path=GSUserExports 108 | rm -rf $export_path 109 | mkdir $export_path 110 | 111 | #archive and export ipa 112 | archive_path=$export_path/"$app_name".xcarchive 113 | app_path=$export_path/"$app_name".ipa 114 | 115 | 116 | get_provisioning_id "$profile" 117 | PROVISIONING_PROFILE=$provisioningid 118 | 119 | 120 | # 修改configFileName 下的Xcode 配置文件中的CUSTOM_PROVISIONING_PROFILE 的值为Provision 的UDID 121 | sed -i "" "s/^CUSTOM_PROVISIONING_PROFILE.*$/CUSTOM_PROVISIONING_PROFILE = $PROVISIONING_PROFILE/g" Config/"$configFileName" 122 | 123 | 124 | echo "workspace : $workspace" >> $logFile 125 | echo "scheme : $scheme" >> $logFile 126 | echo "configuration : $configuration" >> $logFile 127 | echo "profile : $profile" >> $logFile 128 | echo "profile UDID: $provisioningid" >> $logFile 129 | 130 | echo "cleanning ....." 131 | xcodebuild clean -workspace "$workspace" -scheme "$scheme" -configuration "$configuration" 132 | 133 | 134 | # 执行大包 135 | echo "building ....." 136 | xcodebuild -scheme "$scheme" -configuration "$configuration" archive -archivePath $archive_path -workspace "$workspace" GCC_PREPROCESSOR_DEFINITIONS="${macro_setting}" >> $logFile 137 | 138 | echo "Archive ....." 139 | xcodebuild -exportArchive -exportFormat ipa -archivePath $archive_path -exportPath $app_path -exportProvisioningProfile $profile >> $logFile 140 | 141 | 142 | ############################# 143 | # 144 | # 发布到分发平台 145 | # 146 | ############################# 147 | 148 | # 发布到fir 149 | echo "firing ....." 150 | fir_user_key="f7ba0bfcc7e87cd0c59b0951f15d247b" 151 | fir p $app_path -T "$fir_user_key" >> $logFile 152 | 153 | #发布到pre.im 154 | pre_im_user_key="xxx" 155 | curl -F "file=@${app_path}" -F "user_key=${pre_im_user_key}" -F "update_notify=1" -F "app_resign=1" http://pre.im/api/v1/app/upload >> $logFile 156 | 157 | mv $logFile $export_path 158 | echo "Done! 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄" 159 | # 发布到蒲公英 160 | # uKey="xxxxx" 161 | # apiKey="xxxx" 162 | # password="xxxx" 163 | # curl -F "file=@${app_path}" -F "uKey=${uKey}" -F "_api_key=${apiKey}" -F "publishRange=2" -F "password=${password}" http://www.pgyer.com/apiv1/app/upload -------------------------------------------------------------------------------- /图片加载/Screen Shot 2015-06-26 at 2.42.23 PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/图片加载/Screen Shot 2015-06-26 at 2.42.23 PM.png -------------------------------------------------------------------------------- /图片加载/Screen Shot 2015-06-30 at 10.56.26 AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/图片加载/Screen Shot 2015-06-30 at 10.56.26 AM.png -------------------------------------------------------------------------------- /图片加载/iOS 图片加载.md: -------------------------------------------------------------------------------- 1 | #iOS 图片加载优化 2 | 3 | 首先系统从磁盘加载一张图片,并使用UIImageView 来显示,需要经过以下步骤: 4 | 5 | > * 从磁盘拷贝图片数据到内核缓冲区 6 | > 7 | > * 从内核缓冲区复制数据到用户空间 8 | > 9 | > * 把图像数据赋值给UIImageView ,如果图片数据为没有解码的png / jepg 格式,会先解码为[位图数据](http://www.raywenderlich.com/69855/image-processing-in-ios-part-1-raw-bitmap-modification) 10 | > 11 | > * 图像渲染。 12 | 13 | 14 | ##从磁盘拷贝图片数据到内核缓冲区 15 | 16 | 这一项是由iOS 内核决定的,应用程序无法干扰,也不需要处理。 17 | 18 | 19 | ##从内核缓冲区复制数据到用户空间 20 | 现在操作系统将操作系统的核心代码与应用程序,用户运行的服务程序分离。任何作为内核的一部分运行代码(如驱动程序),都需要在```内核空间```运行。运行在内核空间的代码拥有特殊的权限,如可以直接的写链接在计算机上的硬件设备等。 21 | 22 | 用户处理的标准的应用程序代码要在用户空间中运行。```用户空间```中的运行的软件无法直接访问硬件,如我们在磁盘读取图片的时候,因此,用户空间会向内核发起请求,让内核代表应用程序执行任务。并且内核把图片数据读入内核缓冲区,用户再从内核缓冲区读取数据复制到用户内存空间,这里有一次内存拷贝的时间消耗,并且读取后整个文件数据就已经存在于用户内存中,占用了进程的内存空间。 23 | 24 | 使用```内存映射```的技术,可以避免数据从内核缓冲区复制到用户空间的性能消耗。内存映射是用mmap把文件映射到用户空间里的虚拟内存,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,相当于已经把整个文件放入内存,但在真正使用到这些数据前却不会消耗物理内存,也不会有读写磁盘的操作,只有真正使用这些数据时,也就是图像准备渲染在屏幕上时,虚拟内存管理系统VMS才根据缺页加载的机制从磁盘加载对应的数据块到物理内存 25 | 26 | [FastImageCache](https://github.com/path/FastImageCache#what-fast-image-cache-does)使用了内存映射技术等,加快了图片的加载。 27 | 28 | **FastImageCache** 由Path工程师开发,可以帮app更快更有效率的储存并检索图片的工具。它在图片处理上做了些什么呢? 29 | > * 同一风格或者尺寸的图片存储在一起 30 | > * 将图片解码后的bitmap,缓存在本地 31 | > * 运用LRU 算法来管理图片的缓存 32 | > * 生成图片数据的时候,使图片的字节对齐,避免了Core Animation 的拷贝操作。 33 | > * 使用mmap 进行磁盘的I/O 操作。 34 | 35 | FastImageCache 采用的是 mmap 将文件映射到内存。缺陷是:数据的文件超过内存大小时,会导致内存交换严重降低性能,不过一般这种情况比较少;另外内存中的数据是定时 flush 到文件的,如果数据还未同步时程序挂掉,就会导致数据错误。 36 | 37 | 38 | 参考链接: 39 | 40 | [FastImageCache 解析](http://blog.cnbang.net/tech/2578/) 41 | 42 | [Creating a Bitmap Graphics Context](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-CJBHBFFE) 43 | 44 | [How CacheLine affect the performance of loading data](http://stackoverflow.com/questions/23790837/what-is-byte-alignment-cache-line-alignment-for-core-animation-why-it-matters) 45 | 46 | [Apple iPhone CPU](https://zh.wikipedia.org/wiki/%E8%98%8B%E6%9E%9CA8) 47 | 48 | ##把图像数据赋值给UIImageView ,如果图片数据为没有解码的png / jepg 格式,会先解码为位图数据 49 | 50 | 51 | **加载图片的方式** 52 | > * ```imageWithName:``` 加载图片之后会立刻进行解码,并由系统缓存图片解码后的数据。但是```imageWithName```方法只能用于加载在程序资源目录下的图片。由于解码后的图片是由系统来缓存的,因此也没办法决定什么时候将图片移除,也不能设置缓存大小。 53 | > 54 | > * ```imageWithContentsOfFile:``` 加载图片后,不进行解码。不缓存图片 55 | > 56 | > * ```CGImageSourceCreateImageAtIndex:``` 加载图片后,进行解码,通过设置kCGImageSourceShouldCache ,可以在图片的生命周期内,保存图片解码后的数据。 57 | 58 | 59 | ``` 60 | NSURL *imageURL = [NSURL fileURLWithPath:filePath]; 61 | NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES}; 62 | CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL); 63 | CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,(__bridge CFDictionaryRef)options); 64 | UIImage *image = [UIImage imageWithCGImage:imageRef]; 65 | CGImageRelease(imageRef); 66 | CFRelease(source); 67 | ``` 68 | 69 | 70 | > * 使用UIKit 重绘图片,实现强制解码。图片不缓存 71 | 72 | 73 | ``` 74 | UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, YES, 0); 75 | [image drawInRect:imageView.bounds]; 76 | image = UIGraphicsGetImageFromCurrentImageContext(); 77 | UIGraphicsEndImageContext(); 78 | ``` 79 | 80 | 81 | **自定义缓存** 82 | > * NSCache 基本上就是一个会自动移除对象来释放内存的 NSMutableDictionary。和字典不同的是,它会在系统内存紧张的时候丢弃存储的对象。NSCache是线程安全的,我们可以在不同的线程中添加、删除和查询缓存中的对象,而不需要锁定缓存区域。而且它不像NSMutableDictionary对象,一个缓存对象不会拷贝key对象。 83 | > 84 | > 推荐一个个人觉得很好用的缓存框架:[TMCache](https://github.com/tumblr/TMCache) 85 | 86 | 87 | **图片的压缩格式** 88 | > 89 | 主要来看看[png](https://zh.wikipedia.org/wiki/PNG) 和 [jpeg](https://zh.wikipedia.org/wiki/JPEG) 这两种压缩格式吧。对于PNG图片来说,加载会比JPEG更长,因为文件可能更大,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程。JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JPEG解压算法比基于zip的PNG算法更加复杂。 90 | > 91 | > jpeg 对于噪点大的图片效果比较好,png 适合锋利的线条或者渐变色的图片。对于不友好的png图片,相同像素的JPEG图片总是比PNG加载更快,除非一些非常小的图片、但对于友好的PNG图片,一些中大尺寸的图效果还是很好的。 92 | > ![](./Screen Shot 2015-06-26 at 2.42.23 PM.png) 93 | > 94 | 95 | 96 | ##图像渲染 97 | 关于图像的渲染,主要从以下三点分析: 98 | > **offscreen rendring** 99 | > 100 | > **Blending** 101 | > 102 | > **Rasterize** 103 | 104 | **```offscreen rendering```**指的是在图像在绘制到当前屏幕前,需要先进行一次渲染,之后才绘制到当前屏幕。在进行offscreen rendring的时候,显卡需要另外alloc一块内存来进行渲染,渲染完毕后在绘制到当前屏幕,而且对于显卡来说,onscreen到offscreen的上下文环境切换是非常昂贵的(涉及到OpenGL的pipelines和barrier等), 105 | 106 | 会造成```offscreen rendring```的操作有: 107 | > * layer.mask 的使用 108 | > * layer.maskToBounds 的使用 109 | > * layer.allowsGroupOpacity 设置为yes 和 layer.opacity 小于1.0 110 | > * layer.shouldRasterize 设置为yes 111 | > * layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing 112 | 113 | 114 | **```Blending```** 会导致性能的损失。在iOS的图形处理中,blending主要指的是混合像素颜色的计算。最直观的例子就是,我们把两个图层叠加在一起,如果第一个图层的透明的,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending。更多的计算,导致性能的损失,在一些不需要透明度的地方,可以设置alpha 为1.0 或者减少 图层的叠加。 115 | 116 | 117 | **```Rasterize```**启用shouldRasterize属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。 118 | 当我们使用得当时,光栅化可以提供很大的性能优势但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。 119 | 120 | 图片渲染可以异步的执行,但是有没有遇到当使用tableview 来显示渲染后的内容,添加到```contentView```,```NSString```的sizeWithAttributes,sizeWithFont 和计算cell高度的```systemLayoutSizeFittingSize``` 等都是要在主线程执行的。主线程忙碌会影响用户体验,特别是滚动tableView的时候. 121 | 122 | ![](./Screen Shot 2015-06-30 at 10.56.26 AM.png) 123 | 124 | 把Cell 的content 分解为内容和渲染的方法。内容就是:UIImageView ,Labels。渲染的方法就是为UIImageView 准备图片,为Labels 准备要显示的文字。涉及到渲染的东西一般都是都可以在子线程执行,而内容一般要在主线程执行。在主线程空闲的时候,我们可以做一些预加载。 125 | 126 | **主线程什么时候空闲呀???** 127 | 128 | 主线程的[RunLoop](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html)的状态是可以通过系统提供的API 获取的。 129 | 130 | 可被监听的状态有: 131 | 132 | ``` 133 | typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 134 | kCFRunLoopEntry = (1UL << 0), //RunLoop 开始 135 | kCFRunLoopBeforeTimers = (1UL << 1),//将要执行timer 136 | kCFRunLoopBeforeSources = (1UL << 2),//将要执行Source 137 | kCFRunLoopBeforeWaiting = (1UL << 5),//将要睡眠了 138 | kCFRunLoopAfterWaiting = (1UL << 6), //被唤醒了 139 | kCFRunLoopExit = (1UL << 7), //RunLoop退出 140 | kCFRunLoopAllActivities = 0x0FFFFFFFU 141 | }; 142 | ``` 143 | 144 | ``` 145 | CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 146 | CFStringRef runLoopMode = kCFRunLoopDefaultMode; 147 | CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler 148 | (kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) { 149 | // 在这里处理预加载 150 | }); 151 | CFRunLoopAddObserver(runLoop, observer, runLoopMode); 152 | ``` 153 | 最后调用CFRunLoopRemoveObserver 移除观察者,调用CFRelease 释放观察者。 154 | -------------------------------------------------------------------------------- /调试/SymbolicBreakPoint.md: -------------------------------------------------------------------------------- 1 | UNRECOGNIZED SELECTOR SENT TO INSTANCE 问题快速定位的方法 2 | 五月 7, 2014 ROBOT 3条评论 3 | 开发中常见的一类崩溃错误是遇到:unrecognized selector sent to instance 0xaxxxx…而backtrace又无法明确说明错误在哪行代码,如何快速定位BUG呢? 4 | 5 | 有时读代码一下很难找到是哪个instance出的问题,这时定制有效的DEBUG断点是最好的办法,方法如下: 6 | 7 | 在Debug菜单中选择 Breakpoints -> Create Symbolic Breakpoint… 8 | ![](./SymbolicBreakpoint1-300x69.png) 9 | 10 | -[NSObject(NSObject) doesNotRecognizeSelector:] 11 | 12 | ![](./SymbolicBreakpoint2-300x120.png) 13 | 14 | 然后再运行复现错误时断点会停在真正导致崩溃的地方。 -------------------------------------------------------------------------------- /调试/SymbolicBreakpoint1-300x69.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/调试/SymbolicBreakpoint1-300x69.png -------------------------------------------------------------------------------- /调试/SymbolicBreakpoint2-300x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedon/iOS-tech/bac107cf9c77f4dcaa3f8c9e59855b462d86b3e8/调试/SymbolicBreakpoint2-300x120.png --------------------------------------------------------------------------------